Pilih urutan berkelanjutan terpanjang

12

Saya mencoba membuat kueri dalam PostgreSQL 9.0 yang mendapatkan urutan terpanjang dari baris kontinu untuk kolom tertentu.

Pertimbangkan tabel berikut:

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

Di mana lap_nounik untuk masing-masing (race_id, car_type).

Saya ingin permintaan untuk menghasilkan urutan terpanjang untuk yang diberikan race_iddan car_type, jadi itu akan mengembalikan int(atau panjang) yang tertinggi.

Dengan data berikut:

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

Untuk car_type = red and race_id = 1kueri akan kembali 5sebagai urutan lap_nobidang terlama .

Saya menemukan pertanyaan serupa di sini namun situasi saya sedikit lebih mudah.

(Saya juga ingin tahu urutan terpanjang untuk diberikan car_typeuntuk semua ras, tetapi berencana untuk menyelesaikannya sendiri.)

DaveB
sumber

Jawaban:

20

Deskripsi Anda menghasilkan definisi tabel seperti ini:

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)
);

Solusi umum untuk kelas masalah ini

Untuk mendapatkan urutan terpanjang (1 hasil, terpanjang dari semua, pilih sewenang-wenang jika ada ikatan):

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)hanya dihitung TRUE(= langkah ke grup berikutnya), yang menghasilkan angka baru untuk setiap grup baru.

Terkait pertanyaan tentang SO, satu jawaban yang menampilkan solusi prosedural dengan plpgsql :

Jika persyaratan teratas adalah kinerja, fungsi plpgsql biasanya lebih cepat dalam kasus khusus ini karena dapat menghitung hasilnya dalam pemindaian tunggal.

Lebih cepat untuk nomor berurutan

Kita dapat memanfaatkan fakta bahwa berturut - turutlap_no menentukan urutan, untuk versi yang jauh lebih sederhana dan lebih cepat :

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;

Putaran berturut-turut berakhir dengan hal yang sama grp. Setiap putaran yang hilang menghasilkan lebih rendah grpper partisi.

Ini bergantung pada (race_id, car_type, lap_no)keberadaan UNIQUE NOT NULL. Nilai NULL atau duplikat dapat mematahkan logika.

Diskusi tentang alternatif Jack yang lebih sederhana

Versi @ Jack secara efektif menghitung semua putaran (baris) di mana sebelumnya lap_nodalam hal ini race_idsama car_type. Itu lebih sederhana dan lebih cepat dan benar - selama masing car_type- masing hanya dapat memiliki satu urutan per race_id.

Tetapi untuk tugas sesederhana itu kueri bisa lebih sederhana, belum. Ini akan mengikuti secara logis bahwa semua lap_noper (car_type, race_id)harus dalam urutan , dan kami hanya bisa menghitung putaran:

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

Sebaliknya, jika seseorang car_typedapat memiliki beberapa urutan terpisah per race_id (dan pertanyaannya tidak menentukan sebaliknya), versi Jack akan gagal.

Lebih cepat untuk jenis balapan / mobil tertentu

Sebagai balasan atas komentar / klarifikasi dalam pertanyaan: membatasi kueri ke yang diberikan (race_id, car_type) akan membuatnya lebih cepat , tentu saja:

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 <> biola di sini
Old SQL Fiddle

Indeks

Kunci untuk kinerja terbaik adalah indeks pas (kecuali untuk solusi prosedural yang disebutkan bekerja dengan pemindaian sekuensial tunggal). Sebuah indeks multicolumn seperti ini berfungsi terbaik:

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

Jika tabel Anda memiliki UNIQUEkendala yang saya asumsikan di atas, itu diterapkan hanya dengan indeks (unik) ini secara internal, dan Anda tidak perlu membuat indeks lain.

Erwin Brandstetter
sumber
Hai Erwin, terima kasih yang melakukan pekerjaannya, namun dibutuhkan ~ 17dt pada database saya! Jangan kira Anda bisa memberikan modifikasi sehingga dibutuhkan race_id dan car_type sebagai parameter daripada membandingkan seluruh tabel? (Saya telah mencoba menulis ulang dan terus mengalami kesalahan)
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|
*/
Jack mengatakan coba topanswers.xyz
sumber
atau mungkin sum((lap_no=(prev+1))::integer)+1tetapi saya tidak yakin itu lebih mudah dibaca
Jack mengatakan coba topanswers.xyz