Utilizzando diverse funzioni della finestra e due sottoquery, questo dovrebbe funzionare in modo decentemente veloce:
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
come nome per la colonna timestamp.
Supponendo ts
essere unico e indicizzato (un vincolo univoco lo fa automaticamente).
In un test con una tabella reale con 50.000 righe era necessaria solo una singola scansione dell'indice . Quindi, dovrebbe essere abbastanza veloce anche con tavoli grandi. In confronto, la tua query con join/distinta non è terminata dopo un minuto (come previsto).
Anche una versione ottimizzata, che gestisce un cross join alla volta (il join sinistro con una condizione a malapena limitante è effettivamente un cross join) non è terminato dopo un minuto.
Per ottenere le migliori prestazioni con un tavolo grande, regola le impostazioni della memoria, in particolare per work_mem
(per grandi operazioni di ordinamento). Considera di impostarlo (molto) più alto per la tua sessione temporaneamente se puoi risparmiare la RAM. Leggi di più qui e qui.
Come?
-
Nella sottoquery
sub1
guarda l'evento della riga precedente e tienilo solo se è cambiato, segnando così il primo elemento di un nuovo gruppo. Allo stesso tempo, ottieni l'id
della riga precedente e successiva (pre_id
,post_id
). -
Nella sottoquery
sub2
,count()
conta solo valori non nulli. Il risultatogrp
contrassegna i peer in blocchi di stessi eventi consecutivi. -
Nel
SELECT
finale , prendi il primopre_id
e l'ultimopost_id
per gruppo per ogni riga per arrivare al risultato desiderato.
In realtà, questo dovrebbe essere ancora più veloce nelSELECT
esterno :last_value(post_id) OVER (PARTITION BY grp ORDER BY ts RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) AS post_id
... poiché l'ordinamento della finestra concorda con la finestra per
pre_id
, quindi è necessario un solo ordinamento. Un rapido test sembra confermarlo. Ulteriori informazioni su questa definizione di frame.
SQL Fiddle.