(SQL Server 2008 and above): I needed to update a dimDate
table to include the following columns:
- Last Sunday Of The Month:
- Last Monday Of The Month:
- Last Tuesday Of The Month:
- Last Wednesday Of The Month:
- Last Thursday Of The Month:
- Last Friday Of The Month:
- Last Saturday Of The Month:
Don't get me wrong; it is very simple to locate the last day of the month in SQL through the following piece of code:
CONVERT(DATE, DATEADD(DD, - (DATEPART(DD, (DATEADD(MM, 1, DayInTime)))), DATEADD(MM, 1, DayInTime))) AS [LastDayOfTheMonth]
But I could not find anywhere on the Internet, and I admit to not spending more than 2 hours searching, where people openly showed how to identify the last (insert day name) of the month.
Thus, I solved the equation and am posting it here, in case it is useful for anyone else, or, in case anyone has a simpler method I could have used, but simply could not see it.
The dimDates
table is populated through a prepopulated dimNumbers
table:
IF OBJECT_ID('dbo.dimNumbers') IS NOT NULL
DROP TABLE dbo.dimNumbers;
DECLARE @UpperBound INT = 1000000;
;WITH cteN(Number) AS
(
SELECT
ROW_NUMBER() OVER (ORDER BY s1.[object_id]) - 1
FROM
sys.all_columns AS s1
CROSS JOIN
sys.all_columns AS s2
)
SELECT [Number]
INTO ref.dimNumbers
FROM cteN
WHERE [Number] <= @UpperBound;
CREATE UNIQUE CLUSTERED INDEX CIX_dimNumbers ON ref.dimNumbers([Number]);
And then the dim Dates table is populated through the following means. And yes, I am lazy and want SQL to perform ALL possible calculations.
DECLARE @YearsToPopulate INT = 130;
-- Use the Magic of SQL to identify 1 Jan and then 31st December at the various edges of the required date time frames.
DECLARE @StartDate DATE = DATEADD(yy, DATEDIFF(yy,0,DATEADD(yyyy,-@YearsToPopulate,GETDATE())), 0);
DECLARE @EndDate DATE = DATEADD(yy, DATEDIFF(yy,0,DATEADD(yyyy,@YearsToPopulate,GETDATE())) + 1, -1);
DECLARE @RecordsToCreate INT = DATEDIFF(dd,@StartDate,@EndDate);
;WITH MyFullDateRange AS
(
SELECT TOP (@RecordsToCreate)
CAST(DATEADD(dd, Number, @StartDate) AS DATE) AS DayInTime
FROM
ref.[dimNumbers]
)
SELECT
--Insert Formulas here, using [DayInTime] as the Variable
-- The Formulas I have used here are not the topic of this discussion.
FROM
MyFullDateRange
And so, it took me a few hours to prove, but I finally came up with a simple, repeatable pattern to discover the "last (insert day name) of the month".
NB: Place the following inside the above SELECT
statement.
--Sunday [DayOfWeek] = 1.. Need to convert 1 to 0 <-> N + (7 - 1) % 7
DATEADD(DD,
- ((DATEPART(dw, DATEADD(DD, - (DATEPART(DD, (DATEADD(MM, 1, DayInTime)))), DATEADD(MM, 1, DayInTime)))) + (7 - 1) ) % 7 ,
CONVERT(DATE, DATEADD(DD, - (DATEPART(DD, (DATEADD(MM, 1, DayInTime)))), DATEADD(MM, 1, DayInTime)))
)
AS [LastsSundayOfTheMonth]
--Monday [DayOfWeek] = 2.. Need to convert 2 to 0 <-> N + (7 - 2) % 7
DATEADD(DD,
- ((DATEPART(dw, DATEADD(DD, - (DATEPART(DD, (DATEADD(MM, 1, DayInTime)))), DATEADD(MM, 1, DayInTime)))) + (7 - 2) ) % 7 ,
CONVERT(DATE, DATEADD(DD, - (DATEPART(DD, (DATEADD(MM, 1, DayInTime)))), DATEADD(MM, 1, DayInTime)))
)
AS [LastMondayOfTheMonth]
--Tuesday [DayOfWeek] = 3.. Need to convert 3 to 0 <-> N + (7 - 3) % 7
DATEADD(DD,
- ((DATEPART(dw, DATEADD(DD, - (DATEPART(DD, (DATEADD(MM, 1, DayInTime)))), DATEADD(MM, 1, DayInTime)))) + (7 -3) ) % 7 ,
CONVERT(DATE, DATEADD(DD, - (DATEPART(DD, (DATEADD(MM, 1, DayInTime)))), DATEADD(MM, 1, DayInTime)))
)
AS [LastTuesdayOfTheMonth]
--Wednesday [DayOfWeek] = 4.. Need to convert 4 to 0 <-> N + (7 - 4) % 7
DATEADD(DD,
- ((DATEPART(dw, DATEADD(DD, - (DATEPART(DD, (DATEADD(MM, 1, DayInTime)))), DATEADD(MM, 1, DayInTime)))) + (7 - 4) ) % 7 ,
CONVERT(DATE, DATEADD(DD, - (DATEPART(DD, (DATEADD(MM, 1, DayInTime)))), DATEADD(MM, 1, DayInTime)))
)
AS [LastWednesdayOfTheMonth]
--Thursday [DayOfWeek] = 5.. Need to convert 5 to 0 <-> N + (7 - 5 ) % 7
DATEADD(DD,
- ((DATEPART(dw, DATEADD(DD, - (DATEPART(DD, (DATEADD(MM, 1, DayInTime)))), DATEADD(MM, 1, DayInTime)))) + (7 - 5) ) % 7 ,
CONVERT(DATE, DATEADD(DD, - (DATEPART(DD, (DATEADD(MM, 1, DayInTime)))), DATEADD(MM, 1, DayInTime)))
)
AS [LastThursdayOfTheMonth]
--Friday [DayOfWeek] = 6.. Need to convert 6 to 0 <-> N + (7 - 6 ) % 7
DATEADD(DD,
- ((DATEPART(dw, DATEADD(DD, - (DATEPART(DD, (DATEADD(MM, 1, DayInTime)))), DATEADD(MM, 1, DayInTime)))) + (7-6) ) % 7 ,
CONVERT(DATE, DATEADD(DD, - (DATEPART(DD, (DATEADD(MM, 1, DayInTime)))), DATEADD(MM, 1, DayInTime)))
)
AS [LastFridayOfTheMonth]
--Saturday [DayOfWeek] = 7.. Need to convert 7 to 0 <-> N + (7 - 7 ) % 7
DATEADD(DD,
- ((DATEPART(dw, DATEADD(DD, - (DATEPART(DD, (DATEADD(MM, 1, DayInTime)))), DATEADD(MM, 1, DayInTime)))) + (7-7) ) % 7 ,
CONVERT(DATE, DATEADD(DD, - (DATEPART(DD, (DATEADD(MM, 1, DayInTime)))), DATEADD(MM, 1, DayInTime)))
)
AS [LastsSaturdayOfTheMonth]
I hope this is useful for others, or that someone is able to point out a simpler way to perform these.
To expand on [Matts] Answer for SQL Server 2012:
The below code works for SQL Server 2012 and above, and hopefully clearly identifies how each day is identified. (if it isn't, let me know and I will clarify)
DECLARE @YearsToPopulate INT = 130;
-- Use the Magic of SQL to identify 1 Jan and then 31st December at the various edges of the required date time frames.
DECLARE @Date1 DATE = DATEADD(yy, DATEDIFF(yy,0,DATEADD(yyyy,-@YearsToPopulate,GETDATE())), 0);
DECLARE @Date2 DATE = DATEADD(yy, DATEDIFF(yy,0,DATEADD(yyyy,@YearsToPopulate,GETDATE())) + 1, -1);
DECLARE @RecordsToCreate INT = DATEDIFF(dd,@Date1,@Date2);
WITH MyFullDateRange AS
(
SELECT TOP (@RecordsToCreate) CAST(DATEADD(dd, Number, @Date1) AS DATE) AS DayInTime
FROM ref.[dimNumbers]
)
SELECT DayInTime
--Sunday [DayOfWeek] = 1.. Need to convert 1 to 0 <-> N + (0 - 1) % 7
,LastSundayOfMonth = DATEADD(DAY, 0 - (@@DATEFIRST - 1 + DATEPART(dw,EOMONTH(DayInTime))) % 7, EOMONTH(DayInTime))
--Monday [DayOfWeek] = 2.. Need to convert 2 to 0 <-> N + (0 - 2) % 7
,LastMondayOfMonth = DATEADD(DAY, 0 - (@@DATEFIRST - 2 + DATEPART(dw,EOMONTH(DayInTime))) % 7, EOMONTH(DayInTime))
--Tuesday [DayOfWeek] = 3.. Need to convert 2 to 0 <-> N + (0 - 3) % 7
,LastTuesdayOfMonth = DATEADD(DAY, 0 - (@@DATEFIRST - 3 + DATEPART(dw,EOMONTH(DayInTime))) % 7, EOMONTH(DayInTime))
--Wednesday [DayOfWeek] = 4.. Need to convert 4 to 0 <-> N + (0 - 4) % 7
,LastWednesdayOfMonth = DATEADD(DAY, 0 - (@@DATEFIRST - 4 + DATEPART(dw,EOMONTH(DayInTime))) % 7, EOMONTH(DayInTime))
--Thursday [DayOfWeek] = 5.. Need to convert 2 to 0 <-> N + (0 - 5) % 7
,LastThursdayOfMonth = DATEADD(DAY, 0 - (@@DATEFIRST - 5 + DATEPART(dw,EOMONTH(DayInTime))) % 7, EOMONTH(DayInTime))
--Friday [DayOfWeek] = 6.. Need to convert 2 to 0 <-> N + (0 - 6) % 7
,LastFridayOfMonth = DATEADD(DAY, 0 - (@@DATEFIRST - 6 + DATEPART(dw,EOMONTH(DayInTime))) % 7, EOMONTH(DayInTime))
--Saturday [DayOfWeek] = 7.. Need to convert 2 to 0 <-> N + (0 - 7) % 7
,LastSaturdayOfMonth = DATEADD(DAY, 0 - (@@DATEFIRST - 7 + DATEPART(dw,EOMONTH(DayInTime))) % 7, EOMONTH(DayInTime))
FROM MyFullDateRange;
I would be using this code, if I did not have to support SQL Server 2008.
More SQL Server 2012 onwards code:
Unfortunately, what I was hoping would be a good alternative, is not working. Bare in mind the solution needs to fit within a dimDate table. Any suggestions to improve this? The part I do not like about this solution, is I would need to perform a double trip to dimDate table to update it.
DECLARE @YearsToPopulate INT = 130;
DECLARE @Date1 DATE = DATEADD(yy, DATEDIFF(yy,0,DATEADD(yyyy,-@YearsToPopulate,GETDATE())), 0);
DECLARE @Date2 DATE = DATEADD(yy, DATEDIFF(yy,0,DATEADD(yyyy,@YearsToPopulate,GETDATE())) + 1, -1);
DECLARE @RecordsToCreate INT = DATEDIFF(dd,@Date1,@Date2);
WITH MyFullDateRange AS
(
SELECT TOP (@RecordsToCreate) CAST(DATEADD(dd, Number, @Date1) AS DATE) AS DayInTime
FROM ref.[dimNumbers]
)
, CreateListOfDatesAndDOWs As
(
Select DayInTime AS DayInTime, DatePART( DW , DayInTime ) AS DayNumber
From MyFullDateRange
)
Select DayInTime AS [currentDate]--SQL 2012 -- DateFromParts(Year(DayInTime),Month(DayInTime) , 1) AS [currentDate]
,LastSun = MAX(CASE WHEN DayNumber=1 THEN DayInTime END)
,LastMon = MAX(CASE WHEN DayNumber=2 THEN DayInTime END)
,LastTue = MAX(CASE WHEN DayNumber=3 THEN DayInTime END)
,LastWed = MAX(CASE WHEN DayNumber=4 THEN DayInTime END)
,LastThu = MAX(CASE WHEN DayNumber=5 THEN DayInTime END)
,LastFri = MAX(CASE WHEN DayNumber=6 THEN DayInTime END)
,LastSat = MAX(CASE WHEN DayNumber=7 THEN DayInTime END)
From CreateListOfDatesAndDOWs
Group By DayInTime --DateFromParts(Year(DayInTime),Month(DayInTime),1)--SQL 2012
As this returns the following resultset, which for a dimDate table, is not what we are after. (I need to figure out how to format a table!)
currentDate LastSun LastMon LastTue LastWed LastThu LastFri LastSat
----------- ---------- ---------- ---------- ---------- ---------- ---------- ----------
1886-01-01 NULL NULL NULL NULL NULL 1886-01-01 NULL
1886-01-02 NULL NULL NULL NULL NULL NULL 1886-01-02
1886-01-03 1886-01-03 NULL NULL NULL NULL NULL NULL
1886-01-04 NULL 1886-01-04 NULL NULL NULL NULL NULL
1886-01-05 NULL NULL 1886-01-05 NULL NULL NULL NULL
1886-01-06 NULL NULL NULL 1886-01-06 NULL NULL NULL
1886-01-07 NULL NULL NULL NULL 1886-01-07 NULL NULL
1886-01-08 NULL NULL NULL NULL NULL 1886-01-08 NULL
1886-01-09 NULL NULL NULL NULL NULL NULL 1886-01-09
1886-01-10 1886-01-10 NULL NULL NULL NULL NULL NULL
1886-01-11 NULL 1886-01-11 NULL NULL NULL NULL NULL
1886-01-12 NULL NULL 1886-01-12 NULL NULL NULL NULL
1886-01-13 NULL NULL NULL 1886-01-13 NULL NULL NULL
1886-01-14 NULL NULL NULL NULL 1886-01-14 NULL NULL
1886-01-15 NULL NULL NULL NULL NULL 1886-01-15 NULL
1886-01-16 NULL NULL NULL NULL NULL NULL 1886-01-16
1886-01-17 1886-01-17 NULL NULL NULL NULL NULL NULL
1886-01-18 NULL 1886-01-18 NULL NULL NULL NULL NULL
1886-01-19 NULL NULL 1886-01-19 NULL NULL NULL NULL
1886-01-20 NULL NULL NULL 1886-01-20 NULL NULL NULL
1886-01-21 NULL NULL NULL NULL 1886-01-21 NULL NULL
1886-01-22 NULL NULL NULL NULL NULL 1886-01-22 NULL
1886-01-23 NULL NULL NULL NULL NULL NULL 1886-01-23
1886-01-24 1886-01-24 NULL NULL NULL NULL NULL NULL
1886-01-25 NULL 1886-01-25 NULL NULL NULL NULL NULL
1886-01-26 NULL NULL 1886-01-26 NULL NULL NULL NULL
1886-01-27 NULL NULL NULL 1886-01-27 NULL NULL NULL
1886-01-28 NULL NULL NULL NULL 1886-01-28 NULL NULL
1886-01-29 NULL NULL NULL NULL NULL 1886-01-29 NULL
1886-01-30 NULL NULL NULL NULL NULL NULL 1886-01-30
1886-01-31 1886-01-31 NULL NULL NULL NULL NULL NULL
1886-02-01 NULL 1886-02-01 NULL NULL NULL NULL NULL
1886-02-02 NULL NULL 1886-02-02 NULL NULL NULL NULL
1886-02-03 NULL NULL NULL 1886-02-03 NULL NULL NULL
1886-02-04 NULL NULL NULL NULL 1886-02-04 NULL NULL
1886-02-05 NULL NULL NULL NULL NULL 1886-02-05 NULL
1886-02-06 NULL NULL NULL NULL NULL NULL 1886-02-06