在PostgreSQL中查找日期范围之间的交集

我有两个日期check_in和check_out的记录,我想知道同时签入多个人的范围.

所以,如果我有以下签入/签出:

>人A:下午1点至下午6点
> B人:下午3点至晚上10点
>人C:晚上9点 – 晚上11点

我希望得到下午3点 – 下午6点(人A和B重叠)和晚上9点 – 晚上10点(人B和C重叠).

我可以用线性时间编写一个算法用代码来完成这个,是否可以通过线性时间的关系查询和PostgreSQL来做到这一点?

它需要具有最小响应,意味着没有重叠范围.因此,如果有一个结果给出了下午6点到晚上9点和晚上8点到晚上10点的范围,那将是不正确的.它应该在下午6点到晚上10点返回.

最佳答案 假设

解决方案在很大程度上取决于包括所有约束的确切表定义.由于问题中缺乏信息,我将假设此表:

CREATE TABLE booking (
  booking_id serial PRIMARY KEY
, check_in   timestamptz NOT NULL
, check_out  timestamptz NOT NULL
, CONSTRAINT valid_range CHECK (check_out > check_in)
);

因此,没有NULL值,只有包含较低和独占上限的有效范围,我们并不真正关心谁签入.

假设当前版本的Postgres,至少9.2.

询问

使用UNION ALL和窗口函数只使用SQL的一种方法:

SELECT ts AS check_id, next_ts As check_out
FROM  (
   SELECT *, lead(ts) OVER (ORDER BY ts) AS next_ts
   FROM  (
      SELECT *, lag(people_ct, 1 , 0) OVER (ORDER BY ts) AS prev_ct
      FROM  (
         SELECT ts, sum(sum(change)) OVER (ORDER BY ts)::int AS people_ct
         FROM  (
            SELECT check_in AS ts, 1 AS change FROM booking
            UNION ALL
            SELECT check_out, -1 FROM booking
            ) sub1
         GROUP  BY 1
         ) sub2
      ) sub3
   WHERE  people_ct > 1 AND prev_ct < 2 OR  -- start overlap
          people_ct < 2 AND prev_ct > 1     -- end overlap
   ) sub4
WHERE  people_ct > 1 AND prev_ct < 2;

SQL Fiddle.

说明

>在子查询中,sub1在一列中派生check_in和check_out表. check_in为人群添加一个,check_out减去一个.
>在sub2中对同一时间点的所有事件求和并用窗口函数计算一个运行计数:这是一个总和sum()上的窗口函数sum() – 并转换为整数或者我们从中得到数字:

 sum(sum(change)) OVER (ORDER BY ts)::int

>在sub3中查看前一行的计数
>在sub4中,仅保留重叠时间范围开始和结束的行,并使用lead()将时间范围的末尾拉到同一行.
>最后,只保留时间范围开始的行.

为了优化性能,我将在plpgsql函数中遍历表,如dba.SE上的相关答案所示:

> Calculate Difference in Overlapping Time in PostgreSQL / SSRS

点赞