Checking For Time Range Overlap, The Watchman Problem [sql]
Solution 1:
Here is a way to flatten date range like this
Start|End2009-1-106:00|2009-1-118:002009-2-120:00|2009-2-204:002009-2-206:00|2009-2-214:00You have to compare previousandnext dates in each row and see whether
- Current row's Start date falls between previous row's date range.
- Current row's End date falls between next row's date range.

Using above code, implementing UDF is as simple as followed.
createfunction fnThereIsWatchmenBetween(@from datetime, @to datetime)
returns bit
asbegindeclare @_Result bit
declare@FlattenedDateRangetable (
Start datetime,
[End] datetime
)
insert@FlattenedDateRange(Start, [End])
selectdistinctStart=casewhen Pv.Start isnullthen Curr.Start
when Curr.Start between Pv.Start and Pv.[End] then Pv.Start
else Curr.Start
end,
[End] =casewhen Curr.[End] between Nx.Start and Nx.[End] then Nx.[End]
else Curr.[End]
endfrom shift Curr
leftjoin shift Pv on Pv.ID = Curr.ID -1--; prevleftjoin shift Nx on Nx.ID = Curr.ID +1--; next
if exists( select1from FlattenedDateRange R
where@frombetween R.Start and R.[End]
and@tobetween R.Start and R.[End]) beginset @_Result =1--; There is/are watchman/men during specified date rangeendelsebeginset @_Result =0--; There is NO watchmanendreturn @_Result
endSolution 2:
An unguarded interval obviously starts either at the end of a watched period or at the beginning of the whole time range that you are checking. So you need a query that selects all elements from this set that don't have an overlapping shift. The query would look like:
select1from shifts s1 wherenot exists
(select1from shifts s2
where s2.start<=s1.endand s2.end > s1.end
)
and s1.end>=start_of_range and s1.end< end_of_range
union
select1wherenot exists
(select1from shifts s2
where s2.start<=start_of_range and s2.end > start_of_range
)
If this is non-empty, then you have an unguarded interval. I suspect it will run in quadratic time, so it might be slower than "sort, fetch and loop".
Solution 3:
One way is to create a temp table with a row for each time value requiring to be checked (which is a function of the resolution of your shifts).
If it were minutes it would have 60 * 24 = 1440 rows for a day; about 10K rows for a week.
Then the SQL is relatively simple:
SELECT COUNT(1) FROM #minutes m LEFT JOIN shifts s ON m.checktime BETWEEN s.start_time AND s.end_time HAVING COUNT(1) = 0
This has the benefit of also being able to show how many shifts are covering the same time.
The execution time should be negligible given the scales you've described.
Solution 4:
I was looking at date ranges and thought I would re-visit this question. I may fall flat on my face here, but it seems these two conditions would be enough
(1) Shift isnotat beginning ofrangeand has noleft neighbour
OR
(2) Shift isnotatendofrangeand has noright neighbour.
Appreciate this may not be the most efficient.
CREATETABLE times
(
TimeID int,
StartTime Time,
EndTime Time
)
INSERTINTO 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 rangenotexists
(select1from 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 rangenotexists
(select1from times s2
where s2.startTime <= s1.endTime and s2.endTime > s1.endTime
)
and s1.EndTime<@end_of_range
Result set
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
Actually it's only necessary to check either the right neighbours or the left neighbours, as long as you make sure that the start and end of range is checked, so you could introduce the start of range as a dummy interval and just check the right neighbours as follows:-
select*from
(
select timeID,StartTime,EndTime
from times unionselect0,@start_of_range,@start_of_range) s1
wherenotexists
(select1from times s2
where s2.startTime<=s1.endTime and s2.endTime > s1.endTime
)
and s1.EndTime<@end_of_range
Result set
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
Post a Comment for "Checking For Time Range Overlap, The Watchman Problem [sql]"