If we start with a function to get the N'th week day:
Create Function [dbo].[fnGetNthWeekDay] (
@theDate datetime
, @theWeekday int
, @theNthDay int
)
Returns Table
As
Return
/* ===========================================================================================
Author: Jeff Williams
Created: 10/17/2019
Description: Returns the Nth day of the week from the beginning or end of the month of the
specified input date (@theDate).
This function was originally developed by Peter Larrson - the difference between his
version and this version are:
1) Removed hard-coded string date literals (e.g. '1900-01-01' and '1753-01-01')
a) Using the integer date value is natively converted in the execution plans
where the string date literals are implicitly converted.
2) Removed the derived table and moved the logic to the CROSS APPLY
3) Removed the extra SIGN function
4) Returns both theDate and nthDate
Input Parameters:
@theDate datetime to calculate the Nth day from
@theWeekDay the weekday to calculate
1 = Monday, 2 = Tuesday, ..., 7 = Sunday
@theNthDay the week number of the month
Valid values: -5, -4, -3, -2, -1, 1, 2, 3, 4, 5
Example Calls:
Select * From dbo.fnGetNthWeekDay('2020-09-01', 1, 1); -- Monday of 1st week
Select * From dbo.fnGetNthWeekDay('2020-11-01', 4, 4); -- Thursday of 4th week
Select * From dbo.fnGetNthWeekDay('2020-05-01', 1, -1); -- Last Monday of month
Revision History
Date Edited By Change
---------- --------------- --------------------------------------------------------------
10/17/2019 Jeff Williams Created
=========================================================================================== */
Select theDate = @theDate
, dt.nthDate
From (Values (dateadd(month, datediff(month, @theNthDay, @theDate), 0))) As mm(FirstOfMonth)
Cross Apply (Values (dateadd(day, 7 * @theNthDay - 7 * sign(@theNthDay + 1)
+ (@theWeekday + 6 - datediff(day, -53690, mm.FirstOfMonth) % 7) % 7, mm.FirstOfMonth))) As dt(nthDate)
Where @theWeekday Between 1 And 7
And datediff(month, dt.nthDate, @theDate) = 0
And @theNthDay In (-5, -4, -3, -2, -1, 1, 2, 3, 4, 5);
Then it becomes very easy to generate a year's data:
With months(num)
As (
Select t.n
From (
Values (1), (2), (3), (4), (5), (6), (7), (8), (9), (10), (11), (12)) As t(n)
)
Select *
From months As m
Cross Apply dbo.fnGetNthWeekDay(datetimefromparts(year(getdate()), m.num, 1, 0, 0, 0, 0), 2, 2) As t;