gpt4 book ai didi

sql - 查找忽略重叠的总分钟数(将基于光标的答案转换为 CTE)

转载 作者:行者123 更新时间:2023-12-04 16:17:47 25 4
gpt4 key购买 nike

有一个 existing question 询问如何找到多个日期范围内有多少分钟,忽略重叠。

给出的示例数据是(userID 不是特别相关)

--Available--
ID userID availStart availEnd
1 456 '2012-11-19 16:00' '2012-11-19 17:00'
2 456 '2012-11-19 16:00' '2012-11-19 16:50'
3 456 '2012-11-19 18:00' '2012-11-19 18:30'
4 456 '2012-11-19 17:30' '2012-11-19 18:10'
5 456 '2012-11-19 16:00' '2012-11-19 17:10'
6 456 '2012-11-19 16:00' '2012-11-19 16:50'

我可以使用游标解决问题,但我认为它应该适用于 CTE,但我不知道该怎么做。

方法是按开始时间排列每个范围然后我们构建一个按顺序合并范围的范围,直到找到一个不与我们合并的范围重叠的范围。然后我们计算合并范围内有多少分钟,并记住这一点。我们继续下一个范围,再次合并任何重叠的范围。每当我们得到一个不重叠的起点时,我们就累积分钟数。最后,我们将累积的分钟数添加到最后一个范围的长度上

很容易看出,由于顺序的原因,一旦一个范围与之前的范围不同,那么没有其他范围可以与之前的范围重叠,因为它们的开始日期都更大。

Declare
@UserID int = 456,
@CurStart datetime, -- our current coalesced range start
@CurEnd datetime, -- our current coalesced range end
@AvailStart datetime, -- start or range for our next row of data
@AvailEnd datetime, -- end of range for our next row of data
@AccumMinutes int = 0 -- how many minutes so far accumulated by distinct ranges

Declare MinCursor Cursor Fast_Forward For
Select
AvailStart, AvailEnd
From
dbo.Available
Where
UserID = @UserID
Order By
AvailStart

Open MinCursor

Fetch Next From MinCursor Into @AvailStart, @AvailEnd
Set @CurStart = @AvailStart
Set @CurEnd = @AvailEnd

While @@Fetch_Status = 0
Begin
If @AvailStart <= @CurEnd -- Ranges Overlap, so coalesce and continue
Begin
If @AvailEnd > @CurEnd
Set @CurEnd = @AvailEnd
End
Else -- Distinct range, coalesce minutes from previous range
Begin
Set @AccumMinutes = @AccumMinutes + DateDiff(Minute, @CurStart, @CurEnd)
Set @CurStart = @AvailStart -- Start coalescing a new range
Set @CurEnd = @AvailEnd
End
Fetch Next From MinCursor Into @AvailStart, @AvailEnd
End

Select @AccumMinutes + DateDiff(Minute, @CurStart, @CurEnd) As TotalMinutes

Close MinCursor
Deallocate MinCursor;

让 CTE 工作,只是递归中的一个愚蠢错误。查询计划爆炸令人印象深刻:

With OrderedRanges as (
Select
Row_Number() Over (Partition By UserID Order By AvailStart) AS RN,
AvailStart,
AvailEnd
From
dbo.Available
Where
UserID = 456
),
AccumulateMinutes (RN, Accum, CurStart, CurEnd) as (
Select
RN, 0, AvailStart, AvailEnd
From
OrderedRanges
Where
RN = 1
Union All
Select
o.RN,
a.Accum + Case When o.AvailStart <= a.CurEnd Then
0
Else
DateDiff(Minute, a.CurStart, a.CurEnd)
End,
Case When o.AvailStart <= a.CurEnd Then
a.CurStart
Else
o.AvailStart
End,
Case When o.AvailStart <= a.CurEnd Then
Case When a.CurEnd > o.AvailEnd Then a.CurEnd Else o.AvailEnd End
Else
o.AvailEnd
End
From
AccumulateMinutes a
Inner Join
OrderedRanges o On
a.RN = o.RN - 1
)

Select Max(Accum + datediff(Minute, CurStart, CurEnd)) From AccumulateMinutes

这是否适用于 CTE,是否存在以这种方式在列表上累积的通用模式?

http://sqlfiddle.com/#!6/ac021/2

最佳答案

以下查询根据您的定义查找数据中的周期。它首先使用相关子查询来确定一条记录是否是一个时间段的开始(即,与更早的时间段没有重叠)。然后它将“periodStart”指定为最近的开始,即非重叠周期的开始。

以下(未经测试的)查询采用这种方法:

with TimeWithOverlap as (
select t.*,
(case when exists (select * from dbo.Available tbefore where t.availStart > tbefore.availStart and tbefore.availEnd >= t.availStart)
then 0
else 1
end) as IsPeriodStart
from dbo.Available t
),
TimeWithPeriodStart as (
select two.*,
(select MAX(two1.AvailStart) from TimeWithOverlap two1 where IsPeriodStart = 1 and two1.AvailStart <= two.AvailStart
) as periodStart
from TimeWithOverlap two
)
select periodStart, MAX(AvailEnd) as periodEnd
from TimeWithPeriodStart twps
group by periodStart;

http://sqlfiddle.com/#!6/3483c/20 (第二次查询)

如果两个时段同时开始,那么它仍然有效,因为 AvailStart 值相同。由于相关的子查询,即使在中等规模的数据集上,这也可能不会很好地执行。

还有其他方法可以解决这个问题。例如,如果您有 SQL Server 2012,您将能够使用累积求和函数,它提供了一种更简单的方法。

关于sql - 查找忽略重叠的总分钟数(将基于光标的答案转换为 CTE),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/13482921/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com