sql – 多个日期范围之间的分钟总和

该方案是用户指定何时可用,这些指定时间可以相互重叠.我正在努力获得他们可用的总时间. sql小提琴示例:
--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'

输出应为130分钟:

1: 60
2: 0 as falls inside 1
3: 30
4: 30 as the last 10 mins is covered by 3
5: 10 as first 60 mins is covered by 1
6: 0 as falls inside 1

我可以获得总重叠分钟数,但这超过了可用分钟数的总和:

SQL Fiddle

我有什么想法可以达到这个目的吗?

编辑11月21日:感谢所有人的解决方案 – 在某种程度上,我很高兴看到这不是一个’简单’的查询.

编辑11月23日12:这都是伟大的工作.在内部,我们认为最好确保用户不能输入重叠时间(例如强制他们修改现有条目)!

解决方法

Gordon Linoff一个 CTE based answer

我在所有工作算法上做了一些performance analysis
空白值意味着花了太长时间.这是在单个Core i7 X920 @ 2GHz芯片上测试的,由几个SSD支持.创建的唯一索引是UserID上的集群,AvailStart.如果您认为可以改善任何表现,请告诉我.

这个CTE版本比线性更差,sql Server无法以有效的方式做RN = RN 1加入.我使用下面的混合方法对此进行了纠正,我将第一个CTE保存并索引到表变量中.这仍然是基于光标的方法的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,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,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,AvailEnd
  From
    dbo.Available
  Where
    UserID = @UserID
)
Insert Into @UserRanges Select * From OrderedRanges


;With AccumulateMinutes (RN,AvailEnd
  From
    @UserRanges
  Where 
    RN = 1
  Union All
  Select
    o.RN,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,CurEnd)) 
From 
  AccumulateMinutes 
Option
  (MaxRecursion 0)

Return @Ret

End

http://sqlfiddle.com/#!6/bfd94

相关文章

SELECT a.*,b.dp_name,c.pa_name,fm_name=(CASE WHEN a.fm_n...
if not exists(select name from syscolumns where name=&am...
select a.*,pano=a.pa_no,b.pa_name,f.dp_name,e.fw_state_n...
要在 SQL Server 2019 中设置定时自动重启,可以使用 Window...
您收到的错误消息表明数据库 &#39;EastRiver&#39; 的...
首先我需要查询出需要使用SQL Server Profiler跟踪的数据库标...