ゴードン・リノフはCTEベースの答えを持っています
動作しているすべてのアルゴリズムについてパフォーマンス分析を行いました。空白の値は、時間がかかりすぎることを意味します。これは、2つのSSDに支えられた単一のCore i7 X920@2GHzチップでテストされています。作成された唯一のインデックスは、UserIDのクラスターであるAvailStartでした。パフォーマンスを改善できると思われる場合は、お知らせください。
このCTEバージョンは線形よりも悪く、SQLServerは効率的な方法でRN= RN+1結合を実行できません。以下のハイブリッドアプローチでこれを修正しました。ここでは、最初のCTEを保存してテーブル変数にインデックスを付けます。これには、カーソルベースのアプローチの10倍のIOが必要です。
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
http://sqlfiddle.com/#!6/ac021/2
パフォーマンス分析を行った後、カーソルベースのアプローチ以外の何よりも優れたパフォーマンスを発揮するハイブリッドCTE/テーブル変数バージョンを次に示します。
Create Function dbo.AvailMinutesHybrid(@UserID int) Returns Int As
Begin
Declare @UserRanges Table (
RN int not null primary key,
AvailStart datetime,
AvailEnd datetime
)
Declare @Ret int = Null
;With OrderedRanges as (
Select
Row_Number() Over (Partition By UserID Order By AvailStart) AS RN,
AvailStart,
AvailEnd
From
dbo.Available
Where
UserID = @UserID
)
Insert Into @UserRanges Select * From OrderedRanges
;With AccumulateMinutes (RN,Accum, CurStart, CurEnd) as (
Select
RN, 0, AvailStart, AvailEnd
From
@UserRanges
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
@UserRanges o On
a.RN + 1 = o.RN
)
Select
@Ret = Max(Accum + datediff(Minute, CurStart, CurEnd))
From
AccumulateMinutes
Option
(MaxRecursion 0)
Return @Ret
End
http://sqlfiddle.com/#!6/bfd94