【问题标题】:Collapse multiple rows having contiguous timestamps折叠具有连续时间戳的多行
【发布时间】:2012-06-25 09:22:01
【问题描述】:

我有一张表,表示产品的使用情况,有点像日志。产品使用记录为多个时间戳,我想使用时间范围表示相同的数据。

看起来像这样(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

我想折叠与上一次运行时间差小于 delta(例如:2 秒)的行,并获取开始时间和结束时间,像这样:

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

请注意,如果同一产品的连续使用时间间隔超过 delta(在本例中为 2 秒),则会将它们分成两行。

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

【问题讨论】:

    标签: sql postgresql


    【解决方案1】:

    灵感来自 this answer,不久前由 @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. 最里面的查询将根据useridproduct 和时间差分配分组ID。实际上,我认为PARTITION BY 前两个字段应该是安全的。
    2. groupped_t 给了我所有的源列 + 一个额外的运行组号。我在这里只使用了ORDER BY 用于sum() 窗口函数,因为我需要组ID 是唯一的。
    3. periods 只是每个组中第一个和最后一个时间戳的辅助查询。
    4. 最后,我在grp_nr 上加入groupped_tperiods(这就是为什么我需要它是唯一的)以及每个组中第一个条目的时间戳。

    您也可以在SQL Fiddle 上查看此查询。

    请注意,timestampbeginendreserved words in the SQLend 也适用于 PostgreSQL),因此您应该避免或双引号。

    【讨论】:

    • 我真的很喜欢您使用 1 和 null 将寄存器组合在一起的方式!我一直在努力解决这个问题,尝试使用 0 和 1... 像这样的简单转折怎么会发生如此大的变化,对吧?太棒了,谢谢!
    猜你喜欢
    • 2014-12-19
    • 2016-07-07
    • 2021-12-09
    • 1970-01-01
    • 2021-11-12
    • 1970-01-01
    • 1970-01-01
    • 2018-11-22
    • 1970-01-01
    相关资源
    最近更新 更多