合并 postgres 中的日期范围以消除重叠

Coalescing date ranges in postgres to eliminate overlaps

如果我有一个 postgres table a:

  member |    start   |    end
---------+------------+------------
    1    | 2015-01-01 | 2015-05-01
---------+------------+------------
    1    | 2015-03-01 | 2015-06-01
---------+------------+------------
    2    | 2015-01-01 | 2015-05-01
---------+------------+------------
    2    | 2015-06-01 | 2015-08-01

我将如何合并日期以消除像这样的重叠范围:

  member |    start   |    end
---------+------------+------------
    1    | 2015-01-01 | 2015-06-01
---------+------------+------------
    2    | 2015-01-01 | 2015-05-01
---------+------------+------------
    2    | 2015-06-01 | 2015-08-01

chop 中,CTE 原始范围 "chopped" 变成更小的、不相交(但可能相邻)的范围。它们由原始范围的所有端点构成,包括开始和结束。

主要select工作如下(从里到外读):

  1. 当范围与前一个范围相邻时,范围的相邻标志为零(假设范围按其开始日期排序)。
  2. 相邻标志的累积和给我们一个分组值:所有相邻范围将具有相同的总和。
  3. 最外面的块只是计算相邻范围组的边界值。

window functions的黑魔法...

with chop as (
  select member,
         pt as start,
         lead(pt) over (partition by member order by pt) finish,
         (
           select count(*)
           from   a
           where  b.member = a.member
           and    b.pt >= a.start
           and    b.pt < a.finish
         ) need_it
  from   (
           select member, start pt from a
           union
           select member, finish pt from a
         ) b
)
-- 3
select member,
       min(start),
       max(finish)
from   (
         -- 2
         select member,
                start,
                finish,
                sum(adjacent) over (partition by member order by start) grp
         from   (
                  -- 1
                  select member,
                         start,
                         finish,
                         case
                           when start <= lag(finish) over (partition by member order by start)
                           then 0
                           else 1
                         end adjacent
                  from   chop
                  where  need_it > 0
                ) t
       ) q
group by member,
         grp
order by member,
         min(start);

我将 end 重命名为 finish 因为 end 是关键字。