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. :)
Related
We have a dataset containing shift activity times from employees, looking like this:
Data Table
The results set should look like this:
Date - Employee ID - Start Time - End Time - Fixed Start Day
2020-01-02 - 17207 - 00:00 - 07:00 - NULL
2020-01-02 - 17207 - 23:00 - 07:00 - P
For most employees, they only work daytime and you can simply use min(starttime) and max(endtime) and group by date.
For one project, however, the employees work night shifts. Here's where my main questions come from:
1) If their shift started the previous day, the fixed date column should show a "P"
2) When they ended a shift on the same day they started a new one, it gets hard to define the correct start/end times.
What would be the best approach for this? I had this as starting point before trying to tackle the fixed date issue, but this obviously won't do:
SELECT schedDate,
tvID,
NULL as 'Fixed Start Day',
CONVERT(char(20),MIN(StartTime),108) as 'Fixed Start Time',
CONVERT(char(20),MAX(EndTime),108) as 'Fixed End Time'
FROM agentschedule
WHERE DATEPART(yy,schedDate) = 2020
GROUP BY schedDate
tvID
I tried a case when for defining when fixed start day should be null and when it should be P, but with the start/end time I'm not sure where to start..
Important note: due to company restrictions I can't use create table... so the end result has to be a select query.
Schematically:
SELECT date,
starttime,
CASE WHEN FixedStartDay IS NULL
THEN endtime
ELSE '24:00'
END endtime
FROM table
UNION ALL
SELECT DATEADD(DAY, 1, date),
'00:00',
endtime
FROM table
WHERE FixedStartDay IS NOT NULL
Of course if I understand the problem correctly.
I created a new SSIS package (SQL 2017) that reads through an "Export Request" table and exports data based on the schedule to an external storage. For example, users can add a new row to this table like "select * from table 1 where col1=2" with the schedule as June 25, 2018 3:30 pm.
My package (which is running using SQL agent) loops all the time in search of new tasks in that table and when scheduled, exports the result of the queries to a folder at that time.
There is a new feature requested to have these exported scheduled for recurring. For example, customers might want to export every day starting June 25, 2018 3:30 pm. The schedule can get complicated for annually, monthly, daily, every 3 month, ...
What is the best way to implement recurring inside SSIS? I can use a 5 char field for Cron but don't know how to query for new tasks using their Cron schedule.
Any help?
To confirm what you already have: You have a TASK table with tasks in it and some sort of date flag for when it needs to run. Then your job runs every x number of minutes and checks if there are any tasks to be picked up/run. If so it runs them. Then i am guessing you have a flag in the table you set saying it is completed after you run it in your loop? If you have multiple tasks to run at a time it just runs them all one at a time?
Then what you want to do is add this to it: something I did something very similar. I created a lookup table for each task (in my process each task had a name and I used that name to reference the lookup table, I called it schedule lookup).
In that table I put when and how often the report would need to run (so 2 columns) time of day and frequency. One example is 7:00 and weekdays. So for this report it runs every weekday only (m-F) and at 7pm.
Then when my job runs it would run the task like you have above, but then have another step, that would flag that task as complete, but then insert a new task in the task table (the task details would be the same) but I would look at my schedule lookup table described above to figure out the next date/time when the job should run again and use that as my next run date/time in my task table.
Below is the SP I use in my process to get the next schedule day/time and to update the existing one to completed, and then to create the new one.
NOTE: My scheduling has some advanced options you may not need, I have LOTS of comments that should explain what/why I am doing everything. I am calling a few functions I created in places but I dont think you would need those and can figure out what to do instead of my functions, but if you have questions let me know:
This is what I used so it is using my table structures/etc, but you could convert it to yours easily enough.
--Purpose
----------------------------------------------------------------------------------
-- calculates the next time to run/schedule the job
----------------------------------------------------------------------------------
-- NOTE: TO USE you have to insert the first value manually in queue table
----- possibile scenerios
-- if we want to schedule every x hours, or x days
-- run every month only
-- run weekdays only
-- run on certain days of month only
-- TO ADD MORE COMPLEX or different types of schedules:
-- special - different times for different days of week
-- ex - so have dayofweek:2:00,dayofweek:3:00 (and we parse out the day of week and number splitting out the strings)
-- hourly - to do more then once a day??
-- WHEN #ScheduleLookupType = 'hourly' THEN DATEADD(DAY, 1, #CurrentScheduleDate) -- FIX FIX FIX
-- EXEC dbo.JobsDynamicRescheduleFindNextTimeToScheduleJob #ReportName = 'TestReport1'
----------------------------------------------------------------------------------
ALTER PROCEDURE [dbo].[JobsDynamicRescheduleFindNextTimeToScheduleJob]
#ReportName VARCHAR(50)
AS
SET NOCOUNT ON
BEGIN TRY
-- left here for testing outside of SP
-- this will be passed from SP
--DECLARE #ReportName AS VARCHAR(50)
--SET #ReportName = 'TESTREport'
-- this sets the first day of the week to Monday (we need it set to a value to do calcluations for weekdays, I set it to 1 for monday and 7 for sunday)
-- this is due to server settings could have somethign else so forcing it here
SET DATEFIRST 1
DECLARE #CurrentScheduleDate AS DATE -- find the current date for the job that just ran
DECLARE #CurrentScheduleDayNumberOfWeek AS SMALLINT -- this pulls the number of the day of week 1=monday, 2=tuesdday
DECLARE #ScheduleLookupType AS VARCHAR(20) -- this is the type of schedule to do calculations on
DECLARE #TimeOfDayToScheduleJob AS VARCHAR(20) -- look this up, its the time to schedule the job
DECLARE #SpecialScheduleValue AS VARCHAR(8000) -- this is special value to lookup (only needed if non standard one)
DECLARE #NewScheduleDateONLY AS DATETIME -- to hold just the date of the schedule before combinng with time
DECLARE #NewScheduleDateTime AS DATETIME -- to hold the new schedule date and time, actual value to insert into queue
-- pull the current schedule date/time from the queue table
SELECT #CurrentScheduleDate = NextRunDateTime
FROM dbo.GenericReportingQueue (NOLOCK)
WHERE IsGenerated IS NULL
AND ReportName = #ReportName
-- to override for testing
--SET #CurrentScheduleDate = '5/20/2016'
SET #CurrentScheduleDayNumberOfWeek = DATEPART(WEEKDAY, #CurrentScheduleDate)
-- pull these values from lookup table
SELECT #ScheduleLookupType = dbo.fn_GetValueLookupTableValue(#ReportName, 'RescheduleJobDynamic_ScheduleLookupType'),
#TimeOfDayToScheduleJob = dbo.fn_GetValueLookupTableValue(#ReportName, 'RescheduleJobDynamic_TimeOfDayToScheduleJob'),
#SpecialScheduleValue = dbo.fn_GetValueLookupTableValue(#ReportName, 'RescheduleJobDynamic_SpecialScheduleValue')
/*
-- reset for testing
SET #ScheduleLookupType = 'specialdays' -- weekly, weekdays, monthly, specialdays
SET #TimeOfDayToScheduleJob = '8:00'
SET #SpecialScheduleValue = '5,6'
*/
-- calculations to get the date to schedule the job next time based off logic
SELECT #NewScheduleDateONLY = CASE
WHEN #ScheduleLookupType = 'daily' THEN DATEADD(DAY, 1, #CurrentScheduleDate)
WHEN #ScheduleLookupType = 'weekly' THEN DATEADD(DAY, 7, #CurrentScheduleDate)
WHEN #ScheduleLookupType = 'monthly' THEN DATEADD(MONTH, 1, #CurrentScheduleDate)
WHEN #ScheduleLookupType = 'yearly' THEN DATEADD(YEAR, 1, #CurrentScheduleDate)
-- only run on weekdays and skip weekends
WHEN #ScheduleLookupType = 'weekdays' THEN
CASE
WHEN #CurrentScheduleDayNumberOfWeek IN (1, 2, 3, 4) THEN DATEADD(DAY, 1, #CurrentScheduleDate)
WHEN #CurrentScheduleDayNumberOfWeek = 5 THEN DATEADD(DAY, 3, #CurrentScheduleDate)
END -- end case for day of week
-- only run on weekends and skip weekdays
WHEN #ScheduleLookupType = 'weekends' THEN
CASE
WHEN #CurrentScheduleDayNumberOfWeek = 6 THEN DATEADD(DAY, 1, #CurrentScheduleDate)
WHEN #CurrentScheduleDayNumberOfWeek = 7 THEN DATEADD(DAY, 6, #CurrentScheduleDate)
END -- end case for weekends only
WHEN #ScheduleLookupType = 'specialdays' THEN
-- for this we need to determine the current day, and the next day we want to run on, then add that many days
-- if next day is not till the following week we just find the first day in the list
-- Take taht number and do dateadd to it
DATEADD(DAY,
-- this does the select to determine what number to add based off current day and next day list
(SELECT ISNULL(
-- if this one I want to take today value and subtract from next value found
-- then add that number to todays date to give me the next schedule date
(SELECT TOP 1 StringValue - #CurrentScheduleDayNumberOfWeek
FROM dbo.fn_ParseText2Table(#SpecialScheduleValue, ',')
WHERE StringValue > #CurrentScheduleDayNumberOfWeek
ORDER BY StringValue)
,
-- if none found above I need to go to the next weeks first value
-- I need to take 7 - todays number (to get the rest of the week) then add the next number for the next week to it
(SELECT TOP 1 (7 - #CurrentScheduleDayNumberOfWeek) + StringValue
FROM dbo.fn_ParseText2Table(#SpecialScheduleValue, ',')
ORDER BY StringValue)
)-- end is null
) -- end select
, #CurrentScheduleDate) -- end dateadd for speical days
END -- outer case
SET #NewScheduleDateTime = #NewScheduleDateONLY + ' ' + #TimeOfDayToScheduleJob
-- for testing
--SELECT #ScheduleLookupType AS ReportLookupType, #TimeOfDayToScheduleJob AS TimeOfDayToSchedule, #SpecialScheduleValue AS SpecialValuesForCalc, #NewScheduleDateTime AS NewDateTimeToRun,
--#CurrentScheduleDate AS CurrentDateSchedule, #CurrentScheduleDayNumberOfWeek AS CurrentNumberDayOfWeek, #NewScheduleDateONLY AS NewScheduleDateOnly
-- &&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
-- now update and insert the new schedule date/time into the table
-- update existing record
UPDATE dbo.GenericReportingQueue
SET IsGenerated = 1,
DateReportRun = GETDATE(),
LastUpdateDate = GETDATE()
WHERE ISGenerated IS NULL
AND ReportName = #ReportName
-- insert new record with new date
INSERT INTO dbo.GenericReportingQueue (
ReportName, NextRunDateTime, CreatorID, CreateDate
)
SELECT #ReportName, #NewScheduleDateTime, 1, GETDATE()
END TRY
BEGIN CATCH
RETURN
END CATCH
I dont know about other countries but in South Africa we have a public holiday on a Monday if the Sunday was a Public holiday. I need to write an Update statement that will return the date after x number of days and should the date be a monday after a public holiday it should add an additional day. My simple update statement looks like:
UPDATE tbl_ProjectTracker
set Predicted_Date = DATEADD(DAY, 20, Actual_Eng_Start_date)
I already have a table for referencing but I am not sure if I should determine the additional day on the table or on the Update statement.
Please assist
You should have a PublicHoliday table
Then you could Update predicted_date like that
DECLARE #PublicHoliday AS TABLE
(
HolidayDate date
)
DECLARE #NumberDaysPredict int = 20
UPDATE pt
set pt.Predicted_Date = DATEADD(DAY, #NumberDaysPredict + hl.NumberHolidaysOnSunday, pt.Actual_Eng_Start_date)
FROM tbl_ProjectTracker pt
CROSS APPLY
(
SELECT Count(*) AS NumberHolidaysOnSunday
FROM #PublicHoliday ph
WHERE ph.HolidayDate BETWEEN pt.Actual_Eng_Start_date AND DATEADD(DAY, #NumberDaysPredict, Actual_Eng_Start_date)
AND Datepart(dw,ph.HolidayDate) = 1 -- Sunday
) hl
A simple way would be a case construct for the number of days:
UPDATE tbl_ProjectTracker
set Predicted_Date = DATEADD(
DAY,
CASE WHEN Actual_Eng_Start_date IN
(select day from public_holidays where datepart(dw, day) = 1) THEN 21 ELSE 20 END,
Actual_Eng_Start_date)
Unfortunately SQL Server's date / time functions are weak to say the least. The result of DATEPART(dw, ...) depends on a setting (DATEFIRST), so the query is kind of unreliable. Always make sure the setting is correct when you run it. It has been requested to extend DATEPART such as to accept an optional parameter for DATEFIRST so you'd get a consistent query, but Microsoft has closed this as "unsolvable" (https://connect.microsoft.com/SQLServer/feedbackdetail/view/432303/datepart-dw-date-should-allow-optional-date-first-Parameter).
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.
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.