2012-06-25 11 views
6

Tengo una tabla que representa el uso de un producto, como un registro. El uso del producto se registra como marcas de tiempo múltiples, quiero representar los mismos datos usando rangos de tiempo.Contraer varias filas con marcas de tiempo contiguas

Parece que este (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 

Quiero colapsar filas cuyo tiempo de diferencia con la ejecución anterior es inferior a un delta (por ejemplo: 2 segundos), y obtener el tiempo y la hora de finalización comenzará, como esto:

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 

Tenga en cuenta que el uso consecutivo de un mismo producto se divide en dos filas, si su uso es más de del ta (2 segundos, en este ejemplo) aparte.

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') 
; 

Respuesta

6

Inspirado por this answer, dado un tiempo atrás por @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 consulta interna asignará enfajadora identificaciones basadas en la diferencia userid, product y el tiempo. Supuse que debería ser seguro PARTITION BY primeros dos campos, de hecho.
  2. groupped_t me da todas las columnas de origen + un número de grupo de ejecución adicional. Solo usé ORDER BY aquí para la función de ventana sum(), ya que necesito que los ID de grupo sean únicos.
  3. periods es solo una consulta de ayuda para la primera y última marca de tiempo de cada grupo.
  4. Por último, unirme a groupped_t con periods en el grp_nr (es por eso que necesitaba que sea único) y una marca de tiempo de la primera entrada en cada grupo.

También puede marcar esta consulta en SQL Fiddle.

Nota, que timestamp, begin y end son reserved words in the SQL (end también para PostgreSQL), por lo que debe evitar o doble citarlos.

+0

Eso es ... hermoso! Funciona exactamente como se esperaba, gracias! –

Cuestiones relacionadas