SQL - 如何识别给定数据中的 1 小时时间段孤岛?
Posted
技术标签:
【中文标题】SQL - 如何识别给定数据中的 1 小时时间段孤岛?【英文标题】:SQL - How to identify 1 hour time period islands in the given data? 【发布时间】:2021-04-08 19:50:12 【问题描述】:目标是接受收到的第一个投诉,并拒绝在第一个投诉后 1 小时内收到的所有投诉。例如我有下面的数据。
ComplaintID | DateTime |
---|---|
1 | 12/24/2019 1:07 PM |
2 | 12/24/2019 1:20 PM |
3 | 12/24/2019 1:40 PM |
4 | 12/24/2019 2:00 PM |
5 | 12/24/2019 2:10 PM |
6 | 12/24/2019 2:12 PM |
7 | 12/24/2019 2:50 PM |
8 | 12/24/2019 2:55 PM |
9 | 12/24/2019 3:00 PM |
10 | 12/24/2019 3:08 PM |
11 | 12/24/2019 4:00 PM |
12 | 12/24/2019 4:50 PM |
13 | 12/24/2019 7:00 PM |
14 | 12/26/2019 7:01 PM |
所需输出:
ComplaintID | DateTime | Status |
---|---|---|
1 | 12/24/2019 1:07 PM | Accept |
2 | 12/24/2019 1:20 PM | Reject |
3 | 12/24/2019 1:40 PM | Reject |
4 | 12/24/2019 2:00 PM | Reject |
5 | 12/24/2019 2:10 PM | Accept |
6 | 12/24/2019 2:12 PM | Reject |
7 | 12/24/2019 2:50 PM | Reject |
8 | 12/24/2019 2:55 PM | Reject |
9 | 12/24/2019 3:00 PM | Reject |
10 | 12/24/2019 3:08 PM | Reject |
11 | 12/24/2019 4:00 PM | Accept |
12 | 12/24/2019 4:50 PM | Reject |
13 | 12/24/2019 7:00 PM | Accept |
14 | 12/26/2019 7:01 PM | Accept |
我知道使用编程语言会很容易,但是我需要 SQL 中的解决方案。
编辑:
根据@Gordon 的建议,我实现了以下递归查询,它可以工作!但是,它在大数据上似乎效率低下。
with RECURSIVE t AS (
select row_number as rn,ts, lag(ts,1) over (order by row_number) as baseline from main_table where row_number<3
UNION ALL
SELECT
rn+1 as rn
,(select ts from main_table where row_number=rn+1) as ts
,case when datediff('hour',ts,baseline)>24 then ts else baseline end as baseline
from (select * FROM t order by rn desc limit 1 )t where rn<=(select count(*)-1 from main_table)
)
,real_baseline as
(
select rn,ts,lead(baseline,1) over (order by rn) as real_baseline from t
)
select *
,case when row_number() over (partition by real_baseline order by ts) =1 then 'Accept'
else 'Reject' end as status
from real_baseline
【问题讨论】:
我认为这需要递归 CTE —— 这在大量数据上效率不高。 当您说“在 SQL 中”时,您实际上是指纯 SQL,还是仅仅意味着它必须在 DBMS 中发生?它必须是单个查询,还是可以是一个过程?限制的原因是什么? 程序/UDF 也很好。 sql-server 还是 postgres? 查看LAG()
和LEAD()
分析SQL 函数,并尝试在数据中找到session
的开头以获得结果。 here 或 here
【参考方案1】:
通常您可以应用领先/滞后,但这里不能。领先/滞后的问题是所需的不可预测范围。同样,递归 CTE 似乎不可行,因为它需要递归部分中的 MIN 函数;但是,这是不允许的。由于一个函数是令人满意的,也许最好的函数是返回一个表。见fiddle。
create or replace function public.accept_reject_complaints()
returns table( o_complaintid integer
, o_datetime timestamp
, o_status text
)
language plpgsql
AS $$
declare
l_current_end_ts timestamp = '-infinity'::timestamp;
c_complaint_list cursor for
select complaintid, datetime
from complaints
order by datetime;
begin
for complaint_rec in c_complaint_list
loop
if complaint_rec.datetime > l_current_end_ts then
o_status = 'Accept';
l_current_end_ts = complaint_rec.datetime + interval '1 hour';
else
o_status = 'Reject';
end if;
o_datetime = complaint_rec.datetime;
o_complaintid = complaint_rec.complaintid;
return next;
end loop ;
end ;
$$;
不幸的是,由于它涉及游标循环,因此对于大数据量来说,性能将是一个问题。
【讨论】:
【参考方案2】:这是简单的方法。将每个日期时间截断为 Hour,然后在每个小时内将 First 或 Minimum datetime 作为接受,其他作为拒绝。
PS 我已经使用 table_name 作为投诉更改它。在 Postgresql 8 中测试。
SELECT ComplaintID,DateTime,CASE WHEN row_number() over(partition by hour order by
DateTime)=1 THEN 'Accept' else 'Reject' end as Status from
(select ComplaintID,DateTime ,date_trunc('hour',DateTime)as hour from complaint)A ;
【讨论】:
我认为你误解了目标。请查看所需的输出以更好地理解它。 根据您的输出图像 2019 年 12 月 24 日下午 1:07 接受然后 2019 年 12 月 24 日下午 3:08 拒绝应该是“接受”,因为在第一次抱怨 2 小时后直到 3 :07 PM 从 3:08 开始是下一个小时。根据我从第一次开始的理解,该小时应该开始,在这种情况下,每个小时都从 2:07 、 3:07 pm 、4:07 pm .. 开始?【参考方案3】:-
利用
ComplaintID
的连续性,查询为:
with recursive cte as (
select 1 ComplaintID, min(DateTime) DateTime,
min(DateTime) prev
from main_table
union all
select t2.ComplaintID, t2.DateTime,
case when t1.prev + interval '1 hour' < t2.DateTime
then t2.DateTime else t1.prev end
from cte t1 join main_table t2
on t1.ComplaintID+1 = t2.ComplaintID
)
select ComplaintID, DateTime,
case when DateTime=prev
then 'Accept' else 'Reject' end Status
from cte
order by ComplaintID
DB Fiddle
-
提取
Accept
的每一行,查询为:
with recursive cte as (
(
select ComplaintID, DateTime, 'Accept' Status
from main_table order by DateTime limit 1
)
union all
(
select t2.ComplaintID, t2.DateTime, 'Accept'
from cte t1 join main_table t2
on t1.DateTime + interval '1 hour' < t2.DateTime
order by t2.DateTime limit 1
)
)
select t1.ComplaintID, t1.DateTime, coalesce(t2.Status, 'Reject') Status
from main_table t1 left join cte t2
on t1.ComplaintID=t2.ComplaintID
order by t1.ComplaintID
DB Fiddle
【讨论】:
以上是关于SQL - 如何识别给定数据中的 1 小时时间段孤岛?的主要内容,如果未能解决你的问题,请参考以下文章
如何从SQL中的当前时间获取所有时间在下一个小时范围内的数据
SQL Server - 如何根据将插入数据半小时的表中的几个参数来计算持续时间?