PostgreSQL
 sql >> Database >  >> RDS >> PostgreSQL

Funzione della finestra di PostgreSQL:partizione per confronto

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?

  1. 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 ).

  2. Nella sottoquery sub2 , count() conta solo valori non nulli. Il risultato grp contrassegna i peer in blocchi di stessi eventi consecutivi.

  3. Nel SELECT finale , prendi il primo pre_id e l'ultimo post_id per gruppo per ogni riga per arrivare al risultato desiderato.
    In realtà, questo dovrebbe essere ancora più veloce nel SELECT 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.