I need to find whether a new schedule overlaps any existing schedules.
This is the "intervals" table:
id first last
1 1900-01-01 09:00 1900-01-01 10:00
2 1900-01-01 15:00 1900-01-01 18:00
3 1900-01-01 18:01 1900-01-01 08:00
I am using a scalar function dbo.TimeOnly for extracting time part from the datetime fields.
My selection criteria as follows
First case
declare #start datetime
declare #end datetime
set #start = dbo.TimeOnly('2011-may-11 08:01:00');
set #end = dbo.TimeOnly('2011-may-11 15:30:00');
select * from intervals where
( NOT ( dbo.TimeOnly(last) < #start OR #end < dbo.TimeOnly(first) ) )
This will return 1st and 2nd records. I got this logic from Check whether the schedule overlaps each other?
Second case
set #start = dbo.TimeOnly('2011-may-11 07:01:00');
set #end = dbo.TimeOnly('2011-may-11 08:30:00');
How do I write a query that will return only the 3rd record for the criteria in the second case?
UPDATE
I will give more details for my problem
Different people are managing a particular event for a certain time duration in a day.
For Monday, the schedule format is like this
Id Start End User Days
1 00:01 AM 08:00 AM 'A' 1
2 08:01 AM 04:00 PM 'B' 1
3 04:00 PM 00:00 AM 'C' 1
For Tuesday's
4 08:01 AM 04:00 PM 'B' 2
5 07:00 PM 07:00 AM 'C' 2
For Wednesday's
6 08:01 AM 04:00 PM 'A' 4
7 10:00 PM 08:00 AM 'B' 4
Here days are stored in the bit value format ie
Monday=1,Tuesday=2,Wednesday=4,Thursday=8,Friday=16,Saturday=32 and Sunday=64
When we creating a schedule for a particular day, it should not overlap between times.
I would like to get a SQL query for checking any schedules exists while creating a new schedule for a particular day.
For a particular event time (Say An even occured at 04:00 AM on Tuesday) I would like to find the correct schedule (Will be "5") that falls between the Start and End time.
Change your SELECT to this:
select * from intervals where
(
( dbo.TimeOnly(last) > dbo.TimeOnly(first)
AND
NOT (dbo.TimeOnly(last) < #start OR #end < dbo.TimeOnly(first)) )
OR
( dbo.TimeOnly(last) < dbo.TimeOnly(first)
AND
( #start >= dbo.TimeOnly(first) OR #end <= dbo.TimeOnly(last) OR (#start < dbo.TimeOnly(first) AND #end > dbo.TimeOnly(last)) ) )
)
I might've missed a parenthesis somewhere, but I hope not.
The concept here is a query with 2 main groupings combined with an OR. The first clause checks intervals where last > first and is mainly a copy of your existing query with the addition of the last > first condition, while the 2nd clause checks intervals where last < first.
In the case where last < first, there are 3 ways that an interval can overlap:
start is after the interval's first
end is before the interval's last
start and end completely engulf the interval, i.e., start is before first and end is after last
Any one of these 3 conditions would mean the schedule to check is within an existing interval, so the 3 conditions are combined with ORs.
You will need to do something like this:
Declare #start datetime
Declare #duration int
Set #start = dbo.TimeOnly('2011-May-11 07:01:00');
-- Get the number of minutes in your timespan
Set #duration = DateDiff(minute, '2011-May-11 07:01:00', '2011-May-12 08:30:00');
Select id, first, last
From intervals
Where (DateDiff(minute, dbo.TimeOnly(first)), #start) + #duration) > 0
AND DateDiff(minute, dbo.TimeOnly(first), #start) <
DateDiff(minute, dbo.TimeOnly(first), dbo.TimeOnly(last));
Assuming that your dbo.TimeOnly function is essentially the same as Convert(time, {timefield}).
This will first find the difference between the new start time and the existing start times, then will find out if the duration of the new period is longer than that difference. This covers new periods that begin before or during existing ones.
The last clause compares the difference between the new start time and the existing start time to the duration of the existing period, to check whether the existing period is longer than the difference between them. Otherwise, the new start time will naturally be after the existing end time, which means it does not overlap.
Related
Example I have data in table which has start date, end date and duration. I want to show hourly time slot.
logic:
Condition 1. If start date =9:00 and end date = 11:00 then show the date as
09:00-10:00
10:00-11:00
It should repeat 2 times and all related column data will also repeat 2 times.
this will continue if time slot is suppose 11:00- 14:00 then
11:00-12:00
12:00-13:00
13:00-14:00
It should repeat 3 times.
Condition 2: If start date is 9:30 and end date is 10:30 then
time should round up. i.e. start date should be 9:00 and end date should be 11:00
How can I achieve this in Sql Server?
I assume that your issue is getting multiple rows from one, rather than formatting the date/time values as a string.
For this, you can use a recursive CTE:
with cte as (
select startdate as thetime, t.*
from t
union all
select dateadd(hour, 1, cte.startdate), . . . -- rest of columns here
from cte
where cte.thetime < cte.enddate
)
select cte.*
from cte;
You can then format thetime however you like, including the hyphenated version in your question.
SQL Server has a default limit of 100 for recursion -- the number of rows produced. Your example only uses times, so this can't exceed 24 and is not an issue. However, it could be an issue in other circumstances in which case option (maxrecursion 0) can be added to the query.
I have a list of schedule that looks like this
Start Time 2016-6-20 7:30AM End Time 2016-6-20 8:00AM
I want to create a query to calculate the duration between the intervals, it would work for 30 minutes but I don’t know how to create one for when it ends on 2:45 or 2:15. If I do date diff I would give me 30 minutes duration but for 2:15 or 2:45 it will not give me that
I want something to look like
7:30, Start time7:30, End Time 8:00AM, durwtion 30 minutes
8:00, Start time 8:00, End time 8:15, duration 15 minutes
do you mean something like this? fixing it up for hr:min would be a task, but minutes is easy.
declare #starttime datetime = '2016-6-20 7:30AM'
declare #endtime datetime = '2016-6-20 8:00AM'
select rtrim(datediff(minute,#starttime, #endtime)) + ' min'
edit, try this query
;with duration_dates as
(
SELECT top (100)
dateadd(minute,(datediff(minute,0,Start_Time_UTC)/ 15)*15,0) Start_Time_UTC
,dateadd(minute,(datediff(minute,0,dateadd(minute,15,End_time_UTC))/15)*15,0) End_time_UTC
--,<your id column here>
FROM EmployeeSchedules
)
SELECT
TimeInterval
,Start_Time_UTC
,End_time_UTC
,datediff(minute, Start_Time_UTC,End_time_UTC) duration
FROM Times
LEFT JOIN duration_dates ON CAST(Start_Time_UTC AS Time) = TimeInterval
I am after some guidance on the best way to get useful information out of our MIS database
Scenario:- I want to check staff utilisation by a variable period that I can drill down into. This needs to then be split into days so I can assess over a 24 hour period what was done
The table is huge and has loads of columns we need to calculate, so ideally I need to split the records that span 2 days into 2
The table has a datetimeformat field that has user [starttime], it then has a separate field that has [duration] which is in decimal hours.
So an example would be:
ID StartTime Duration Qty username
1 2016-11-24 23:00:00 2.00 1000 Joe Bloggs
In the example above Joe starts at 11pm and works till 1 am, so what I need is to somehow split this record in my query to put anything before midnight as 1 record and anything after into another This example is pretty simple as it is half/half but some might start at 10pm and finish at 6pm so I would need 2 hours and 6 hours.
Not sure on the best way to do this, my initial thoughts was to create a cte where a start time is in 1 day and if the starttime + duration was in the next day then split the record.
Not sure if there is an easier way or if anyone has had to do this before.
Any help appreciated
#Joe has the right idea, here is pseudo-SQL
SELECT ID,StartTime,Duration,Qty,username
WHERE TRUNCATE(StartTime,DAY) = TRUNCATE(StartTime + Duration hours ,DAY)
UNION
SELECT ID,StartTime, TRUNCATE(StartTime,DAY) + 1 days - StartTime hours ,Qty,username
WHERE TRUNCATE(StartTime,DAY) < TRUNCATE(StartTime+Duration hours,DAY)
UNION
SELECT ID,TRUNCATE(StartTime+Duration hour,DAY),StartTime + duration hours - DATE(StartTime+Duration),Qty,username
WHERE TRUNCATE(StartTime,DAY) < TRUNCATE(StartTime+Duration hours,DAY)
Where TRUNCATE(timestamp,DAY) truncates a timestamp to YYYY-MM-DD 00:00:00
You can multiply rows with join. Make Tally table, simple table with numbers 1, 2, 3... and do a join. I will use table starting at zero here:
CREATE TABLE Tally0 (Number INT IDENTITY(0,1) PRIMARY KEY NOT NULL);
GO
INSERT INTO Tally0 DEFAULT VALUES;
GO 10000
Now the harders part is conversion between dates and numerics:
;WITH
tmp1 AS (SELECT *,
DATEDIFF(SECOND, CONVERT(DATE, StartTime), StartTime)/3600.0
+ DATEPART(NANOSECOND, StartTime)/(3600*1000000000.0) AS startingHours
FROM Record),
tmp2 AS (SELECT *,
startingHours + Duration AS endingHours,
(startingHours + Duration)/24.0 AS endingDays
FROM tmp1)
SELECT *,
CASE WHEN Number = 0 THEN StartTime
ELSE DATEADD(DAY, Number, CONVERT(DATE, StartTime))
END AS StartTime2,
CASE WHEN Number = 0 AND 1 < endingDays THEN 24 - startingHours
WHEN Number = 0 THEN Duration
WHEN Number + 1 < endingDays THEN 24
ELSE endingHours - Number * 24
END AS Duration2
FROM tmp2
JOIN Tally0 ON Number < endingDays
So I have a set of Hours for a particular set of days (lets call it a schedule), that schedule describes the general pattern. However, occasionally there are what I will call exceptions where the schedule is grown or restricted.
For example:
Schedule for Monday is 7:00 AM - 4:00 PM, 5:00 PM - 9:00 PM
Exceptions for this next Monday in particular: 6:00 PM - 7:00 PM
Is it possible to manipulate the hours in the SQL Query to return 7:00 AM - 4:00 PM, 5:00 - 6:00PM, and 7:00 PM - 9:00 PM. I realize it is probably possible with a temp table and about three different queries, but I was hoping to do it more elegantly.
Another option might be to do this all in C# logic... I will really appreciate any help with this scheduling problem.
Source Tables (please note I am still Open to changing the structure if necessary)
Schedule - A user can set up a named schedule
Id
Name
ScheduleDates - Describes the dates range(s) a schedule is active
Id
ScheduleId
StartDate
EndDate
ScheduleEntries - Describe the schedule that describes most
of the dates in the schedule
ScheduleId
StartTime
EndTime
DayId (Describes which day of the week)
Exception
Id
Reason
StartTime
EndTime
Here is what I came up with for my specific case I decided that I would just like all the information back so that I can inform the user something about the hours are special.
DECLARE #start DATE = '12-25-2013'
DECLARE #end DATE = '1-31-2014'
;WITH [DateIt] AS -- Explodes the dates between start and end inclusive
(
SELECT
#start AS [sd],
DATENAME(dw, #start) AS [dt],
DATEPART(dw, #start) AS [dw]
UNION ALL
SELECT
DATEADD(DAY, 1, sd),
DATENAME(dw, DATEADD(DAY, 1, sd)) AS [dt],
DATEPART(dw, DATEADD(DAY, 1, sd)) AS [dw]
FROM
[DateIt] [di]
WHERE
sd < #end
)
SELECT --DISTINCT -- May need distinct here
[di].[sd] AS [Date],
--[di].[dw] AS [DayNumber],
--[di].[dt] AS [DayName],
[s].[Id] AS [ScheduleId],
[s].[Name] AS [ScheduleName],
[se].[LocationId],
[se].[StartTime],
[se].[EndTime],
0 AS [ExceptionState] -- Normal Schedule
FROM
[DateIt] [di]
INNER JOIN [campusHours].[ScheduleEntries] [se] ON [di].[dw] = [se].[DayId]
INNER JOIN [campusHours].[Schedule] [s] ON [s].[Id] = [se].[ScheduleId]
--INNER JOIN [campusHours].[ScheduleDates] [sd] ON [sd].[ScheduleId] = [s].[Id]
WHERE
EXISTS
(
SELECT
1
FROM
[campusHours].[ScheduleDates] sd
WHERE
-- Choose the valid schedule
[di].[sd] BETWEEN sd.[StartDate] AND sd.[EndDate]
AND
-- Don't take days from a different schedule
s.[Id] = sd.[ScheduleId]
)
UNION ALL -- We are going to add in the exceptions
SELECT
[di].[sd] AS [Date],
--[di].[dw] AS [DayNumber],
--[di].[dt] AS [DayName],
0 AS [ScheduleId], -- Represents that we aren't a schedule, could use NULL if you like
'Exception' AS [ScheduleName],
[e].[LocationId],
[e].[StartTime],
[e].[EndTime],
CASE WHEN [e].[IsContraction] = 1 THEN 1 ELSE 2 END AS [ExceptionState] -- 1 is contraction, 2 is extension
FROM
[DateIt] [di]
INNER JOIN [campusHours].[Exception] [e] ON [di].[sd] = [e].[StartDate]
ORDER BY
[di].[sd], [LocationId], [se].[StartTime]
Please put your suggestions as in what you have already figured, we can definitely make on that. Still my suggestion would be to make a proc and use Getdate function and create a algo to check whether that time falls under that your exception. Rest I am sure you can figure. :)
I have table in my db called Tasks.
Every record in that table has 2 fields: StartDate, EndDate
I need to create recursive stored procedure that will send mails in middle of those dates.
For example:
Start is 2013-10-22 12:00:00:000
End is 2013-10-24 12:00:00:000
I can do:
SELECT DATEADD(ms,
DATEDIFF(ms,'2013-10-22 12:00:00:000', '2013-10-24 12:00:00:000')/2,
'2013-10-22 12:00:00:000')
and then check if now is greater than that date, if Yes then I can send mail.
But I need to do that recursively: first main must be send in middle, second in 1/4, third in 1/8 etc and at the last when there are 2 hours left.
My first idea was to add column to my table and store date of last main in it, but I would like to avoid modifying tables.
I think it recursive select would be better, but any ideas on how to solve that are welcome :)
EDIT: My sample fiddle: http://sqlfiddle.com/#!3/25d0d/1
My example:
task starts at 2013-10-22 8:00 and ends at 2013-10-22 21:00
procedure starts as 2013-10-22 10:00
first record has send time 14:30 so nothing to send
procedure starts as 2013-10-22 12:00
first record has send time 14:30 so nothing to send
procedure starts as 2013-10-22 14:00
first record has send time 14:30 so nothing to send
procedure starts as 2013-10-22 16:00
first record has send time 14:30 so send mail about that task
next message should be send about 17:45
procedure starts as 2013-10-22 18:00
first record has send time 17:45 so send mail about that task
next message should be send about 19:22
procedure starts as 2013-10-22 20:00
first record has send time 19:22 so mail should be sended,
but because from 19:22 till 21:00 is less that 2 hours no mail is needed
Can't test on SQL Server 2005, but on SQL Server 2008 you can use a recursive common table expression (replacing the fixed dates below with your procedure parameters). The first part gets the first time, the second part keeps calculating the time between the last time and the end time until the difference between the times is less than 4 hours;
WITH cte AS (
SELECT DATEADD(ms,
DATEDIFF(ms, '2013-10-22 12:00:00:000',
'2013-10-24 12:00:00:000')/2,
'2013-10-22 12:00:00:000'
) a
UNION ALL
SELECT DATEADD(ms, DATEDIFF(ms,cte.a, '2013-10-24 12:00:00:000')/2, cte.a)
FROM cte
WHERE DATEDIFF(hour, cte.a, '2013-10-24 12:00:00:000') >= 4
)
SELECT * FROM cte;
An SQLfiddle to test with.
EDIT: To get the tasks that have a mail time that was in the last 2 hours (ie that should generate a mail), you can use something like;
WITH cte AS (
SELECT taskid,enddate, DATEADD(s,
DATEDIFF(s, startdate, enddate)/2, startdate) tm
FROM Tasks
UNION ALL
SELECT taskid,enddate, DATEADD(ms, DATEDIFF(ms,cte.tm, enddate)/2, cte.tm)
FROM cte
WHERE DATEDIFF(hour, cte.tm, enddate) >= 4
)
SELECT taskid, tm FROM cte WHERE tm < GETDATE() AND DATEDIFF(hour, tm, GETDATE()) < 2
If the job runs at somewhat irregular intervals, you may want to truncate GETDATE() to just hours.