Twój opis daje następującą definicję tabeli :
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)
);
Ogólne rozwiązanie dla tej klasy problemów
Aby uzyskać najdłuższą sekwencję (1 wynik, najdłuższy ze wszystkich, arbitralny wybór, jeśli istnieją remisy):
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)
liczy się tylko TRUE
(= krok do następnej grupy), co daje nową liczbę dla każdej nowej grupy.
Powiązane pytanie dotyczące SO, jedna odpowiedź zawierająca rozwiązanie proceduralne z plpgsql :
Jeśli najwyższym wymaganiem jest wydajność, funkcja plpgsql jest zazwyczaj szybsza w tym konkretnym przypadku, ponieważ może obliczyć wynik w jednym skanie.
Szybciej dla kolejnych numerów
Możemy wykorzystać fakt, że kolejne lap_no
definiują sekwencję dla znacznie prostszej i szybszej wersji :
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;
Kolejne okrążenia kończą się tak samo grp
. Każde brakujące okrążenie powoduje zmniejszenie grp
liczby partycji.
To zależy od (race_id, car_type, lap_no)
bycia UNIQUE NOT NULL
. Wartości NULL lub duplikaty mogą uszkodzić logikę.
Omówienie prostszej alternatywy Jacka
@ Wersja Jacka skutecznie zlicza wszystkie okrążenia (wiersze), gdzie poprzednie lap_no
w ten race_id
miał takie same car_type
. To jest prostsze, szybsze i poprawne - o ile każdy car_type
może mieć tylko jedną sekwencję na race_id
.
Jednak w przypadku tak prostego zadania zapytanie może być jeszcze prostsze. Logiczne byłoby, że wszystkie lap_no
na (car_type, race_id)
muszą być w sekwencji , i moglibyśmy po prostu policzyć okrążenia:
SELECT race_id, car_type, count(*) AS seq_len
FROM tbl
GROUP BY race_id, car_type
ORDER BY seq_len DESC
LIMIT 1;
Z drugiej strony, jeśli car_type
można mieć wiele oddzielnych sekwencji na identyfikator_ wyścigu (a pytanie nie określa inaczej), wersja Jacka się nie powiedzie.
Szybciej dla danego typu wyścigu / samochodu
W odpowiedzi na komentarz / wyjaśnienia w pytaniu: ograniczenie zapytania do jednego (race_id, car_type)
poda go , oczywiście, znacznie szybciej :
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 <> skrzypce tutaj
Old SQL Fiddle
Indeks
Kluczem do najwyższej wydajności jest indeks dopasowania (z wyjątkiem wspomnianego rozwiązania proceduralnego pracującego z pojedynczym skanem sekwencyjnym). Taki indeks wielokolumnowy najlepiej służy:
CREATE INDEX tbl_mult_idx ON tbl (race_id, car_type, lap_no);
Jeśli twoja tabela ma UNIQUE
ograniczenie, które założyłem u góry, jest ono implementowane tylko z tym (unikalnym) indeksem wewnętrznie i nie musisz tworzyć kolejnego indeksu.
źródło
sum((lap_no=(prev+1))::integer)+1
ale nie jestem pewien, czy jest to łatwiejsze do odczytania