我有两个日期check_in
和check_out
的记录,我想知道同时签入多个人时的范围。
所以,如果我有以下签到/结账:
1PM - 6PM
3PM - 10PM
9PM - 11PM
我希望获得3PM - 6PM
(人员A和B的重叠)和9PM - 10PM
(人员B和C的重叠)。
我可以编写一个算法来在线性时间内使用代码执行此操作,是否可以通过线性时间内的PostgreSQL
关系查询来执行此操作?
它需要具有最小响应,意味着没有重叠范围。因此,如果有一个结果给出范围6PM - 9PM
和8PM - 10PM
,那么这将是不正确的。它应该返回6PM - 10pm
。
答案 0 :(得分:1)
解决方案在很大程度上取决于确切的表定义,包括所有约束。由于问题中缺乏信息,我将假设此表:
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()
- 和转为integer
,或者我们从numeric
获取:
sum(sum(change)) OVER (ORDER BY ts)::int
sub3
中查看上一行的计数sub4
中保留重叠时间范围开始和结束的行,并将时间范围的末尾拉到lead()
的同一行。要优化性能我会在plpgsql函数中遍历表一次,如dba.SE上的相关答案所示:
答案 1 :(得分:1)
想法是将时间划分为句点并将其保存为具有指定粒度的位值。
0
- 没有人检查过一粒1
- 有人检查了一粒假设粒度为1小时,期间为1天。
之后我们对范围中的每个值进行二元OR,我们得到答案。
可以在线性时间内完成。这是Oracle的一个例子,但它可以很容易地转换为PostgreSQL。
with rec (checkin, checkout)
as ( select 13, 18 from dual
union all
select 15, 22 from dual
union all
select 21, 23 from dual )
,spanempty ( empt)
as ( select '000000000000000000000000' from dual) ,
spanfull( full)
as ( select '111111111111111111111111' from dual)
, bookingbin( binbook) as ( select substr(empt, 1, checkin) ||
substr(full, checkin, checkout-checkin) ||
substr(empt, checkout, 24-checkout)
from rec
cross join spanempty
cross join spanfull ),
bookingInt (rn, intbook) as
( select rownum, bin2dec(binbook) from bookingbin),
bitAndSum (bitAndSumm) as (
select sum(bitand(b1.intbook, b2.intbook)) from bookingInt b1
join bookingInt b2
on b1.rn = b2.rn -1 ) ,
SumAll (sumall) as (
select sum(bin2dec(binbook)) from bookingBin )
select lpad(dec2bin(sumall - bitAndSumm), 24, '0')
from SumAll, bitAndSum
结果:
000000000000011111111110