我遇到了一个更大问题的障碍。
作为大型查询的一部分,我需要解决“守夜人”问题。 我有一张表,其中包含日程安排的变化:
ID | Start | End
1 | 2009-1-1 06:00 | 2009-1-1 14:00
2 | 2009-1-1 10:00 | 2009-1-1 18:00
3 | 2009-2-1 20:00 | 2009-2-2 04:00
4 | 2009-2-2 06:00 | 2009-2-2 14:00
作为查询的一部分,我需要确定在给定时间范围内房间内是否至少有一名守望者。
因此,如果我将范围2009-1-1 06:00
指定为2009-1-1 12:00
,则结果为真,因为第1和第2轮合并以涵盖此时间段 - 实际上可以链接任意数量的轮班以保持小心。但是,如果我将2009-2-1 22:00
检查为2009-1-2 10:00
,则结果为false,因为第二天早上4点到6点之间有休息。
我想在LINQ中实现 ,或者在SQL Server(2005)中实现用户定义的函数,因为在这两种情况下,这只是更大查询逻辑的一部分。必须运行以识别需要注意的元素。真实数据集涉及与任何给定时间段相交的大约一百个移位记录,但并不总是覆盖整个范围。
我发现的最接近的是 How to group ranged values using SQL Server 对于数字范围,但它取决于在下一个范围开始之前结束的每个范围。如果我可以构建相同的手表统一视图,只考虑重叠的手表,那么检查是否涵盖了特定时间将是微不足道的。统一视图如下所示:
Start | End
2009-1-1 06:00 | 2009-1-1 18:00
2009-2-1 20:00 | 2009-2-2 04:00
2009-2-2 06:00 | 2009-2-2 14:00
注意:通过拉动所有数据并在其上运行一些手动循环来实现整个过程相对容易,但这是当前的系统,并且由于轮班次数和时间而相当慢必须检查的范围。
答案 0 :(得分:2)
这是一种像这样平整日期范围的方法
Start | End
2009-1-1 06:00 | 2009-1-1 18:00
2009-2-1 20:00 | 2009-2-2 04:00
2009-2-2 06:00 | 2009-2-2 14:00
您必须比较每行中的之前的 和 日期,看看是否
使用上面的代码,实现UDF非常简单。
create function fnThereIsWatchmenBetween(@from datetime, @to datetime)
returns bit
as
begin
declare @_Result bit
declare @FlattenedDateRange table (
Start datetime,
[End] datetime
)
insert @FlattenedDateRange(Start, [End])
select distinct
Start =
case
when Pv.Start is null then Curr.Start
when Curr.Start between Pv.Start and Pv.[End] then Pv.Start
else Curr.Start
end,
[End] =
case
when Curr.[End] between Nx.Start and Nx.[End] then Nx.[End]
else Curr.[End]
end
from shift Curr
left join shift Pv on Pv.ID = Curr.ID - 1 --; prev
left join shift Nx on Nx.ID = Curr.ID + 1 --; next
if exists( select 1
from FlattenedDateRange R
where @from between R.Start and R.[End]
and @to between R.Start and R.[End]) begin
set @_Result = 1 --; There is/are watchman/men during specified date range
end
else begin
set @_Result = 0 --; There is NO watchman
end
return @_Result
end
答案 1 :(得分:1)
无人看守的间隔显然是在观察期结束时或您正在检查的整个时间范围的开始处开始的。因此,您需要一个查询来选择此集合中没有重叠移位的所有元素。查询看起来像:
select 1
from shifts s1 where not exists
(select 1 from shifts s2
where s2.start<=s1.end and s2.end > s1.end
)
and s1.end>=start_of_range and s1.end< end_of_range
union
select 1
where not exists
(select 1 from shifts s2
where s2.start<=start_of_range and s2.end > start_of_range
)
如果这是非空的,那么你有一个无人看守的间隔。我怀疑它会以二次方式运行,所以它可能比“sort,fetch and loop”慢。
答案 2 :(得分:0)
一种方法是创建一个临时表,每个时间值需要检查一行(这是你的班次分辨率的函数)。
如果是几分钟,一天就会有60 * 24 = 1440行;一周约10K行。
然后SQL相对简单:
SELECT COUNT(1)
来自#minutes m
LEFT JOIN在s.start_time和s.end_time之间移动m.checktime 有COUNT(1)= 0
这样做的好处是能够显示同一时间有多少班次。
根据您描述的尺度,执行时间应该可以忽略不计。
答案 3 :(得分:0)
我正在查看日期范围,并认为我会重新访问这个问题。我可能会在这里黯然失色,但似乎这两个条件就够了
(1) Shift is not at beginning of range and has no left neighbour
OR
(2) Shift is not at end of range and has no right neighbour.
欣赏这可能不是最有效的。
CREATE TABLE times
(
TimeID int,
StartTime Time,
EndTime Time
)
INSERT INTO times
VALUES
(1,'10:00:00','11:00:00'),
(2,'11:00:00','12:00:00'),
(3,'13:00:00','14:00:00'),
(4,'14:30:00','15:00:00'),
(5,'15:00:00','16:00:00'),
(6,'16:00:00','17:00:00')
declare @start_of_range time ='09:30:00'
declare @end_of_range time = '17:30:00'
select timeID,StartTime,EndTime
from times s1 where
-- No left neighbour and not at beginning of range
not exists
(select 1 from times s2
where s2.startTime < s1.startTime and s2.endTime >= s1.startTime
)
and s1.StartTime>@start_of_range
or
-- No right neighbour and not at end of range
not exists
(select 1 from times s2
where s2.startTime <= s1.endTime and s2.endTime > s1.endTime
)
and s1.EndTime<@end_of_range
结果集
timeID StartTime EndTime
1 10:00:00.0000000 11:00:00.0000000
2 11:00:00.0000000 12:00:00.0000000
3 13:00:00.0000000 14:00:00.0000000
4 14:30:00.0000000 15:00:00.0000000
6 16:00:00.0000000 17:00:00.0000000
实际上只需检查右邻居或左邻居,只要确保检查范围的开始和结束,这样就可以将范围的开始作为虚拟区间引入并按如下方式检查正确的邻居: -
select * from
(
select timeID,StartTime,EndTime
from times union select 0,@start_of_range,@start_of_range) s1
where
not exists
(select 1 from times s2
where s2.startTime<=s1.endTime and s2.endTime > s1.endTime
)
and s1.EndTime<@end_of_range
结果集
timeID StartTime EndTime
0 09:30:00.0000000 09:30:00.0000000
2 11:00:00.0000000 12:00:00.0000000
3 13:00:00.0000000 14:00:00.0000000
6 16:00:00.0000000 17:00:00.0000000