Sua descrição resulta em uma definição de tabela como esta:
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)
);
Solução geral para esta classe de problemas
Para obter a sequência mais longa (1 resultado, o mais longo de todos, escolha arbitrária se houver empate):
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)
conta apenas TRUE
(= passo para o próximo grupo), o que resulta em um novo número para cada novo grupo.
Pergunta relacionada ao SO, uma resposta que apresenta uma solução procedural com o plpgsql :
Se o principal requisito for o desempenho, a função plpgsql normalmente é mais rápida nesse caso específico, pois pode calcular o resultado em uma única varredura.
Mais rápido para números consecutivos
Podemos capitalizar o fato de definir consecutivamente lap_no
uma sequência, para uma versão muito mais simples e rápida :
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;
Voltas consecutivas acabam no mesmo grp
. Cada volta faltando resulta em uma menor grp
por partição.
Isso depende de (race_id, car_type, lap_no)
ser UNIQUE NOT NULL
. Valores nulos ou duplicatas podem quebrar a lógica.
Discussão da alternativa mais simples de Jack
@ Versão de Jack conta efetivamente todas as voltas (linhas), onde o anterior lap_no
nesta race_id
tinham o mesmo car_type
. Isso é mais simples, rápido e correto - desde que cada um car_type
possa ter apenas uma sequência por race_id
.
Mas, para uma tarefa tão simples, a consulta ainda pode ser mais simples. Segue-se logicamente que todos os lap_no
por (car_type, race_id)
devem estar em sequência , e poderíamos apenas contar as voltas:
SELECT race_id, car_type, count(*) AS seq_len
FROM tbl
GROUP BY race_id, car_type
ORDER BY seq_len DESC
LIMIT 1;
Se, por outro lado, é car_type
possível ter várias seqüências separadas por race_id (e a pergunta não especificar de outra forma), a versão de Jack falhará.
Mais rápido para um determinado tipo de corrida / carro
Em resposta ao comentário / esclarecimentos da pergunta: restringir a consulta a uma dada (race_id, car_type)
tornará muito mais rápido , é claro:
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 <> mexer aqui
Old SQL Fiddle
Índice
A chave para o desempenho superior é um índice adequado (exceto para a solução processual mencionada que trabalha com uma única varredura seqüencial). Um índice de várias colunas como este serve melhor:
CREATE INDEX tbl_mult_idx ON tbl (race_id, car_type, lap_no);
Se sua tabela tiver a UNIQUE
restrição que assumi na parte superior, isso será implementado apenas com esse índice (exclusivo) internamente e você não precisará criar outro índice.
fonte
sum((lap_no=(prev+1))::integer)+1
mas não tenho certeza de que é mais fácil de ler