sql custom generated week ranges - sql

I'm trying to create custom week numbers and ranges based on user entered Week Start Day.
I'm passing that User defined day with SET Datefirst = #Userdefinedvalue (using a function to map my weekday start values to T-SQL's). Also I have a function to gets me the last day of the current week.
What is the best approach for this?
My Goal is: if I select Tuesday as my start day of the week the Stored Procedure to generate my week numbers and start/end dates based on my selection for the entire year

The idea is to find the first day of the week. First, use SQL Server built in functions to move the start of the week to the Sunday (in the US) by subtracting datepart(wd). Actually datepart(wd) returns values form 1 to 7 and we want them 0 to 6.
Then, add back in an offset based on your date of the week. Here is example code:
declare #offset int (case when #DateFirst = 'Mon' then 1
when #DateFirst = 'Tue' then 2
...
when #DateFirst = 'Sun' then 0
end);
select dateadd(dd, #offset - (datepart(wd, thedate) - 1)
thedate) as WeekStartDate

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.

SQL query that displays the current date and count of days between two specific dates

I am trying to write a SQL query that shows the count of the days and date depending upon the financial period it falls in. The financial period starts 5 days before the month end eg march 27th to 26th April
For the above mentioned period if the day is 29th march, the count of the day should be 3 and the date should be 2020-04. The date should adjust depending upon the period it falls in.
I tried to adress the second part of this query by writing the below script but it does not bring any result
declare #date datetime
set #date = getdate()
SELECT format (date,'yyyy-MM-dd') as date
where #date
between dateadd(day,-5,EOMONTH(getdate(),-1)) and dateadd(day,-5,EOMONTH(getdate()))
updated to include 5 days from previous month.
Is this what you are looking for the second part?
Edit: Modifying the query. I guess this should give you what you need for both the parts.
you can try changing the dates in set #date.
Please note that the -4 instead of -5 is done intentionally as you said the financial month starts 5 days earlier. For March, 31 - 5 would give 26, but it should start on 27 right? so that on 29th the no. of days should be 3 including 27 and 29. Anyways, the query should be self explanatory, might just need to change the number depending on your requirement.
declare #date datetime
--set #date = getdate()
set #date = '2020-02-14'
SELECT format(#date,'yyyy-MM-dd') as date,
case WHEN #date < dateadd(day,-4,EOMONTH(#date)) THEN format(EOMONTH(#date),'yyyy-MM')
ELSE format(EOMONTH(#date, 1),'yyyy-MM') END
AS FinancialMonth,
CASE WHEN #date < dateadd(day,-4,EOMONTH(#date)) THEN DATEDIFF(day,dateadd(day,-5,EOMONTH(#date,-1)), #date)
ELSE DATEDIFF(day, dateadd(day,-5,EOMONTH(#date)), #date) END
AS CountDays
The following code will calculate what you need. Please read the comments in the code itself.
-- First, lets declare all the variables we need.
-- I tried to name them so they are selfexplanatory.
declare #inputdate DateTime,
#financialPeriodStartDate DateTime,
#EndOfTheMonth DateTime,
#nextFinantialMonthYear DateTime,
#previousFinancialPeriodStartDate DateTime
-- Now we calculate some of the values
set #inputdate = GETDATE() -- Let's use Today's date, but you can change this to any date.
set #EndOfTheMonth = EOMONTH(#inputdate) -- This is the end of the month for the input date
set #financialPeriodStartDate = DATEADD(DAY,-5,EOMONTH(#inputdate)) -- This is you Financial Period Start Date for the giving input date.
set #previousFinancialPeriodStartDate = DATEADD(DAY,-5,EOMONTH(DATEADD(MONTH, -1, #inputdate))) -- Also calculates the Start Date of the precious financial period.
set #nextFinantialMonthYear = DATEADD(MONTH, 1,#financialPeriodStartDate) -- This is the start date of the next financial period.
-- In the following Select, it calculates the values you need, based in the previous variables.
select
#inputdate as Today,
#EndOfTheMonth as EndOfMonth,
#previousFinancialPeriodStartDate as PreviousFinancialPeriodStartDate,
#financialPeriodStartDate as FinacialPeriodStartDate,
CASE WHEN DATEDIFF(DAY , #financialPeriodStartDate, #inputdate + 1 ) < 0
THEN DATEDIFF(DAY , #previousFinancialPeriodStartDate, #inputdate + 1 )
WHEN DATEDIFF(DAY , #financialPeriodStartDate, #inputdate + 1 ) >=0
THEN DATEDIFF(DAY , #financialPeriodStartDate, #inputdate + 1 )
END as DaysFromFPStartDate,
STR(YEAR(#nextFinantialMonthYear)) +'-'+LTRIM(RTRIM(STR(MONTH(#nextFinantialMonthYear)))) as NextFinantialPeriodMonthAndYear
I hope it helps.

How to write a variable within SQL using Dateadd and DateDiff for finding the last TWO days of the previous month

I am trying to write a variable using the dateadd and datediff that shows the last two days of previous month. One variable will be the second to last day of the previous month, the one I am having trouble with. The other will be the last day of the previous month, the one I was able to get. I am using SQL Server.
I've tried looking for it on Stack and I have only seen the last day of the previous month given and NOT the second to last day. I tried learning the dateadd and datediff, (which I still want to do).
This is what I tried so far:
Declare #CurrentMonth as date = '3/1/2019'
Declare #SecLastDayPrevMonth as date = DATEADD(MONTH, DATEDIFF(MONTH, 0, #currentmonth), -2)
Declare #LastDayPrevMonth as date = DATEADD(MONTH, DATEDIFF(MONTH, 0, #currentmonth), -1)
The results I am getting for the seclastdayPrevMonth is 2/28/2019. Instead I would want 2/27/2019
I am also getting 2/28/2019 for lastdayprevmonth which is what I want.
I am writing variables because the current month will change every month, and instead of having to update the other days I need within my query, I want to use variables so I am only updating the current month and everything else is flowing through.
And explanation as to why my dateadd/datediff is wrong and an explanation for why the correct dateadd/datediff is the way it is, will be very helpful
Why not refer to the last day when calculating the second last day? Also, your usage of DATEADD is very weird. The syntax is DATEADD(interval, increment, datetime)
Declare #recmonth as date = '3/1/2019'
Declare #LastDayPrevMonth as date = EOMONTH(DATEADD(MONTH, -1, #RecMonth))
Declare #SecLastDayPrevMonth as date = DATEADD(DAY, -1, #LastDayPrevMonth)
SELECT #SecLastDayPrevMonth, #LastDayPrevMonth
So we can calculate the last day of the previous month by subtracting one month from a date and then calling EOMONTH, which returns the last day of a given month. Then the second last day is just subtracting one day from that.
Yields:
SecLastDayPrevMonth LastDayPrevMonth
------------------- ----------------
2019-02-27 2019-02-28
As to "why", DATEDIFF() takes 3 arguments: datepart (string representation of a specific date part), startdate, enddate (both of which must be convertible to a date-ish object).
0 is essentially SQL's epoch, which, in this case is 1/1/1900. So the difference in months between 0 and 3/1/2019 is (119*12)+2 (+2 because we exclude March, since we aren't calculating a full month) = 1430 months difference.
Then, we are trying to add 2 months to our value. DATEADD() takes 3 arguments: datepart, number, date. But, in the example, you are adding 1430 months to whatever date -2 gets converted to (in this case, I believe it would be 12/30/1899, or 2 days before epoch). So, 1430 months after 12/30/1899 would be 2/30/2019, but February only has 28 days in 2019, so it returns 2/28/2019. In a Leap Year, it probably would return 2/29/2019.
To get your #LastDayPrevMonth and #SecLastDayPrevMonth with only DATEDIFF() and DATEADD(), you just need to change your calculations a little.
First thing you want to do is find the First Day of your Given Month. That can be done with DATEADD(month,DATEDIFF(month,0,#CurrentDate),0). We're essentially using the same thing we used above to calculate the number of months since epoch, but then we are adding those months back to epoch.
Now that we know the First Day of the Given Month, all we have to do is subtract days to get a day from the prior month.
So,
DECLARE #CurrentDate date = '2019-03-15' ; -- Changed it to something in the middle of the month.
DECLARE #FirstDayOfGivenMonth = DATEADD(month,DATEDIFF(month,0,#CurrentDate),0) ; -- 3/1/2019
DECLARE #LastDayOfPrevMonth date = DATEADD(day,-1,#FirstDayOfThisMonth) ; -- 2/28/2019
DECLARE #SecLastDayOfPrevMonth date = DATEADD(day, -2, #FirstDayOfThisMonth) ; -- 2/27/2019
SELECT #LastDayOfPrevMonth AS LDPM, #SecLastDayOfPrevMonth AS SLDPM ;
DECLARE #FourDaysLeftInPrevMonth date = DATEADD(day, -4, #FirstDayOfThisMonth) ; -- 2/25/2019
SELECT #FourDaysLeftInPrevMonth AS FourDaysLeftPrev ;
Granted, since SQL 2012, this can all be accomplished much easier with the EOM() function to get to the last day of a month. But if you only could use the two original functions from your original question, this would be one way to get to your needed values.

SQL Recur every x weekday every x weeks

I'm trying to write a sql query which depending on what the user selects will recur every x day every x weeks.
So the user will select that they want the job to recur on tuesdays every 2 weeks
the values that are supplied are
declare #StartDate datetime -- when the job first recurs
declare #recurrenceValue1 int -- amount of weeks
declare #recurrenceValue2 int -- day of week (mon-sun)
declare #NextOcurrance datetime -- when the job will recur
I know how to set it for the amount of weeks:
SET #NextOccurance = (Convert(char(12),#StartDate + (#RecurrenceValue1),106))
But I'm not sure how to make it roll on to the day of the week so if the #startDate is today and it should recur every 2 weeks on a tuesday it will see that 2 weeks today is wednesday so will loop until it know that the day is tuesday and that will be the #NextRecurrance date.
Thanks in advance
An easy way to add a number of weeks to a date is to use (MSDN DATEADD)
DATEADD(wk, #StartDate, #recurrenceValue1)
To find out which day of the week the date you are looking at belongs, you can use (MSDN DATEPART)
DATEPART(dw, #StartDate)
This function uses DATEFIRST to determine which day of the week is the first one (http://msdn.microsoft.com/en-us/library/ms181598.aspx)
SET DATEFIRST 1 --Where 1 = Monday and 7 = Sunday
So for your problem (DATEFIRST being set to 1 = Monday)..
SET DATEFIRST 1
declare #StartDate datetime -- when the job first recurs
declare #recurrenceValue1 int -- amount of weeks
declare #recurrenceValue2 int -- day of week (mon-sun)
declare #NextOcurrance datetime -- when the job will recur
SET #StartDate = '2011-12-16' -- This is a Friday
SET #recurrenceValue1 = 2 -- In 2 weeks
SET #recurrenceValue2 = 2 -- On Tuesday
SET #NextOcurrance = DATEADD(wk, #recurrenceValue1, #StartDate) -- Add our 2 weeks
/* Check if our incrementation falls on the correct day - Adjust if needed */
IF (DATEPART(dw, #NextOcurrance) != #recurrenceValue2) BEGIN
DECLARE #weekDay int = DATEPART(dw, #NextOcurrance)
SET #NextOcurrance = DATEADD(dd, ((7 - #weekDay) + #recurrenceValue2), #NextOcurrance) -- Add to #NextOcurrance the number of days missing to be on the requested day of week
END
The logic for the number of days to add is as follow:
We have 7 days in a week, how many days does it take to reach the end of this week. Add this number of days to the #recurrenceValue2 (day of week we are looking for).
PS: I can't post more than 2 HyperLinks because of my reputation. This is why the DATEFIRST URL is in plain text.
Here's some code to allow certain specific date to be handled differently. Though this code is good only for unique dates. If ones needs to skip an entire week for instance, using this code will require to add values for each day of this week to skip. For ranges other than unique days, this code should be modified to handle date ranges or specific weeks and/or day of week.
CREATE TABLE OccurenceExclusions (
ExclusionDate DATE not null,
NumberOfDaysToAdd int not null
PRIMARY KEY (ExclusionDate)
)
INSERT OccurenceExclusions VALUES ('2012-01-01', 7)
SET #NextOcurrance = DATEADD(dd, COALESCE((SELECT NumberOfDaysToAdd
FROM OccurrenceExclusions
WHERE ExclusionDate = #NextOcurrance), 0), #NextOcurrance)
Probably the best solution would be to use a calendar table. http://web.archive.org/web/20070611150639/http://sqlserver2000.databases.aspfaq.com/why-should-i-consider-using-an-auxiliary-calendar-table.html
You could then query it like so:
SELECT TOP 1 #NextOccurance = Date
FROM CalendarTable
WHERE Date >= DATEADD(week, #recurranceValue1, #StartDate)
AND DateName = #recurranceValue2
Using the principles the #DanielM set out i changed it to fit my query see below:
BEGIN
SET DATEFIRST 1 --Where 1 = Monday and 7 = Sunday
declare #StartDate datetime -- when the job first recurs
declare #recurrenceValue1 int -- amount of weeks
declare #recurrenceValue2 int -- day of week (mon-sun)
declare #NextOcurrance datetime -- when the job will recur
-- sets #nextoccurence to next date after x amount of weeks
SET #NextOccurance = DATEADD(wk, #recurrenceValue1, #StartDate) -- Add on weeks /* Check if our incrementation falls on the correct day - Adjust if needed */
IF (DATEPART(dw, #NextOccurance) != #recurrenceValue2)
BEGIN
DECLARE #Weekday int = DATEPART(dw, #NextOccurance)
SET #NextOccurance = DATEADD(dd, (#RecurrenceValue2 - #Weekday), #NextOccurance) -- Add to #NextOcurrance the number of days missing to be on the requested day of week
END
END

SQL Server Date Logic - Finding the next reminder date

Given the following database table:
StartDate DATETIME -- day that the reminder period starts
LastReminderDate DATETIME -- the last time the reminder triggered
DayOfMonth INT -- the day of the month to remind the user
Interval INT -- how often in months to remind the user
How can I figure out the next reminder date based on these values? For example:
StartDate = '6/1/2011'
LastReminderDate = '6/5/2011'
DayOfMonth = 5 -- remind on the 5th of the month
Interval = 2 -- remind every other month
For this particular example, the next reminder date should be 8/5/2011 because it reminds on the 5th of the month every two months. How would I write a function to figure this out?
If LastReminderDate is NULL, then LastReminderDate should be equal to StartDate
UPDATE:
StartDate = '6/1/2011'
LastReminderDate = NULL
DayOfMonth = 5
Interval = 2
In this case, there was no last reminder date. The first time the reminder would occur would be 6/5/2011. The solutions below seem to be returning 8/5 in this case.
Here are some specific rules:
The Reminder should always occur on whatever DayOfMonth is. If DayOfMonth would be illegal for the given month then it should be the last day of that month. For example....if DayOfMonth is 31 and the next reminder date would fall on June 31, then it should be June 30th instead.
The next reminder date should always be based off of the Last Reminder Date plus the Interval. If Last Reminder Date does not match the Day of Month, then it could potentially be more than what the interval was. For example...if Last Reminder was 6/1/2011 and the interval is 2 months, but the reminder is for the 20th of the month, then the next reminder will be 8/20/2011.
If there is no last reminder date, then use the Start Date instead of last reminder date...but this will use the earliest date in the future. If start date was 6/1/2011 and day of month is 5, then this will be 7/5/2011 since today is 6/22/2011. If Day of Month was 25 then it would be 6/25/2011
DECLARE
#StartDate AS datetime, -- day that the reminder period starts
#LastReminderDate AS datetime, -- the last time the reminder triggered
#DayOfMonth AS integer, -- the day of the month to remind the user
#Interval AS integer -- how often in months to remind the user
SET #StartDate = '6/1/2011'
SET #LastReminderDate = '6/5/2011'
SET #DayOfMonth = 5 -- remind on the 5th of the month
SET #Interval = 2 -- remind every other month
SELECT
CASE
WHEN #LastReminderDate IS NULL
THEN
CASE WHEN Day(#StartDate) <= #DayOfMonth
THEN DateAdd( month, ((Year( #StartDate ) - 1900) * 12) + Month( #StartDate ) - 1, #DayOfMonth - 1 )
ELSE DateAdd( month, ((Year( #StartDate ) - 1900) * 12) + Month( #StartDate ) - 0, #DayOfMonth - 1 )
END
ELSE DateAdd( month, #Interval, #LastReminderDate )
END
The meat of this is the last four lines, the SELECT CASE ... END statement. I provided a whole script that lets you plug in different values, and see how the SELECT CASE ... END behaves for those test values.
But to just use this on your table, use only the last four lines (and remove the # from the front of the names so they match the table's column names).
You could also generalize this so that Interval doesn't have to be months. If your table had an IntervalType column, you could supply that as the first argument to DateAdd(). See the docs but some common intervals are days, months, years, and so on.
EDIT2: Respect DayOfMonth.
Since you want to use the StartDate if there is no LastReminderDate, then you'll want to use COALESCE for that bit of logic: COALESCE(LastReminderDate, StartDate)
Now, get to the last of the previous month: DATEADD(DAY, -DAY(COALESCE(LastReminderDate, StartDate)), COALESCE(LastReminderDate, StartDate))
Finally, add the months and then get to the date that we need:
DATEADD(MONTH, Interval, DATEADD(DAY, -DAY(COALESCE(LastReminderDate, StartDate)) + DayOfMonth, COALESCE(LastReminderDate, StartDate)))
This will potentially go forward less than two months if the date of the last reminder was on a day of the month after the "DayOfMonth" that's configured for the reminder. You should be able to tweak that depending on what your business logic is in that situation.