另一种方法,使用gaps and islands 方法。答案基于SQL Time Packing of Islands
现场测试:http://sqlfiddle.com/#!18/462ac/11
with gap_detector as
(
select
DateOpen, DateClosed,
case when
lag(DateClosed) over (order by DateOpen) is null
or lag(DateClosed) over (order by DateOpen) < DateOpen
then
1
else
0
end as gap
from dt
)
, downtime_grouper as
(
select
DateOpen, DateClosed,
sum(gap) over (order by DateOpen) as downtime_group
from gap_detector
)
-- group's open and closed detector. then computes the group's downtime
select
downtime_group,
min(DateOpen) as group_date_open,
max(DateClosed) as group_date_closed,
datediff(minute, min(DateOpen), max(DateClosed)) as group_downtime,
sum(datediff(minute, min(DateOpen), max(DateClosed)))
over(order by downtime_group) as downtime_running_total
from downtime_grouper
group by downtime_group
输出:
工作原理
如果 DateOpen 之前没有停机时间,则它是一系列停机时间的开始(由 null lag(DateClosed) 指示)。如果 DateOpen 与上一个停机时间的 DateClosed 有间隔,它也是一系列停机时间的开始。
with gap_detector as
(
select
lag(DateClosed) over (order by DateOpen) as previous_downtime_date_closed,
DateOpen, DateClosed,
case when
lag(DateClosed) over (order by DateOpen) is null
or lag(DateClosed) over (order by DateOpen) < DateOpen
then
1
else
0
end as gap
from dt
)
select *
from gap_detector
order by DateOpen;
输出:
在检测到间隔开始后,我们会计算间隔的总和,这样我们就可以对彼此相邻的停机时间进行分组。
with gap_detector as
(
select
DateOpen, DateClosed,
case when
lag(DateClosed) over (order by DateOpen) is null
or lag(DateClosed) over (order by DateOpen) < DateOpen
then
1
else
0
end as gap
from dt
)
select
DateOpen, DateClosed, gap,
sum(gap) over (order by DateOpen) as downtime_group
from gap_detector
order by DateOpen;
从上面的输出中可以看出,我们现在可以通过在 downtime_group 上分组应用 MIN(DateOpen) 和 MAX(DateClosed),轻松检测停机组的最早 DateOpen 和最新 DateClosed。在 downtime_group 1 上,我们有 08:00 的最早 DateOpen 和 08:45 的最新 DateClosed。在 downtime_group 2 上,我们有 09:06 的最早 DateOpen 和 9:41 的最新 DateClosed。即使同时出现停机时间,我们也可以据此重新计算正确的停机时间。
我们可以通过反转逻辑来消除对 null 先前停机时间的检测(我们正在评估的当前行是表中的第一行),从而使代码更短。我们不是检测间隙,而是检测孤岛(连续停机时间)。如果上一个停机时间的 DateClosed 与当前停机时间的 DateOpen 重叠,则某些东西是连续的,用 0 表示。如果不重叠,则它是一个间隙,用 1 表示。
这是查询:
现场测试:http://sqlfiddle.com/#!18/462ac/12
with gap_detector as
(
select
DateOpen, DateClosed,
case when lag(DateClosed) over (order by DateOpen) >= DateOpen
then
0
else
1
end as gap
from dt
)
, downtime_grouper as
(
select
DateOpen, DateClosed,
sum(gap) over (order by DateOpen) as downtime_group
from gap_detector
)
-- group's open and closed detector. then computes the group's downtime
select
downtime_group,
min(DateOpen) as group_date_open,
max(DateClosed) as group_date_closed,
datediff(minute, min(DateOpen), max(DateClosed)) as group_downtime,
sum(datediff(minute, min(DateOpen), max(DateClosed)))
over(order by downtime_group) as downtime_running_total
from downtime_grouper
group by downtime_group
如果您使用的是 SQL Server 2012 或更高版本:
iif(lag(DateClosed) over (order by DateOpen) >= DateOpen, 0, 1) as gap