En uzun sürekli diziyi seçin


12

Belirli bir sütun için sürekli satırların en uzun dizisini alır PostgreSQL 9.0 bir sorgu oluşturmaya çalışıyorum.

Aşağıdaki tabloyu düşünün:

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

Nerede lap_noher biri için benzersizdir (race_id, car_type).

Belirli bir için en uzun dizisi üretmek için sorgu istiyorum race_idve car_typebu yüzden int(veya uzun) en yüksek dönecekti .

Aşağıdaki verilerle:

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

İçin car_type = red and race_id = 1sorgunun dönecekti 5en uzun dizisi olarak lap_nosahada.

Burada benzer bir soru buldum, ancak durumum biraz daha basit.

(Ayrıca car_typetüm yarışlar için verilen en uzun diziyi bilmek istiyorum , ama bunu kendim halletmeyi planlıyordum.)

Yanıtlar:


20

Açıklamanız şöyle bir tablo tanımıyla sonuçlanır :

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

Bu sorun sınıfı için genel çözüm

En uzun sekansı elde etmek için (1 sonuç, en uzun, bağlar varsa keyfi seçim):

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)sadece TRUEher yeni grup için yeni bir sayı ile sonuçlanan (= sonraki gruba adım) sayar .

Pl ile ilgili soru, plpgsql ile prosedürel bir çözüm içeren bir cevap :

En yüksek gereksinim performanssa, plpgsql işlevi bu özel durumda genellikle daha hızlıdır çünkü sonucu tek bir taramada hesaplayabilir.

Ardışık sayılar için daha hızlı

Çok daha basit ve daha hızlı bir sürüm için ardışık lap_no bir sekans tanımlaması gerçeğinden yararlanabiliriz :

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;

Ardışık turlar da aynı şekilde sonuçlanır grp. Eksik olan her tur, grpbölüm başına daha düşük bir sonuç verir .

Bu (race_id, car_type, lap_no)varlığa dayanır UNIQUE NOT NULL. NULL değerler veya kopyalar mantığı bozabilir.

Jack'in daha basit alternatifinin tartışılması

Jack'in versiyonu @ etkili tüm tur (satırlar) sayar önceki nereye lap_nobunda race_idaynı vardı car_type. Bu daha basit ve daha hızlı ve doğrudur - her biri car_typesadece bir sıraya sahip olabildiği sürece race_id.

Ancak bu kadar basit bir görev için, sorgu henüz daha basit olabilir. Hepsi mantıksal olarak takip edecek lap_nobaşına (car_type, race_id)olmalıdır sırayla ve biz sadece tur güvenebileceğimi:

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

Öte yandan, tek Eğer car_typeolabilir birden fazla ayrı dizileri başına race_id (ve soru aksi belirtmez), Jack'in versiyonu başarısız olur.

Belirli bir yarış / araba tipi için daha hızlı

Sorudaki açıklamaya / açıklamalara cevap olarak: sorguyu belirli bir soruyla sınırlamak elbette çok daha hızlı(race_id, car_type) hale getirecektir :

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 <> keman burada
Eski SQL Keman

indeks

En üst düzeyde performans bir uyum indeksidir (tek bir ardışık tarama ile çalışan söz konusu prosedür çözümü hariç). Bunun gibi çok sütunlu bir dizin en iyi sonucu verir:

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

Masanız varsa UNIQUEkısıtlamayı içeriden sadece bu (benzersiz) endeksi ile uygulanan üstündeki varsayılır ve do not başka bir dizin oluşturmanız gerekir.


Merhaba Erwin, teşekkürler bu işi yapar, ancak benim veritabanı ~ 17 saniye sürer! Bir değişiklik sağlayabileceğinizi varsayalım, bu yüzden tüm tabloyu karşılaştırmak yerine race_id ve car_type parametrelerini alır? (Yeniden yazmayı denedim ve hatalarla karşılaşmaya devam
ettim

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|
*/

ya da belki de sum((lap_no=(prev+1))::integer)+1bunun daha kolay olduğundan emin değilim
Jack diyor ki topanswers.xyz
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.