2012-06-25 10 views
6

Ho una tabella che rappresenta l'utilizzo di un prodotto, un po 'come un registro. L'utilizzo del prodotto viene registrato in più timestamp, voglio rappresentare gli stessi dati utilizzando intervalli di tempo.Comprimi più righe con data e ora contigue

Ecco come si presenta (PostgreSQL 9.1):

userid | timestamp   | product 
------------------------------------- 
001 | 2012-04-23 9:12:05 | foo 
001 | 2012-04-23 9:12:07 | foo 
001 | 2012-04-23 9:12:09 | foo 
001 | 2012-04-23 9:12:11 | barbaz 
001 | 2012-04-23 9:12:13 | barbaz 
001 | 2012-04-23 9:15:00 | barbaz 
001 | 2012-04-23 9:15:01 | barbaz 
002 | 2012-04-24 3:41:01 | foo 
002 | 2012-04-24 3:41:03 | foo 

voglio crollare le righe il cui tempo di differenza con la precedente esecuzione è inferiore a un delta (diciamo: due secondi), e ottenere il tempo e l'ora di fine cominciare, in questo modo:

userid | begin    | end    | product 
---------------------------------------------------------- 
001 | 2012-04-23 9:12:05 | 2012-04-23 9:12:09 | foo 
001 | 2012-04-23 9:12:11 | 2012-04-23 9:12:13 | barbaz 
001 | 2012-04-23 9:15:00 | 2012-04-23 9:15:01 | barbaz 
002 | 2012-04-24 3:41:01 | 2012-04-24 3:41:03 | foo 

si prega di notare che l'utilizzo consecutivo di uno stesso prodotto è suddiviso in due righe se il loro utilizzo è più di del ta (2 secondi, in questo esempio) a parte.

create table t (userid int, timestamp timestamp, product text); 

insert into t (userid, timestamp, product) values 
(001, '2012-04-23 9:12:05', 'foo'), 
(001, '2012-04-23 9:12:07', 'foo'), 
(001, '2012-04-23 9:12:09', 'foo'), 
(001, '2012-04-23 9:12:11', 'barbaz'), 
(001, '2012-04-23 9:12:13', 'barbaz'), 
(001, '2012-04-23 9:15:00', 'barbaz'), 
(001, '2012-04-23 9:15:01', 'barbaz'), 
(002, '2012-04-24 3:41:01', 'foo'), 
(002, '2012-04-24 3:41:03', 'foo') 
; 

risposta

6

Ispirato this answer, dato un po 'indietro dal @a_horse_with_no_name.

WITH groupped_t AS (
SELECT *, sum(grp_id) OVER (ORDER BY userid,product,"timestamp") AS grp_nr 
    FROM (SELECT t.*, 
      lag("timestamp") OVER 
      (PARTITION BY userid,product ORDER BY "timestamp") AS prev_ts, 
      CASE WHEN ("timestamp" - lag("timestamp") OVER 
      (PARTITION BY userid,product ORDER BY "timestamp")) <= '2s'::interval 
      THEN NULL ELSE 1 END AS grp_id 
     FROM t) AS g 
), periods AS (
SELECT min(gt."timestamp") AS grp_min, max(gt."timestamp") AS grp_max, grp_nr 
    FROM groupped_t AS gt 
GROUP BY gt.grp_nr 
) 
SELECT gt.userid, p.grp_min AS "begin", p.grp_max AS "end", gt.product 
    FROM periods p 
    JOIN groupped_t gt ON gt.grp_nr = p.grp_nr AND gt."timestamp" = p.grp_min 
ORDER BY gt.userid, p.grp_min; 
  1. La query più interna assegnerà groupping ID sulla base della differenza userid, product e tempo. Ho pensato che dovrebbe essere sicuro di PARTITION BY primi due campi in effetti.
  2. groupped_t mi fornisce tutte le colonne di origine + un numero di gruppo in esecuzione supplementare. Ho usato solo ORDER BY qui per la funzione finestra sum(), poiché ho bisogno di ID di gruppo per essere univoci.
  3. periods è solo una query di supporto per il primo e l'ultimo timestamp in ciascun gruppo.
  4. Infine, mi unisco allo groupped_t con periods su grp_nr (è per questo che avevo bisogno che fosse univoco) e un timestamp della prima voce in ogni gruppo.

È inoltre possibile controllare questa query su SQL Fiddle.

nota, che timestamp, begin e end sono reserved words in the SQL (end anche per PostgreSQL), così si dovrebbe evitare di uno o due citare.

+0

Questo è ... bello! Funziona esattamente come previsto, grazie! –