我有两个日期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;
说明
>在子查询中,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