Complicated sql date logic: How can I schedule my reports? - sql

I have a table that looks something like this (simplified):
*ScheduledReports*
ReportID
StartDate
Frequency
Interval (1=months, 2=days)
So if I would want to run a report every 3 days, frequency would be 3 and interval would be 2.
I'm trying to write a stored procedure that we can run once a day, that will run all reports from the table that are scheduled for today (or should have been run since the last time we ran the stored procedure).
The stored procedure also has access to the lastRunTime (the last time this stored procedure was run).
This is what my query looks like so far:
-- get monthly reports that should be run
SELECT reportID
FROM ScheduledReports
WHERE intervalType = 1 AND
dateadd(m, (
frequency * ceiling((
--sql server calculates datediff of months based on the actual int of the month
--not if it's a true month later, so the following is necessary to check for
--a dayOfMonth difference
CASE
WHEN startDate > #lastRunTime
THEN 0
WHEN day(startDate) > day(#lastRunTime)
THEN datediff(m, startDate, #lastRunTime) - 1
ELSE datediff(m, startDate, #lastRunTime)
END
) / (frequency*1.0))
), startDate) BETWEEN #lastRunTime AND getDate()
UNION ALL
-- get weekly reports that should be run
SELECT reportID
FROM ScheduledReports
WHERE intervalType = 2 AND
dateadd(d, (
frequency * ceiling((
CASE
WHEN startDate > #lastRunTime
THEN 0
ELSE datediff(d, startDate, #lastRunTime)
END
) / (frequency*1.0)
)), startDate) BETWEEN #lastRunTime AND getDate()
There is something off with the logic though. What is wrong with my logic? How can I accomplish this?

The stored procedure also has access to the lastRunTime (the last time this stored procedure was run).
Don't you need to know the last time each report was produced? And not the last time this sproc was run? Each time you run the sproc, each report may or may not be produced. In order to know if the interval for each report has passed for each report (3 days, 1 month, etc) you need to know when THAT report was last produced.
What if you add a LastReportRun date column to your table. Then don't test todays date against #lastRunTime, but rather against LastReportRun.
Report Run Today; Today = 2012/04/15
ID StartDate Freqcy Interval LastReportRun
-- ---------- ------ -------- ----------------
1 2000/01/01 1 Days 2012/04/14 1 day ago; print it
2 2000/01/01 14 Days 2012/04/09 6 days ago; don`t print it
3 2000/01/01 3 Months 2012/01/13 > 3 mos ago; print it
4 2000/01/01 3 Months 2012/01/17 < 3 mos ago; don`t print it

We managed to fix the bug - we were supposed to be adding 1 in the case, not subtracting, because we want to get the next run date, not the previous. Changed the month case to:
CASE
WHEN startDate > #lastRunTime
THEN 0
WHEN day(startDate) > day(#lastRunTime)
THEN datediff(m, startDate, #lastRunTime)
ELSE datediff(m, startDate, #lastRunTime) + 1
END
and the day case to:
CASE
WHEN startDate > #lastRunTime
THEN 0
ELSE datediff(d, startDate, #lastRunTime) + 1
END
And now it is working perfectly :)

Related

Find number of days in a given week according to the month for a given date. in SQL

Consider the date "2022-07-02"
For the month July first week only have 3 days in it.
I need to find the number of days in the week for the given date.
In above date the week has 3 days where "2022-07-02" day reside.
Example 2 :
For month June in 2022 first week has 5 days in the week
Therefore if i declare a date as "2022-06-03" it should pass the number of days in the week as 5
I need a query to find the number of days for the specific week.
set datefirst 1 -- assumes Monday is set as start of week
declare #myDate date = getdate();
-- calculate the next last day of week
declare #nextSunday date = dateadd(day, 7 - datepart(weekday, #myDate), #myDate);
select case
-- advancing into the next month implies a partial week
-- datediff(month, #myDate, nextSunday) = 1 would be equivalent
when day(#nextSunday) < day(#myDate) then 7 - day(#nextSunday)
-- else see if still within first week
when day(#nextSunday) < 7 then day(#nextSunday)
else 7 end;
Within a query you might use it this way:
select case
when day(nextSunday) < day(dateColumn) then 7 - day(nextSunday)
when day(nextSunday) < 7 then day(nextSunday)
else 7 end
from
myData cross apply (
values (dateadd(day, 7 - datepart(weekday, dateColumn), dateColumn))
) v(nextSunday);
https://dbfiddle.uk/?rdbms=sqlserver_2019&fiddle=ee5bfb52dabe31dd619cfd136689db59
If you don't want the shorthand form then just replace every instance of nextSunday in the final step with its full expression.
There's nothing in the logic that prevents this from working with another first day of week. I just chose a variable name that helped ellucidate this particular problem.

Checking active date in last six months

Hi I have table of temp who works at our company. Table has start date and End date. I need to find out all temps who were active in last six months.
Table like below
TEMPID STARTDATE ENDDATE
1 (ENDDATE >= DATEADD(MONTH,-6,GETDATE())
OR (ENDDATE = '1900-01-01'
AND STARTDATE <= GETDATE())
2
3
I am trying like this
SO basically I am looking only active jobs in last six months but by putting this condition it shows all recored who were avtive evan before
Doesn't seem like you're asking to filter by start date so leave that out of the query. I'm assuming you're using '1900-01-01' in place of a NULL value to indicate an employee is still active.
... WHERE (ENDDATE='1900-01-01' OR ENDDATE BETWEEN DATEADD(MONTH,-6,GETDATE()) AND GETDATE())
If you just want people who were hired within the past 6 months, you could add AND STARDATE >= DATEADD(MONTH, -6, GETDATE())

Createing a report using financial periods

I have created a report for management that will total everything up by month with in a date range. Management has now decided that rather than by month they would like to go by period. We have 13 periods in a year each is 28 days except the last one is 29 or 30 depending on if its a leap year. The beginning of the first period is always 1-1-YYYY. So now I will need to figure out what the beginning and end of each period is and total up each period. I am not really sure how to do this since every year the dates will change and they may want to look at periods from the previous year through the current period. The code and results I am currently using are enclosed
SELECT
DATEADD(MONTH, DATEDIFF(MONTH, 0, finspecteddate), 0) AS 'Date'
,COUNT(*) AS Lots
,sum(flotSize) as 'Lot Size'
,sum(LReject) 'Lots Rejected'
,sum(fnumreject) as Rejected
,sum(fsampleSize) as 'Sample Size'
,sum(BDueDate) as 'Before Due Date'
FROM
ReportData
WHERE
finspecteddate >= '01-01-2014'
AND finspecteddate <= '10-15-2014'
GROUP BY
DATEADD(MONTH, DATEDIFF(MONTH, 0, finspecteddate), 0)
ORDER BY
date
Modify the following queries to suit your needs:
;WITH Period AS (
SELECT 1 AS ReportingPeriod,
CAST('2013-01-01' AS datetime) AS PeriodStartDate,
CAST('2013-01-28' AS datetime) AS PeriodEndDate
UNION ALL
SELECT CASE
WHEN p.ReportingPeriod = 13 THEN 1
ELSE p.ReportingPeriod + 1
END,
CASE
WHEN p.ReportingPeriod = 13 THEN DATEADD(YEAR,YEAR(p.PeriodStartDate)-1899,'1900-01-01')
ELSE DATEADD(DAY,28,p.PeriodStartDate)
END,
CASE
WHEN p.ReportingPeriod = 12 THEN DATEADD(YEAR,YEAR(p.PeriodStartDate)-1900,'1900-12-31')
ELSE DATEADD(DAY,28,p.PeriodEndDate)
END
FROM Period p
WHERE p.PeriodStartDate < '2017-12-03'
)
SELECT
P.PeriodStartDate
,P.PeriodEndDate
,COUNT(*) AS Lots
,sum(flotSize) as 'Lot Size'
,sum(LReject) 'Lots Rejected'
,sum(fnumreject) as Rejected
,sum(fsampleSize) as 'Sample Size'
,sum(BDueDate) as 'Before Due Date'
FROM
ReportData R
INNER JOIN Period P ON R.finspecteddate >= P.PeriodStartDate AND R.finspecteddate <= P.PeriodEndDate
WHERE
finspecteddate >= '01-01-2014'
AND finspecteddate <= '10-15-2014'
GROUP BY
P.PeriodStartDate
,P.PeriodEndDate
ORDER BY
P.PeriodStartDate
It uses a recursive CTE to build a period table, which is then joined to ReportData to aggregate asccording to your requirements. I don't have SQL Server 2005 to test it on. It works with 2008. Post a SQL Fiddle if you need help in 2005.
If you haven't got one, create a period calendar table with a year, period number, start date and end date columns. Then when you need to refer to periods, you can refer to the table. When they change the definition of what a period is, you can change the table. When they decide that February 29 doesn't count as one of the 28 days, you can change the table. When they decide to use the first Monday instead of the first Thursday as the start of the year, you just change the table. And best of all, changing how next year works won't change how last year works.
Then you just join to the table to determine which period you're in.

did not get perfect answer with datediff

Datediff is very confusing for fetch the difference between two days:
DATEDIFF(DAY, '2014-09-01','2014-09-07')+1) AS totaldays
when i write above then it will give me 7 days it is perfect....
but when i write the below
DATEDIFF(DAY, '2014-09-01','2014-09-02')+1) AS totaldays
then it will give me 2 days but i want 1 day with this below function
DATEDIFF(DAY, '2014-09-01','2014-09-02')+1) AS totaldays
how can i get it?
The DATEDIFF function is working as expected.
DATEDIFF simply subtracts the second parameter from the third parameter by the specific element specified in the first parameter.
For example, the following query provides four columns... all which subtract the day specified in the second parameter from the day specified in the third:
Select DATEDIFF(DAY, '2014-09-01','2014-09-07')+1, -- Calculation: (7-1)+1 = 7
DATEDIFF(DAY, '2014-09-01','2014-09-07'), -- Calculation: (7-1) = 6
DATEDIFF(DAY, '2014-09-01','2014-09-02')+1, -- Calculation: (2-1)+1 = 2
DATEDIFF(DAY, '2014-09-01','2014-09-02') -- Calculation: (2-1) = 1
And the results are as expected:
Col1 | Col2 | Col3 | Col4
7 | 6 | 2 | 1
Here is the MS documentation for this function.
If you want the function to act differently then you will need to utilize CASE statement.
A "day" has a "duration" (that can be measured in units such as 24 hours)
That duration commences at 00:00:00 +0000
When we write a date/time as '2014-09-07' the time is assumed to be
00:00:00 +0000
so this: DATEDIFF(DAY, '2014-09-01','2014-09-07')
is measuring a duration between: "the start of 2014-09-01" to "the start of 2014-09-07" and; trying to represent this as a set of durations where "S" indicates the start point and "---" is the countable duration.
1 2 3 4 5 6 7 (day of the month)
S---S---S---S---S---S---S (time span)
Count the number of "---" in that time span (6) [and the number of start points, "S", is 7]
DATEDIFF() is measuring "duration" NOT the number of start points
What you need to do is add one day to the higher date instead of adding one to the result, because you are using "start of" not "end of" points of time.
from to
start of start of
DATEDIFF(DAY, '2014-09-01','2014-09-08') = 7
DATEDIFF(DAY, '2014-09-01','2014-09-07') = 6
DATEDIFF(DAY, '2014-09-01','2014-09-06') = 5
DATEDIFF(DAY, '2014-09-01','2014-09-05') = 4
DATEDIFF(DAY, '2014-09-01','2014-09-04') = 3
DATEDIFF(DAY, '2014-09-01','2014-09-03') = 2
DATEDIFF(DAY, '2014-09-01','2014-09-02') = 1
DATEDIFF(DAY, '2014-09-01','2014-09-01') = 0
Remove +1 from your SQL.
Use the following : DATEDIFF(DAY, '2014-09-01','2014-09-02') AS totaldays
Due to +1 it was giving wrong output.

Find the next occurance of a day of the week in SQL

I'm trying to update a SQL report sproc's WHERE clause to check whether a given date falls on or before the next occurrence of a class. Classes have a StartDate and occur once per week on the same day each week. Given the StartDate, how can I find the next occurrence of that day of the week?
E.G. If the StartDate is 1/18/2012, a Wednesday, and I run the report as of today, 1/26/2012, I need to find 2/1/2012 which is the next Wednesday after 1/26. If the StartDate is 1/19, a Thurs, and I run the report today, the formula should give me Thurs 1/26 which is today.
Here's sort of the idea in SQL:
SELECT *
FROM tbl_Class cs
INNER JOIN tbl_Enrollment sce ON cs.pk_ClassID = sce.fk_ClassID
WHERE ...
AND sce.StartDate < [Find date of next class after #AsOfDate using cs.StartDate]
Here's some example SQL that I came up with. 3 iterations so you can follow how I got to the end. The 3rd iteration should be something you can incorporate into a WHERE clause by substituting your column names for the variables.
Setup:
DECLARE #Startdate DATETIME,#currentdate datetime
SET #Startdate = '1-26-2012'
SET #Currentdate = '1-23-2012'
--This section just normalizes it so you can use 7 as the interval
--The offset depends on your current setting for DATEFIRST, U.S. English default is 7, Sunday.
-- see http://msdn.microsoft.com/en-us/library/ms187766.aspx
DECLARE #StartDateWorkingDayOfWeek int,#CurrentDateWorkingDayOfWeek int
SELECT #StartDateWorkingDayOfWeek =(DATEPART(weekday,#Startdate)-2)
SELECT #CurrentDateWorkingDayOfWeek=(DATEPART(weekday,#Currentdate)-2)
Iteration #1
--Iteration 1
IF #StartDateWorkingDayOfWeek < #CurrentDateWorkingDayOfWeek
SELECT DATEADD(DAY,DATEDIFF(DAY,0,#Currentdate)/7*7 + 7,#StartDateWorkingDayOfWeek)
else
SELECT DATEADD(DAY,DATEDIFF(DAY,0,#Currentdate)/7*7 + 0,#StartDateWorkingDayOfWeek)
Iteration #2
--Iteration 2
SELECT DATEADD(DAY,DATEDIFF(DAY,0,#Currentdate)/7*7 +
CASE WHEN #StartDateWorkingDayOfWeek < #CurrentDateWorkingDayOfWeek
then 7
ELSE 0
end
,#StartDateWorkingDayOfWeek)
Iteration #3
--iteration 3
SELECT DATEADD(DAY,DATEDIFF(DAY,0,#Currentdate)/7*7 +
CASE WHEN (DATEPART(weekday,#Startdate)-2) < (DATEPART(weekday,#Currentdate)-2)
then 7
ELSE 0
end
,(DATEPART(weekday,#Startdate)-2))
Hat tip to this article:
http://www.sqlmag.com/article/tsql3/datetime-calculations-part-3
Here's what I came up with thanks to TetonSig and his reference to this link: http://www.sqlmag.com/article/tsql3/datetime-calculations-part-3
We can get the date of the previous Monday exclusive of the current date (#AsOfDate) like so:
SELECT DATEADD(day, DATEDIFF(day,0, #AsOfDate-1) /7*7, 0);
This gets the number of days between 1/1/1900 and #AsOfDate in days. /7*7 converts that to whole weeks, and then adds it back to 1/1/1900 (a Mon) to get the Monday before #AsOfDate. The -1 makes it exclusive of #AsOfDate. Without the minus 1, if #AsOfDate were on a Monday, it would be counted as the "previous" Monday.
Next the author shows that to get the inclusive next Monday, we simply need to add 7 to the exclusive previous Monday formula:
SELECT DATEADD(d, DATEDIFF(day,0, #AsOfDate-1) /7*7, 0)+7;
Voila! We've now got the first Monday on or after #AsOfDate. The only problem is, the Monday (0) above is a moving target in my case. I need the first [DayOfWeek] determined by the class date, not the first Monday. I need to swap out a ClassDayOfWeek calculation for the 0s above:
DATEADD(d, DATEDIFF(d, [ClassDayOfWeek], #AsOfDate-1)/7*7, [ClassDayOfWeek])+7
I wanted to calculate the ClassDayOfWeek without being dependent on or having to mess with setting ##datefirst. So I calculated it relative to the base date:
DATEDIFF(d, 0, StartDate)%7
This gives 0 for Mon, 6 for Sun so we can now plug that in for [ClassDayOfWeek]. I should point out that this 0-6 value is dates 1/1/1900-1/7/1900 represented as an int.
DATEADD(d, DATEDIFF(d, DATEDIFF(d, 0, StartDate)%7, #AsOfDate-1)/7*7, DATEDIFF(d, 0, StartDate)%7)+7
And in use per the question:
SELECT *
FROM tbl_Class cs
INNER JOIN tbl_Enrollment sce ON cs.pk_ClassID = sce.fk_ClassID
WHERE ...
AND sce.StartDate < DATEADD(d,
DATEDIFF(d,
DATEDIFF(d, 0, cs.StartDate)%7,
#AsOfDate-1)/7*7,
DATEDIFF(d, 0, cs.StartDate)%7)+7
I derived the answer with a simple case statement.
In your situation #targetDOW would be the day of the week of the class.
DECLARE #todayDOW INT = DATEPART(dw, GETDATE());
DECLARE #diff INT = (#targetDOW - #todayDOW);
SELECT
CASE
WHEN #diff = 0 THEN GETDATE()
WHEN #diff > 0 THEN DATEADD(d,#diff,GETDATE())
WHEN #diff < 0 THEN DATEADD(d,#diff + 7,GETDATE())
END;