Seleccione la secuencia continua más larga


12

Estoy tratando de construir una consulta en PostgreSQL 9.0 que obtenga la secuencia más larga de filas continuas para una columna específica.

Considere la siguiente tabla:

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

Donde lap_noes único para cada uno (race_id, car_type).

Me gustaría que la consulta produjera la secuencia más larga para un determinado race_idy car_type, por lo tanto, devolvería un int(o largo) que sea el más alto.

Con los siguientes datos:

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

Para car_type = red and race_id = 1la consulta volvería 5como la secuencia más larga del lap_nocampo.

Encontré una pregunta similar aquí, sin embargo, mi situación es un poco más sencilla.

(También me gustaría saber la secuencia más larga para un hecho car_typepara todas las razas, pero estaba planeando resolverlo yo mismo).

Respuestas:


20

Su descripción da como resultado una definición de tabla 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)
);

Solución general para esta clase de problemas.

Para obtener la secuencia más larga (1 resultado, el más largo de todos, selección arbitraria si hay empates):

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)solo cuenta TRUE(= paso al siguiente grupo), lo que da como resultado un nuevo número para cada nuevo grupo.

Pregunta relacionada sobre SO, una respuesta con una solución de procedimiento con plpgsql :

Si el requisito principal es el rendimiento, la función plpgsql suele ser más rápida en este caso particular porque puede calcular el resultado en un solo escaneo.

Más rápido para números consecutivos

Podemos aprovechar el hecho de que una secuencia consecutiva lap_no define una secuencia mucho más simple y 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;

Vueltas consecutivas terminan en el mismo grp. Cada vuelta que falta resulta en una baja grppor partición.

Esto se basa en el (race_id, car_type, lap_no)ser UNIQUE NOT NULL. Los valores NULL o duplicados podrían romper la lógica.

Discusión de la alternativa más simple de Jack

La versión de @ Jack cuenta de manera efectiva todas las vueltas (filas) donde la anterior lap_noen esto race_idtenía lo mismo car_type. Eso es más simple, más rápido y correcto, siempre y cuando cada uno car_typesolo pueda tener una secuencia por race_id.

Pero para una tarea así de simple, la consulta podría ser aún más simple. De ello se sigue lógicamente que todo lap_nopor (car_type, race_id)debe ser de forma secuencial , y que sólo podía contar las vueltas:

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

Si, por otro lado, uno car_typepuede tener múltiples secuencias separadas por race_id (y la pregunta no especifica lo contrario), la versión de Jack fallará.

Más rápido para una carrera / tipo de auto determinado

En respuesta al comentario / aclaraciones en la pregunta: restringir la consulta a una dada (race_id, car_type) lo hará mucho más rápido , por supuesto:

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 <> violín aquí
Viejo violín de SQL

Índice

La clave para lograr un rendimiento superior es un índice de ajuste (a excepción de la solución de procedimiento mencionada que funciona con un solo escaneo secuencial). Un índice de varias columnas como este sirve mejor:

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

Si su tabla tiene la UNIQUErestricción que asumí en la parte superior, eso se implementa solo con este índice (único) internamente, y no necesita crear otro índice.


Hola Erwin, gracias, eso hace el trabajo, sin embargo, ¡tarda ~ 17 segundos en mi base de datos! ¿No supone que podría proporcionar una modificación para que tome race_id y car_type como parámetros en lugar de comparar toda la tabla? (He intentado reescribirlo y seguir encontrando errores)
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|
*/

o tal vez sum((lap_no=(prev+1))::integer)+1pero no estoy seguro de que sea más fácil de leer
Jack dice que intente topanswers.xyz
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.