【问题标题】:Calculating total time that falls outside of dataset within date/time range计算日期/时间范围内超出数据集的总时间
【发布时间】:2011-01-02 01:52:10
【问题描述】:

我希望向现有应用程序(数据库是 Microsoft SQL 2005)添加一些新功能。基本上,我正在尝试计算特定部门在特定日期范围内“无人值守”的分钟数(或秒数)。我希望用一条语句理想地查询数据集。我有一个循环遍历记录集,解析它并吐出答案的例程,但它非常难看。有没有人对我如何优化它的可读性有任何建议,使用纯 SQL - 甚至任何关于我应该看什么的指针/文章,我的 Googlefu 都让我失望了。

我想在某些方面这几乎就像是对日历的“空闲时间”搜索,但是是聚合的。

这是一个模拟示例数据集,可让您了解我正在处理的工作(实际上是同事打卡,然后打卡)。为了简单起见,我在下面使用四舍五入到分钟,但我可能会以秒为单位进行计算。

------------------------------------------------------------------------
| Colleague Id | Department Id   | Date In          | Date Out         |
------------------------------------------------------------------------
| 1            | 1               | 04/01/2010 08:45 | 04/01/2010 11:45 |
| 2            | 1               | 04/01/2010 09:00 | 04/01/2010 12:15 |
| 3            | 1               | 04/01/2010 10:00 | 04/01/2010 12:00 |
| 4            | 1               | 04/01/2010 12:30 | 04/01/2010 17:00 |
| 1            | 1               | 04/01/2010 12:45 | 04/01/2010 17:15 |
| 3            | 1               | 04/01/2010 13:00 | 04/01/2010 17:25 |
| 5            | 2               | ...              | ...              |
------------------------------------------------------------------------

例如,如果我在 04/01/2010 08:30:00 和 04/01/2010 17:30 之间查询上表的 Department Id = 1: 00,我预计 35 分钟(或 2100 秒) 的“无人值守时间”(这是该范围的开始、中间和结束的时间总和)无人)。

【问题讨论】:

    标签: sql sql-server sql-server-2005


    【解决方案1】:

    我建议使用 Eric H 的方法。有了这个免责声明,这有点令人讨厌,但它确实提供了一种做同样事情的方法,如果您出于某种原因无法访问数字表。我确信它可以改进,我只是想尝试一下不使用数字表:

    Declare @Start DateTime, @End DateTime
    
    Select @Start = '04/01/2010 09:30'
        , @End = '04/01/2010 17:30'
    
    --Table Creation Stuff
    Declare @y Table (ColleagueId Int, DepartmentId Int, DateIn DateTime, DateOut DateTime)
    
    Insert @y
    Select 1, 1, '04/01/2010 08:45' , '04/01/2010 11:45'
    Union All Select 2 , 1, '04/01/2010 09:00' , '04/01/2010 12:15'
    Union All Select 3 , 1, '04/01/2010 10:00' , '04/01/2010 12:00'
    Union All Select 4 , 1, '04/01/2010 12:30' , '04/01/2010 17:00' 
    Union All Select 1 , 1, '04/01/2010 12:45' , '04/01/2010 17:15' 
    Union All Select 3 , 1, '04/01/2010 13:00' , '04/01/2010 17:25'
    ---------
    
    Select DateDiff(minute, @Start, @End)  -- TotalTime
         - Sum(DateDiff(minute, 
            Case When DateIn < @Start Then @Start Else DateIn End, 
            Case When DateOut > @End Then @End Else DateOut End)) --StaffedTime
         as UnmannedTime
    From
    (
        Select Min(din) DateIn, dout DateOut
        From
        (
            Select Min(y.DateIn) din, Max(y2.DateOut) dout
            From @y y
            Inner Join @y y2 on y.DateOut >= y2.DateIn
            --you probably want to close the other end of these filters, but leave some room
            --(to handle the guy who started @ 7:45, etc...)
            Where y.DateIn < @End 
                and y2.DateOut > @Start             
            Group By y.DateIn
        ) x 
        Group By dout
    ) q
    

    edit添加了上面的case语句来处理当特定时间段在@Start之前开始(或在@End之后结束)时计算StaffedTime

    【讨论】:

    • 感谢 AlexCuse,我采用了 Eric H 的方法,但是你的方法提供了一个有趣的实现。
    • 很高兴您能够使用数字表方法,听到人们无法做到这一点,我经常感到惊讶(因为他们正在使用供应商数据库,或者他们害怕询问DBA 添加一个 ;) )
    • 根据您使用存储过程的频率,您始终可以使用笛卡尔连接动态创建数字表......
    • 确实,我还没有看到一种真正快速生成大量表格的方法(在某处有递归 CTE,但我不能目前通过搜索找到它)。你根本不需要一个非常大的,但你永远不知道。看看这个查询与动态数字表方法相比如何执行会很有趣。
    【解决方案2】:

    我已经创建了一个 Integers 表,我用它来做这样的事情。

    鉴于此,您想要:

    drop table foo 
    go
    
    create table foo (
       c_id int not null,
       d_id int not null,
       datein datetime not null,
       dateout datetime not null
    )
    go
    
    
    insert into foo values (1, 1, '04/01/2010 08:45', '04/01/2010 11:45')
    insert into foo values (2, 1, '04/01/2010 09:00', '04/01/2010 12:15')
    insert into foo values (3, 1, '04/01/2010 10:00', '04/01/2010 12:00')
    insert into foo values (4, 1, '04/01/2010 12:30', '04/01/2010 17:00')
    insert into foo values (1, 1, '04/01/2010 12:45', '04/01/2010 17:15')
    insert into foo values (3, 1, '04/01/2010 13:00', '04/01/2010 17:25')
    go
    
    
    drop procedure unmanned
    go
    
    create procedure unmanned
       @d_id int,
       @start datetime,
       @end datetime
    
    as
    
    select distinct dateadd(ss,i_int,@start)
     from Integers 
          left join foo on dateadd(ss,i_int,@start) >= datein and dateadd(ss,i_int,@start) < dateout
    
    
    where i_int between 0 and 60*60*24
    and dateadd(ss,i_int,@start) >= @start and dateadd(ss,i_int,@start)< @end
    and datein is null
    order by 1
    
    go
    
    exec unmanned 1, '4/1/10 8:30', '4/1/10 17:30'
    

    【讨论】:

      【解决方案3】:

      这是一个范围相交问题:您正在查看一个数字范围:

      4/01/2010 08:30:00 - 04/01/2010 17:30:00  
      

      此范围可以表示为数字 - 微秒,或从一天开始的秒数,例如:

      [1000000, 3000000]
      

      并且你想找到不与任何碰撞的部分:

      [1200000, 1250000]
      [1250000, 1490000]
      [1500000, 1950000]
      ...
      

      当翻译成数字格式时,it would really look like this range intersection algorithm,它几乎可以用任何语言实现。

      编辑:

      有一个关于日期范围的非常有趣的讨论,带有很好的插图和解释here。

      【讨论】:

      • 感谢 Adam 链接这些项目以供进一步阅读,非常有趣!
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-01-19
      • 1970-01-01
      • 1970-01-01
      • 2017-07-29
      相关资源
      最近更新 更多