Sql 计算日期/时间范围内数据集之外的总时间

Sql 计算日期/时间范围内数据集之外的总时间,sql,sql-server,sql-server-2005,Sql,Sql Server,Sql Server 2005,我希望向现有的应用程序添加一些新功能(数据库是MicrosoftSQL2005)。基本上,我试图计算在一组特定的日期范围内,一个特定的部门“无人值守”的时间是多少分钟(或秒)。我希望用一条语句查询数据集。我有一个循环记录集的例程,解析它并给出一个答案,但它非常难看。有人对我如何使用纯SQL优化它的可读性有什么建议吗?甚至还有人对我应该看的东西有什么建议/文章吗?我的Googlefu让我失望了 我想在某些方面,这几乎就像是一个“空闲时间”的日历搜索,但聚合 下面是一个模拟示例数据集,让您了解我正在

我希望向现有的应用程序添加一些新功能(数据库是MicrosoftSQL2005)。基本上,我试图计算在一组特定的日期范围内,一个特定的部门“无人值守”的时间是多少分钟(或秒)。我希望用一条语句查询数据集。我有一个循环记录集的例程,解析它并给出一个答案,但它非常难看。有人对我如何使用纯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               | ...              | ...              |
------------------------------------------------------------------------

因此,例如,如果我在2010年1月4日08:30:00到2010年1月4日17:30:00之间查询部门Id=1的上表,我预计“无人值守时间”的结果为35分钟(或2100秒)(这是无人值守范围开始、中间和结束时的时间之和).

这是一个范围交叉问题:您看到的是一个数字范围:

4/01/2010 08:30:00 - 04/01/2010 17:30:00  
此范围可以表示为数字-微秒或从一天开始的秒数,例如:

[1000000, 3000000]
您希望找到不与以下任何部分碰撞的部分:

[1200000, 1250000]
[1250000, 1490000]
[1500000, 1950000]
...
当转换为数字格式时,它几乎可以用任何语言实现

编辑:


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

我已经创建了一个整数表,我用它来处理类似的事情

鉴于此,您需要:

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'
drop table foo
去
创建表foo(
c_id int不为空,
d_id int不为空,
datein datetime不为空,
dateout datetime不为空
)
去
插入到foo值中(1,1,'04/01/2010 08:45','04/01/2010 11:45')
插入到foo值中(2,1,'04/01/2010 09:00,'04/01/2010 12:15')
插入到foo值中(3,1,'04/01/2010 10:00,'04/01/2010 12:00')
插入到foo值中(4,1,'04/01/2010 12:30,'04/01/2010 17:00')
插入到foo值中(1,1,'04/01/2010 12:45','04/01/2010 17:15')
插入到foo值中(3,1,'04/01/2010 13:00,'04/01/2010 17:25')
去
无人驾驶降落程序
去
创建无人操作的过程
@d_id int,
@开始日期时间,
@结束日期时间
作为
选择不同的日期添加(ss,i_int,@start)
从整数
在dateadd(ss,i_int,@start)>=datein和dateadd(ss,i_int,@start)=@start和dateadd(ss,i_int,@start)<@end
datein为空
按1订购
去
行政长官:1,'4/1/10 8:30','4/1/10 17:30'

我建议使用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
声明@Start DateTime、@End DateTime
选择@Start='04/01/2010 09:30'
,@End='04/01/2010 17:30'
--表格创建工具
声明@y表(collagueid Int、DepartmentId Int、DateIn-DateTime、DateOut-DateTime)
插入@y
选择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'
---------
选择DateDiff(分钟、@Start、@End)--总时间
-总和(日期差)(分钟),
当DateIn<@Start然后@Start Else DateIn End时的情况,
DateOut>@End然后@End Else DateOut End)时的情况--StaffedTime
作为非计划时间
从…起
(
选择Min(din)DateIn,dout DateOut
从…起
(
选择最小(y.日期输入)din,最大(y.日期输出)dout
来自@y
内部联接@y y2 on y.DateOut>=y2.DateIn
--您可能希望关闭这些过滤器的另一端,但留下一些空间
--(处理7:45开始的人,等等)
其中y.DateIn<@End
和y2.DateOut>@Start
y.DateIn分组
)x
一组一组
)q

编辑添加了上述案例陈述,以处理特定时段在@Start之前开始(或在@End之后结束)时的员工时间计算。

感谢Adam链接这些项目以供进一步阅读,非常有趣!感谢AlexCuse,我采用了Eric H的方法,但是您的方法提供了一个有趣的实现。很高兴您能够使用数字表方法,我经常惊讶地听到人们不能使用(因为他们正在使用供应商数据库,或者他们害怕要求DBA添加数据库;))根据您使用存储过程的频率,您始终可以使用笛卡尔连接动态创建数字表……事实上,我还没有看到一种快速生成大量数字表的方法(在某个地方有一个递归CTE,但我目前无法通过搜索找到)。你根本不需要一个很大的,但你永远不知道。与动态数字表方法相比,看看这个查询是如何执行的会很有趣。