使用Postgresql合并连续行

时间:2014-10-12 20:13:34

标签: sql postgresql

我有一个slots表,如下所示:

   Column   |            Type             |
------------+-----------------------------+
 id         | integer                     |
 begin_at   | timestamp without time zone |
 end_at     | timestamp without time zone |
 user_id    | integer                     |

我想连续选择合并的行。假设我有(简化)数据,如:

(1, 5:15, 5:30, 1)
(2, 5:15, 5:30, 2)
(3, 5:30, 5:45, 2)
(4, 5:45, 6:00, 2)
(5, 8:15, 8:30, 2)
(6, 8:30, 8:45, 2)

我想知道是否可以选择格式为:

的行
(5:15, 5:30, 1)
(5:15, 6:00, 2) // <======= rows id 2,3 and 4 merged
(8:15, 8:45, 2) // <======= rows id 5 and 6 merged

修改 这是SQLfiddle

我正在使用Postgresql,版本9.3!

谢谢!

2 个答案:

答案 0 :(得分:12)

这是解决此问题的一种方法。创建一个标志,确定一条记录是否与前一条记录重叠。这是一个小组的开始。然后获取此标志的累积总和并将其用于分组:

select user_id, min(begin_at) as begin_at, max(end_at) as end_at
from (select s.*, sum(startflag) over (partition by user_id order by begin_at) as grp
      from (select s.*,
                   (case when lag(end_at) over (partition by user_id order by begin_at) >= begin_at
                         then 0 else 1
                    end) as startflag
            from slots s
           ) s
     ) s
group by user_id, grp;

Here是一个SQL小提琴。

答案 1 :(得分:2)

Gordon Linoff已经提供了答案(我投了赞成票)。

我使用了相同的方法,但想要处理tsrange type。 所以我想出了this construct

SELECT min(id) b_id, min(begin_at) b_at, max(end_at) e_at, grp, user_id
  FROM (
    SELECT t.*, sum(g) OVER (ORDER BY id) grp
      FROM (
        SELECT s.*, (NOT r -|- lag(r,1,r)
                     OVER (PARTITION BY user_id ORDER BY id))::int g
          FROM (SELECT id,begin_at,end_at,user_id,
                       tsrange(begin_at,end_at,'[)') r FROM slots) s
      ) t
  ) u
 GROUP BY grp, user_id
 ORDER BY grp;

不幸的是,在顶层,必须使用min(begin_at)max(end_at),因为基于范围的联合运算符+没有聚合函数。

我创建了具有独占上限的范围,这允许我使用“is adjacent to” (-|-) operator。我将当前tsrange与前一行中的integer进行比较,默认为当前行,以防前一行没有。然后我否定比较并转换为1,在新小组开始的情况下,这会给我{{1}}。