T-SQL DateTime问题。
我有一组时间范围。在这些时间范围内,可能存在一组重叠的时间范围,我将其称为“阻止”时间。封锁时间不会超过一天。我想要做的是分开时间以排除被阻止的时间,基本上给我时间范围不被“阻止”。可以安全地假设阻塞时间不能超出时间范围。
示例:我上午9点到下午5点工作,下午1点午休30分钟。我想要2行的结果:上午9点到下午1点和下午1点30分到下午5点。
如上所述,我有一组时间范围,所以在上面的例子中,工作时间可能每天都有所不同,休息次数和持续时间可能不同。
我想在SQL方面,输入参数如下所示:
declare @timeranges table ( StartDateTime datetime, EndDateTime datetime )
declare @blockedtimes table ( StartDateTime datetime, EndDateTime datetime )
insert into @timeranges
select '01 Jan 2009 09:00:00', '01 Jan 2009 17:00:00'
union select '02 Feb 2009 10:00:00', '02 Feb 2009 13:00:00'
insert into @blockedtimes
select '01 Jan 2009 13:00:00', '01 Jan 2009 13:30:00'
union select '02 Feb 2009 10:30:00', '02 Feb 2009 11:00:00'
union select '02 Feb 2009 12:00:00', '02 Feb 2009 12:30:00'
结果集看起来像这样。
Start End
--------------------- ---------------------
'01 Jan 2009 09:00:00' '01 Jan 2009 13:00:00'
'01 Jan 2009 13:30:00' '01 Jan 2009 17:00:00'
'02 Feb 2009 10:00:00' '02 Feb 2009 10:30:00'
'02 Feb 2009 11:00:00' '02 Feb 2009 12:00:00'
'02 Feb 2009 12:30:00' '02 Feb 2009 13:00:00'
我可以使用游标或while循环执行此操作,但如果有人可以建议如何在没有迭代的情况下执行此操作,那将非常棒 - 谢谢。
答案 0 :(得分:2)
首先,可能有一些问题,但我会继续努力。
适用于给定数据,只需要尝试其他方案
declare @timeranges table ( StartDateTime datetime, EndDateTime datetime )
declare @blockedtimes table ( StartDateTime datetime, EndDateTime datetime )
insert into @timeranges
select '01 Jan 2009 09:00:00', '01 Jan 2009 17:00:00'
union select '02 Feb 2009 10:00:00', '02 Feb 2009 13:00:00'
--union select '03 Feb 2009 10:00:00', '03 Feb 2009 15:00:00'
insert into @blockedtimes
select '01 Jan 2009 13:00:00', '01 Jan 2009 13:30:00'
union select '02 Feb 2009 10:30:00', '02 Feb 2009 11:00:00'
union select '02 Feb 2009 12:00:00', '02 Feb 2009 12:30:00'
--build an ordered, time range table with an indicator
--to determine which ranges are timeranges 'tr'
--and which are blockedtimes 'bt'
--
declare @alltimes table (row int, rangetype varchar(10), StartDateTime datetime, EndDateTime datetime )
insert into @alltimes
select
row_number() over (order by a.startdatetime), *
from
(
select 'tr' as rangetype ,startdatetime, enddatetime from @timeranges
union
select 'bt' as rangetype ,startdatetime, enddatetime from @blockedtimes
)a
--what does the data look like
--
select * from @alltimes
--
-- build up the results
select
--start time is either the start time of a timerange, or the end of a blockedtime
case
when at1.rangetype = 'tr' then at1.startdatetime
when at1.rangetype = 'bt' then at1.enddatetime
end as [Start],
case
--a time range followed by another time range : end time from the current time range
when at1.rangetype = 'tr' and (select at2.rangetype from @alltimes at2 where at2.row = at1.row+1) = 'tr'
then at1.enddatetime
--a time range followed by nothing (last record) : end time from the currenttime range
when at1.rangetype = 'tr' and (select at2.rangetype from @alltimes at2 where at2.row = at1.row+1) is null
then at1.enddatetime
--a time range followed by a blockedtime : end time is start time of blocked time
when at1.rangetype = 'tr' and (select at2.rangetype from @alltimes at2 where at2.row = at1.row+1) = 'bt'
then (select top 1 at2.startdatetime from @alltimes at2 where at2.row > at1.row and at2.rangetype = 'bt' order by row)
--a blocked time followed by a blockedtime : end time is start time of next blocked time
when at1.rangetype = 'bt' and (select at2.rangetype from @alltimes at2 where at2.row = at1.row+1) = 'bt'
then (select top 1 at2.startdatetime from @alltimes at2 where at2.row > at1.row and at2.rangetype = 'bt' order by row)
--a blocked time followed by a time range : end time is end time of previous time range
when at1.rangetype = 'bt' and (select at2.rangetype from @alltimes at2 where at2.row = at1.row+1) = 'tr'
then (select top 1 at2.enddatetime from @alltimes at2 where at2.row < at1.row and at2.rangetype = 'tr' order by row desc)
--a blocked time followed by nothing (last record) : end time is end time of previous time range
when at1.rangetype = 'bt' and (select at2.rangetype from @alltimes at2 where at2.row = at1.row+1) is null
then (select top 1 at2.enddatetime from @alltimes at2 where at2.row < at1.row and at2.rangetype = 'tr' order by row desc)
end as [End]
from @alltimes at1
答案 1 :(得分:1)
我以为我会分享我最终解决的解决方案:
对临时表进行轻微调整,因为我已经为@timeranges和@blockedtimes添加了一个StartDate字段
declare @timeranges table ( StartDate datetime, StartDateTime datetime, EndDateTime datetime )
declare @blockedtimes table ( StartDate datetime, StartDateTime datetime, EndDateTime datetime )
无论如何看起来比其他一些答案更简单 - 为每个人的欢呼提供帮助:)
select
*
from
(
-- first SELECT get start boundry
select t.StartDateTime s, b.StartDateTime e
from @timeranges t, @blockedtimes b
where
-- same day and blocks overlaps timerange
t.StartDate = b.StartDate and (t.StartDateTime <= b.EndDateTime and b.StartDateTime <= t.EndDateTime)
and
-- the following is the important bit for this SELECT
not exists (select 1 from @blockedtimes b2 where b2.StartDate = b.StartDate and b2.StartDateTime < b.StartDateTime)
union
-- second SELECT get spikes ie middle
select b1.EndDateTime s, b2.StartDateTime e
from @timeranges t, @blockedtimes b1, @blockedtimes b2
where
-- same day and blocks overlaps timerange
t.StartDate = b1.StartDate and (t.StartDateTime <= b1.EndDateTime and b1.StartDateTime <= t.EndDateTime)
and
-- same day and blocks overlaps timerange
t.StartDate = b2.StartDate and (t.StartDateTime <= b2.EndDateTime and b2.StartDateTime <= t.EndDateTime)
and
-- the following is the important bit for this SELECT
b1.EndDateTime < b2.StartDateTime
union
-- third SELECT get end boundry
select b.EndDateTime s, t.EndDateTime e
from @timeranges t, @blockedtimes b
where
-- same day and blocks overlaps timerange
t.StartDate = b.StartDate and (t.StartDateTime <= b.EndDateTime and b.StartDateTime <= t.EndDateTime)
and
-- the following is the important bit for this SELECT
not exists (select 1 from @blockedtimes b2 where b2.StartDate = b.StartDate and b2.StartDateTime > b.StartDateTime)
) t1
答案 2 :(得分:0)
如果输入数据有两个条件,则此解决方案应该有效:A)每个阻塞时间间隔都在一个时间范围内。 (你说这可以假设。)和B)被阻止的时间间隔不重叠 - 也就是说,没有时间被一个以上的阻塞间隔“双重阻挡”。
with TB(src,S,E) as (
select 'T', StartDateTime, EndDateTime from @timeranges as T
union all
select 'B', StartDateTime, EndDateTime from @blockedtimes as B
), TBP(evt,switch,DT,rk) AS (
select
src+DT,
CHARINDEX(src+DT,'TEBSTSBE')/5 AS OffOn,
EventDT,
row_number() over (
order by EventDT, CHARINDEX(src+DT,'TEBSTSBE')/5 desc
) as rk
from TB UNPIVOT (
EventDT FOR DT in ([S],[E])
) as U
)
select
min(DT) as StartDateTime,
max(DT) as EndDateTime
from TBP
group by (rk-1)/2
having min(DT) < max(DT)
order by (rk-1)/2;
这是如何运作的?
它首先用0或1标记所有日期时间值,以指示可用性是否结束(0,对于@timeranges中的EndDateTime值和@blockedtimes中的StartDateTime值)或者开始(对于其他两种可能性为1)特定的时间。然后时间和标签按时间顺序排列,标记并用使用row_number函数的列rk编号。使用CASE表达式可以使标记更具可读性,但是键入的CHARINDEX更少...
由于这些假设,标签序列将在0和1:0,1,0,1,0,1 ...之间交替,每个连续的(0,1)对指示间隔的开始和结束可用性。这些间隔可以用(rk-1)/ 2编号。
行在每个可用性间隔上进行分组。组中的最小日期时间是开始时间,最大值是结束时间,如果这些不同,则组表示属于结果集的非空间隔。请注意,对于您的数据,没有空的间隔,但是如果两个阻塞时间相邻或者阻塞时间与时间范围同时结束,则会存在。
最后,结果以您想要的显示格式进行旋转。
这不是最容易阅读的代码,但它可能值得迷惑。像这样使用row_number和分组的解决方案有时可以解决棘手的问题。
答案 3 :(得分:0)
SELECT COALESCE(bt.StartDateTime, tr.StartDateTime),
bt.EndDateTime
FROM @timeranges tr
CROSS APPLY
(
SELECT bp.StartDateTime, bt.StartDateTime AS EndDateTime
FROM (
SELECT StartDateTime
FROM @blockedtimes bt
WHERE bt.EndDateTime >= tr.StartDateTime
AND bt.StartDateTime <= tr.EndDateTime
UNION ALL
SELECT tr.EndDateTime
) bt
OUTER APPLY
(
SELECT TOP 1 EndDateTime AS StartDateTime
FROM @blockedtimes bti
WHERE bti.EndDateTime >= tr.StartDateTime
AND bti.StartDateTime <= tr.EndDateTime
AND bti.StartDateTime < bt.StartDateTime
ORDER BY
bti.StartDateTime DESC
) AS bp
) bt
此解决方案依赖于以下假设: