Jos*_*itz 5 sql postgresql relational-database window-functions gaps-and-islands
我有两个日期check_in和的记录check_out,我想知道同时签到多个人的范围。
因此,如果我有以下签入/签出:
1PM - 6PM3PM - 10PM9PM - 11PM我想得到3PM - 6PM(人A和B的9PM - 10PM重叠)和(人B和C的重叠)。
我可以编写一个算法来使用代码在线性时间内做到这一点,是否也可以通过线性时间通过关系查询来做到这一点PostgreSQL?
它需要具有最小的响应,这意味着没有重叠的范围。因此,如果有这给了范围的结果6PM - 9PM和8PM - 10PM它是不正确的。它应该返回6PM - 10pm。
该解决方案在很大程度上取决于确切的表定义(包括所有约束)。由于问题中缺乏信息,我将假设此表:
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)
);
Run Code Online (Sandbox Code Playgroud)
因此,没有 NULL 值,只有包含下限和排除上限的有效范围,而且我们并不真正关心谁签入。
还假设 Postgres 的当前版本至少为9.2。
UNION ALL一种仅使用 SQL 使用 a和窗口函数来实现此目的的方法:
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;
Run Code Online (Sandbox Code Playgroud)
在子查询中派生出和sub1的表中的一列。给人群加一,减一。check_incheck_outcheck_incheck_out
总而言之,sub2同一时间点的所有事件并使用窗口函数计算运行计数:这是sum()聚合上的窗口函数sum()- 并转换为integer或我们numeric从中得到:
sum(sum(change)) OVER (ORDER BY ts)::int
Run Code Online (Sandbox Code Playgroud)
查看sub3上一行的计数
仅sub4保留重叠时间范围开始和结束的行,并将时间范围的末尾拉到与 相同的行中lead()。
最后,只保留时间范围开始的行。
为了优化性能,我将在 plpgsql 函数中遍历该表一次,如 dba.SE 上的相关答案所示:
| 归档时间: |
|
| 查看次数: |
976 次 |
| 最近记录: |