Usando varias funciones de ventana diferentes y dos subconsultas, esto debería funcionar decentemente rápido:
WITH events(id, event, ts) AS (
VALUES
(1, 12, '2014-03-19 08:00:00'::timestamp)
,(2, 12, '2014-03-19 08:30:00')
,(3, 13, '2014-03-19 09:00:00')
,(4, 13, '2014-03-19 09:30:00')
,(5, 12, '2014-03-19 10:00:00')
)
SELECT first_value(pre_id) OVER (PARTITION BY grp ORDER BY ts) AS pre_id
, id, ts
, first_value(post_id) OVER (PARTITION BY grp ORDER BY ts DESC) AS post_id
FROM (
SELECT *, count(step) OVER w AS grp
FROM (
SELECT id, ts
, NULLIF(lag(event) OVER w, event) AS step
, lag(id) OVER w AS pre_id
, lead(id) OVER w AS post_id
FROM events
WINDOW w AS (ORDER BY ts)
) sub1
WINDOW w AS (ORDER BY ts)
) sub2
ORDER BY ts;
Usando ts
como nombre para la columna de marca de tiempo.
Suponiendo que ts
ser único y indexado (una restricción única lo hace automáticamente).
En una prueba con una tabla de la vida real con 50k filas, solo necesitó un único escaneo de índice . Por lo tanto, debería ser decentemente rápido incluso con mesas grandes. En comparación, su consulta con combinación / distinción no finalizó después de un minuto (como se esperaba).
Incluso una versión optimizada, que trata con una combinación cruzada a la vez (la combinación izquierda sin apenas una condición limitante es efectivamente una combinación limitada). unión cruzada) no terminó después de un minuto.
Para obtener el mejor rendimiento con una mesa grande, ajuste la configuración de la memoria, en particular para work_mem
(para operaciones de gran orden). Considere configurarlo (mucho) más alto para su sesión temporalmente si puede ahorrar RAM. Lea más aquí y aquí.
¿Cómo?
-
En la subconsulta
sub1
mire el evento de la fila anterior y solo manténgalo si ha cambiado, marcando así el primer elemento de un nuevo grupo. Al mismo tiempo, obtenga elid
de la fila anterior y siguiente (pre_id
,post_id
). -
En la subconsulta
sub2
,count()
solo cuenta valores no nulos. Elgrp
resultante marca a los compañeros en bloques de los mismos eventos consecutivos. -
En el
SELECT
final , toma el primerpre_id
y el últimopost_id
por grupo para cada fila para llegar al resultado deseado.
En realidad, esto debería ser aún más rápido en el exteriorSELECT
:last_value(post_id) OVER (PARTITION BY grp ORDER BY ts RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS post_id
... ya que el orden de clasificación de la ventana coincide con la ventana para
pre_id
, por lo que solo se necesita una única ordenación. Una prueba rápida parece confirmarlo. Más información sobre esta definición de cuadro.
Violín SQL.