最長連続シーケンスを選択


12

特定の列の連続する行の最長シーケンスを取得するPostgreSQL 9.0でクエリを作成しようとしています。

次の表を考慮してください。

lap_id (serial), lap_no (int), car_type (enum), race_id (int FK)

どこlap_noがそれぞれに一意です(race_id, car_type)

クエリで指定されたrace_idandの最長のシーケンスを生成car_typeしたいので、int最高の(または長い)を返します。

次のデータで:

1, 1, red, 1
2, 2, red, 1
3, 3, red, 1
4, 4, red, 1
5, 1, blue, 1
6, 5, red, 1
7, 2, blue, 1
8, 1, green, 1

car_type = red and race_id = 1クエリの場合、フィールドの5最長シーケンスとして返されlap_noます。

ここで同様の質問を見つけましたが、私の状況はもう少し簡単です。

car_typeすべてのレースで与えられたものの中で最長のシーケンスを知りたいのですが、自分で解決することを計画していました。)

回答:


20

説明の結果、テーブル定義は次のようになります。

CREATE TABLE tbl (
   lap_id   serial PRIMARY KEY
 , lap_no   int NOT NULL
 , car_type enum NOT NULL
 , race_id  int NOT NULL  -- REFERENCES ...
 , UNIQUE(race_id, car_type, lap_no)
);

このクラスの問題の一般的な解決策

最長のシーケンスを取得するには(結果が1つ、最長の場合、同点の場合は任意に選択):

SELECT race_id, car_type, count(*) AS seq_len
FROM  (
   SELECT *, count(*) FILTER (WHERE step)
                      OVER (ORDER BY race_id, car_type, lap_no) AS grp
   FROM  (
      SELECT *, (lag(lap_no) OVER (PARTITION BY race_id, car_type ORDER BY lap_no) + 1)
                 IS DISTINCT FROM lap_no AS step
      FROM   tbl
      ) x
   ) y
GROUP  BY race_id, car_type, grp
ORDER  BY seq_len DESC
LIMIT  1;

count(*) FILTER (WHERE step)カウントのみTRUE(=次のグループへのステップ)。これにより、新しいグループごとに新しい番号が作成されます。

SOに関連する質問、plpgsqlによる手続き型ソリューションを特徴とする1つの答え:

一番の要件がパフォーマンスである場合、この特定のケースでplpgsql関数は通常、1回のスキャンで結果を計算できるため高速です。

連続番号の方が速い

連続 lap_noしてシーケンスを定義するという事実を利用して、よりシンプルで高速なバージョンを作成できます。

SELECT race_id, car_type, count(*) AS seq_len
FROM  (
   SELECT race_id, car_type
        , row_number() OVER (PARTITION BY race_id, car_type ORDER BY lap_no) - lap_no AS grp
   FROM   tbl
   ) x
GROUP  BY race_id, car_type, grp
ORDER  BY seq_len DESC
LIMIT  1;

連続したラップは同じ結果になりgrpます。ラップが欠落するたびに、grpパーティションごとに低くなります。

これは存在する(race_id, car_type, lap_no)ことに依存していUNIQUE NOT NULLます。NULL値または重複は、ロジックを破壊する可能性があります。

ジャックのより単純な代替案の議論

@Jackのバージョンは、これの前のものlap_norace_id同じであったすべてのラップ(行)を効果的にカウントしますcar_type。それはより簡単で、より速く、正確です-それぞれcar_typeがにつき1 つのシーケンスしか持つことができない限りrace_id

しかし、そのような単純なタスクの場合、クエリはさらにシンプルになります。論理的には、lap_noper (car_type, race_id)はすべて順番に並んでいる必要があり、ラップを数えるだけです。

SELECT race_id, car_type, count(*) AS seq_len
FROM   tbl
GROUP  BY race_id, car_type
ORDER  BY seq_len DESC
LIMIT  1;

一方、race_idごとに複数の個別のシーケンスを使用car_typeできる場合(および質問では特に指定されていない場合)、Jackのバージョンは失敗します。

特定のレース/車のタイプにより高速

質問のコメント/説明への回答:クエリを指定 (race_id, car_type)されたものに制限すると、もちろんはるかに速くなります

SELECT count(*) AS seq_len
FROM  (
   SELECT row_number() OVER (ORDER BY lap_no) - lap_no AS grp
   FROM   tbl
   WHERE  race_id = 1
   AND    car_type = 'red'
   ) x
GROUP  BY grp
ORDER  BY seq_len DESC
LIMIT  1;

db <> fiddle here
古いSQL Fiddle

索引

最高のパフォーマンスの鍵は、フィッティングインデックスです(前述の手順ソリューションを除き、単一の順次スキャンで動作します)。複数列インデックスこのような最高の機能します:

CREATE INDEX tbl_mult_idx ON tbl (race_id, car_type, lap_no);

テーブルにUNIQUE私が一番上に想定した制約がある場合、それは内部でこの(一意の)インデックスだけで実装され、別のインデックスを作成する必要ありませ


こんにちはアーウィン、仕事をしてくれてありがとう、しかし私のデータベースでは〜17秒かかります!テーブル全体を比較するのではなく、race_idとcar_typeをパラメーターとして受け取るように変更を提供できると思いませんか?(書き直してみましたが、エラーが発生し続けています)
-DaveB

7

create table tbl (lap_no int, car_type text, race_id int);
insert into tbl values (1,'red',1),(2,'red',1),(3,'red',1),(4,'red',1),
                       (1,'blue',1),(5,'red',1),(2,'blue',1),(1,'green',1);
select car_type, race_id, sum(case when lap_no=(prev+1) then 1 else 0 end)+1 seq_len
from ( select *, lag(lap_no) over (partition by car_type, race_id order by lap_no) prev 
       from tbl ) z
group by car_type, race_id
order by seq_len desc limit 1;
/*
|car_type|race_id|seq_len|
|:-------|------:|------:|
|red     |      1|      5|
*/

またはおそらくsum((lap_no=(prev+1))::integer)+1、それが読みやすいかどうかわからない
ジャックはtopanswers.xyzを試す13
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.