I previously wrote about the DimTime dimension. That is a pretty easy dimension to create and a pretty universal dimension. Now let’s look at a DimDate dimension table. There are many more decisions to be made than there are when creating the DimTime dimension and to create a useful DimDate dimension, you should get some user input before finalizing anything.
In the DimDate table I am providing here, I set it up for a company that has a fiscal year from April to March. And the fiscal year has the same value as the calendar year for the April 1 date. So in the included stored procedure I use an offset value of -3 to calculate the fiscal period within the fiscal year. (If the fiscal year were to have a value greater than the calendar year for the April 1 date, I would have used an offset value of 9.)
I also incorporate holidays. Holidays are not always easy to calculate, but for this table I make the assumption that the company has the following holidays:
- New Year's Day
- MLK Day
- Presidents Day
- Memorial Day
- Indepndence Day
- Labor Day
- Thanksgiving Day
- Day after Thanksgiving
- Christmas Eve
- Christmas Day
When a holiday falls on a Saturday, it is observed on the Friday before; When a holiday falls on a Sunday, it is observed on the Monday after; When Christmas Day falls on Saturday, Christmas Eve is observed on the Thursday before Christmas; When Christmas Day falls on Monday, Christmas Eve is observed on the Tuesday after Christmas.
Since holiday observance changes over the years, the accompanying stored procedure only calculates Federal Holidays after 1985. MLK Day began in 1986; Presidents Day began in 1971; Washingtons Birthday was observed on February 22 from 1879 until 1970; Memorial Day was observed on May 30 from 1886 until 1970, but it was not a Federal Holiday until 1971; Beginning in 1971, it is observed on the Last Monday in May; Columbus Day was observed as a Federal Holiday on October 12 from 1937 to 1970; Since 1971 it has been observed on the Second Monday in October; From 1938 to 1970 Veterans Day was observed on November 11; From 1971 to 1977 it was observed on the Fourth Monday in October; And since 1978, Veterans Day has been observed on November 11; From 1863 to 1938 Thanksgiving Day was observed on the Last Thursday in November; In 1939 it was the Third Thursday in November; In 1940 and 1941 it was the Second to Last Thursday in November; Since 1942 it has been observed on the Fourth Thursday in November. I did not include Inauguration Day in the stored procedure. In addition to the ten annual Federal Holidays, Inauguration Day is observed on January 20 every four years following a U.S. Presidential election. It is only observed by Federal Government employees in the Washington D.C. area, so that is why I did not include it.
If you are building a DimDate table that includes dates before 1986 and you want to calculate Federal Holidays, you may want to do more research on the observance of Federal Holidays.
If the company's Supply Chain operations are using the data warehouse, I would add the UPS holidays and the FedEx holidays (but those holidays are not included here.) Each one of those companies has separate holiday schedules for the ground service and their overnight service. You would have to go to the company website each year and incorporate the dates they have decided on.
Then I set an indicator to distinguish between workdays and non-workdays. I have indicators for IsWorkday, IsWeekend, IsWeekday, IsFederalHoliday, IsBankHoliday, and IsCompanyHoliday.
I also include the day number of the year, the day number of the quarter, and then I number the days beginning with 1-1-1900. These values will help when someone is trying to calculate the number of days between dates. Right away you can see that another potential attribute would be to create similar columns for work days.
I could add more indicators, such as IsFirstDayOfMonth, IsUPSGroundHoliday, IsFedExGroundHoliday, etc. And I could add more columns, such as WorkDayNumberOfYear, WorkDayNumberOf FiscalYear, etc.
And I could add indicators, such as IsToday, IsTomorrow, IsYesterday, IsCurrentWeek, IsCurrentMonth, IsLastWeek, IsLastMonth, etc. If I were to setup these indicators I would then have to set up a stored procedure to run everyday to update these values.
In Kimball's view of a data mart, all the natural keys should be represented with a surrogate key that is a simple integer value and that has no meaning at all. This gives us complete freedom in the data mart to add to or redefine a natural key's meaning and, more importantly, the usage of the smallest possible integer type for surrogate keys will lead to a smaller fact table.
All this is very good advice. Nevertheless, there are situations in which the rules surrounding the usage of surrogate keys should be relaxed. With this table I take that approach and I make the surrogate keys meaningful instead of meaningless.
We can use a meaningless key as a surrogate key for the Date dimension. However, is there any point in doing so? In my opinion, the best representation of a date key is an integer in the form of YYYYMMDD, so that 20110109 represents January 9th 2011. Note that even the Kimball Group, writing in the book The Microsoft Data Warehouse Toolkit, accept that this can be a good idea. The main reason for this is that it makes SQL queries that filter by date much easier to write and much more readable – we very often want to partition a measure group by date, for instance. The reason that it's safe to do this is that the date in the date dimension table will never change. You might add some attributes to a date dimension table and you might load new data into it, but the date that is already there will never need to be altered.
It also helps when representing invalid dates. All invalid dates may be easily represented with negative numbers, so -1 may be the unknown date, -2 may be the empty date and so on. We will have plenty of space for all the dummy dates we will ever need. A word of warning about the type of the key: we sometimes face situations where the DateTime data type has been used for the key of the Date dimension. This is absolutely the wrong thing to do, as not only is a DateTime representation going to be bigger than the INT representation, the DateTime data type does not let us add dummy values to the dimension easily.
Here is the way I would initially define the DimDate table.
CREATE TABLE [dbo].[DimDate]( [DateKey] int NOT NULL , [FullDate] DATE NOT NULL , [MonthNumberOfYear] tinyint NOT NULL , [MonthNumberOfQuarter] tinyint NOT NULL , [ISOYearAndWeekNumber] char(7) NOT NULL , [ISOWeekNumberOfYear] tinyint NOT NULL , [SSWeekNumberOfYear] tinyint NOT NULL , [ISOWeekNumberOfQuarter_454_Pattern] tinyint NOT NULL , [SSWeekNumberOfQuarter_454_Pattern] tinyint NOT NULL , [SSWeekNumberOfMonth] tinyint NOT NULL , [DayNumberOfYear] smallint NOT NULL , [DaysSince1900] int NOT NULL , [DayNumberOfFiscalYear] smallint NOT NULL , [DayNumberOfQuarter] smallint NOT NULL , [DayNumberOfMonth] tinyint NOT NULL , [DayNumberOfWeek_Sun_Start] tinyint NOT NULL , [MonthName] varchar(10) NOT NULL , [MonthNameAbbreviation] char(3) NOT NULL , [DayName] varchar(10) NOT NULL , [DayNameAbbreviation] char(3) NOT NULL , [CalendarYear] smallint NOT NULL , [CalendarYearMonth] char(7) NOT NULL , [CalendarYearQtr] char(7) NOT NULL , [CalendarSemester] tinyint NOT NULL , [CalendarQuarter] tinyint NOT NULL , [FiscalYear] smallint NOT NULL , [FiscalMonth] tinyint NOT NULL , [FiscalQuarter] tinyint NOT NULL , [FiscalYearMonth] char(7) NOT NULL , [FiscalYearQtr] char(8) NOT NULL , [QuarterNumber] int NOT NULL , [YYYYMMDD] char(8) NOT NULL , [MM/DD/YYYY] char(10) NOT NULL , [YYYY/MM/DD] char(10) NOT NULL , [YYYY-MM-DD] char(10) NOT NULL , [MonDDYYYY] char(11) NOT NULL , [IsLastDayOfMonth] char(1) NOT NULL , [IsWeekday] char(1) NOT NULL , [IsWeekend] char(1) NOT NULL , [IsWorkday] char(1) NOT NULL DEFAULT 'N' , [IsFederalHoliday] char(1) NOT NULL DEFAULT 'N' , [IsBankHoliday] char(1) NOT NULL DEFAULT 'N' , [IsCompanyHoliday] char(1) NOT NULL DEFAULT 'N' , CONSTRAINT [PK_DimDate_DateKey] PRIMARY KEY CLUSTERED ([DateKey] ASC) ) GO |
Note that all of the columns are defined as NOT NULL. This is ideal for dimension tables, but when trying to incorporate invalid dates, this may not be the best approach. In this example, I do not include any invalid dates.
And here is a stored procedure I would use to populate it.
CREATE PROCEDURE dbo.PopulateDimDate @starting_dt DATE , @ending_dt DATE , @FiscalYearMonthsOffset int AS SET NOCOUNT ON SET DATEFIRST 7 -- Standard for U.S. Week starts on Sunday -- Standard Holidays -- New Years Day - Jan 1 -- MLK Day - 3rd Monday in Jan -- Presidents Day - 3rd Monday in Feb -- Memorial Day - Last Mon in May -- Independence Day - Jul 4 -- Labor Day - 1st Mon in Sep -- Columbus Day - 2nd Mon in Oct -- Veterans Day - Nov 11 -- Thanksgiving Day - 4th Thurs in Nov -- Day after Thanksgiving - Day after 4th Thurs in Nov -- Christmas Eve - Dec 24 -- Christmas Day - Dec 25 DECLARE @HolidayTable TABLE (HolidayKey int NOT NULL PRIMARY KEY , HolidayDate DATE NOT NULL , HolidayName varchar(50) NOT NULL , IsFedHoliday bit NOT NULL DEFAULT(0) , IsBankHoliday bit NOT NULL DEFAULT(0) , IsUSACorpHoliday bit NOT NULL DEFAULT(0) ) DECLARE @Yr int , @EndYr int , @Offset int , @WeekNumberInMonth int , @Jan1 DATE , @Feb1 DATE , @May1 DATE , @Sep1 DATE , @Oct1 DATE , @Nov1 DATE , @MemorialDay DATE , @ThanksgivingDay DATE SET @Yr = DATEPART(yyyy, @starting_dt) SET @EndYr = DATEPART(yyyy, @ending_dt) WHILE @Yr <= @EndYr BEGIN IF @Yr > 1985 BEGIN SET @Jan1 = CAST(CAST(@Yr AS char(4)) + '0101' AS DATE) SET @Feb1 = CAST(CAST(@Yr AS char(4)) + '0201' AS DATE) SET @May1 = CAST(CAST(@Yr AS char(4)) + '0501' AS DATE) SET @Sep1 = CAST(CAST(@Yr AS char(4)) + '0901' AS DATE) SET @Oct1 = CAST(CAST(@Yr AS char(4)) + '1001' AS DATE) SET @Nov1 = CAST(CAST(@Yr AS char(4)) + '1101' AS DATE) -- New Years Day logic -- Could be celebrated on New Years Day, the Friday before, or the Monday after -- depending on whether the day falls on a weekend or not and the value of @FiscalYearMonthsOffset IF (DATEPART(dw, @Jan1) > 1) AND (DATEPART(dw, @Jan1) < 7) BEGIN INSERT INTO @HolidayTable SELECT CAST(CAST(@Yr AS char(4)) + '0101' as int) , CAST(CAST(@Yr AS char(4)) + '0101' AS DATE) , 'New Year''s Day' , 1 -- IsFedHoliday , 1 -- IsBankHoliday , 1 -- IsUSACorpHoliday END IF (DATEPART(dw, @Jan1) = 1) BEGIN INSERT INTO @HolidayTable SELECT CAST(CAST(@Yr AS char(4)) + '0102' as int) , CAST(CAST(@Yr AS char(4)) + '0102' AS DATE) , 'New Year''s Day' , 1 -- IsFedHoliday , 1 -- IsBankHoliday , 1 -- IsUSACorpHoliday END IF (DATEPART(dw, @Jan1) = 7) BEGIN -- For most banks, the fiscal year ends 12-31 and New Year's Day is celebrated in the New Year. IF @FiscalYearMonthsOffset = 0 BEGIN -- When an organization's fiscal year ends on 12-31, and New Years falls on Saturday, New Years is observed on the following Monday. INSERT INTO @HolidayTable SELECT CAST(CAST(@Yr - 1 AS char(4)) + '1231' as int) , CAST(CAST(@Yr - 1 AS char(4)) + '1231' AS DATE) , 'New Year''s Day' , 1 -- IsFedHoliday , 0 -- IsBankHoliday , 0 -- IsUSACorpHoliday INSERT INTO @HolidayTable SELECT CAST(CAST(@Yr AS char(4)) + '0103' as int) , CAST(CAST(@Yr AS char(4)) + '0103' AS DATE) , 'New Year''s Day' , 0 -- IsFedHoliday , 1 -- IsBankHoliday , 1 -- IsUSACorpHoliday END ELSE BEGIN -- When an organization's fiscal year ends on a day other than 12-31, and New Years falls on Saturday, New Years is observed on the previous Friday. INSERT INTO @HolidayTable SELECT CAST(CAST(@Yr - 1 AS char(4)) + '1231' as int) , CAST(CAST(@Yr - 1 AS char(4)) + '1231' AS DATE) , 'New Year''s Day' , 1 -- IsFedHoliday , 0 -- IsBankHoliday , 1 -- IsUSACorpHoliday INSERT INTO @HolidayTable SELECT CAST(CAST(@Yr AS char(4)) + '0103' as int) , CAST(CAST(@Yr AS char(4)) + '0103' AS DATE) , 'New Year''s Day' , 0 -- IsFedHoliday , 1 -- IsBankHoliday , 0 -- IsUSACorpHoliday END END -- MLK Day logic -- 3rd Monday in Jan SET @offset = 2 - DATEPART(dw, @Jan1) SET @WeekNumberInMonth = 3 INSERT INTO @HolidayTable SELECT CAST(CONVERT(char(8), DATEADD(dd, @offset + (@WeekNumberInMonth - CASE WHEN @offset >= 0 THEN 1 ELSE 0 END) * 7, @Jan1), 112) as int) , DATEADD(dd, @offset + (@WeekNumberInMonth - CASE WHEN @offset >= 0 THEN 1 ELSE 0 END) * 7, @Jan1) , 'MLK Day' , 1 -- IsFedHoliday , 1 -- IsBankHoliday , 1 -- IsUSACorpHoliday -- President's Day logic -- 3rd Monday in Feb SET @offset = 2 - DATEPART(dw, @Feb1) INSERT INTO @HolidayTable SELECT CAST(CONVERT(char(8), DATEADD(dd, @offset + (@WeekNumberInMonth - CASE WHEN @offset >= 0 THEN 1 ELSE 0 END) * 7, @Feb1), 112) as int) , DATEADD(dd, @offset + (@WeekNumberInMonth - CASE WHEN @offset >= 0 THEN 1 ELSE 0 END) * 7, @Feb1) , 'President''s Day' , 1 -- IsFedHoliday , 1 -- IsBankHoliday , 1 -- IsUSACorpHoliday -- Memorial Day logic -- Last Monday in May SET @MemorialDay = CASE DATEPART(dw, CAST(CAST(@Yr AS char(4)) + '0531' AS DATE)) WHEN 1 THEN CAST(CAST(@Yr AS char(4)) + '0525' AS DATE) WHEN 2 THEN CAST(CAST(@Yr AS char(4)) + '0531' AS DATE) WHEN 3 THEN CAST(CAST(@Yr AS char(4)) + '0530' AS DATE) WHEN 4 THEN CAST(CAST(@Yr AS char(4)) + '0529' AS DATE) WHEN 5 THEN CAST(CAST(@Yr AS char(4)) + '0528' AS DATE) WHEN 6 THEN CAST(CAST(@Yr AS char(4)) + '0527' AS DATE) ELSE CAST(CAST(@Yr AS char(4)) + '0526' AS DATE) END INSERT INTO @HolidayTable SELECT CAST(CONVERT(char(8), @MemorialDay, 112) as int) , @MemorialDay , 'Memorial Day' , 1 -- IsFedHoliday , 1 -- IsBankHoliday , 1 -- IsUSACorpHoliday -- Independence Day logic -- Jul 4th of each year IF (DATEPART(dw, CAST(CAST(@Yr AS char(4)) + '0704' AS DATE)) > 1) AND (DATEPART(dw, CAST(CAST(@Yr AS char(4)) + '0704' AS DATE)) < 7) BEGIN INSERT INTO @HolidayTable SELECT CAST(CAST(@Yr AS char(4)) + '0704' as int) , CAST(CAST(@Yr AS char(4)) + '0704' AS DATE) , 'Independence Day' , 1 -- IsFedHoliday , 1 -- IsBankHoliday , 1 -- IsUSACorpHoliday END IF (DATEPART(dw, CAST(CAST(@Yr AS char(4)) + '0704' AS DATE)) = 1) BEGIN INSERT INTO @HolidayTable SELECT CAST(CAST(@Yr AS char(4)) + '0705' as int) , CAST(CAST(@Yr AS char(4)) + '0705' AS DATE) , 'Independence Day' , 1 -- IsFedHoliday , 1 -- IsBankHoliday , 1 -- IsUSACorpHoliday END IF (DATEPART(dw, CAST(CAST(@Yr AS char(4)) + '0704' AS DATE)) = 7) BEGIN INSERT INTO @HolidayTable SELECT CAST(CAST(@Yr AS char(4)) + '0703' as int) , CAST(CAST(@Yr AS char(4)) + '0703' AS DATE) , 'Independence Day' , 1 -- IsFedHoliday , 1 -- IsBankHoliday , 1 -- IsUSACorpHoliday END -- Labor Day logic -- 1st Monday in September SET @offset = 2 - DATEPART(dw, @Sep1) SET @WeekNumberInMonth = 1 INSERT INTO @HolidayTable SELECT CAST(CONVERT(char(8), DATEADD(dd, @offset + (@WeekNumberInMonth - CASE WHEN @offset >= 0 THEN 1 ELSE 0 END) * 7, @Sep1), 112) as int) , DATEADD(dd, @offset + (@WeekNumberInMonth - CASE WHEN @offset >= 0 THEN 1 ELSE 0 END) * 7, @Sep1) , 'Labor Day' , 1 -- IsFedHoliday , 1 -- IsBankHoliday , 1 -- IsUSACorpHoliday -- Columbus Day logic -- 2nd Monday in October -- Usually only observed by Fed Govt and Banks SET @offset = 2 - DATEPART(dw, @Oct1) SET @WeekNumberInMonth = 2 INSERT INTO @HolidayTable SELECT CAST(CONVERT(char(8), DATEADD(dd, @offset + (@WeekNumberInMonth - CASE WHEN @offset >= 0 THEN 1 ELSE 0 END) * 7, @Oct1), 112) as int) , DATEADD(dd, @offset + (@WeekNumberInMonth - CASE WHEN @offset >= 0 THEN 1 ELSE 0 END) * 7, @Oct1) , 'Columbus Day' , 1 -- IsFedHoliday , 1 -- IsBankHoliday , 0 -- IsUSACorpHoliday -- Veterans Day logic -- October 11th of each year -- Usually only observed by Fed Govt and Banks IF (DATEPART(dw, CAST(CAST(@Yr AS char(4)) + '1111' AS DATE)) > 1) AND (DATEPART(dw, CAST(CAST(@Yr AS char(4)) + '1111' AS DATE)) < 7) BEGIN INSERT INTO @HolidayTable SELECT CAST(CAST(@Yr AS char(4)) + '1111' as int) , CAST(CAST(@Yr AS char(4)) + '1111' AS DATE) , 'Veterans Day' , 1 -- IsFedHoliday , 1 -- IsBankHoliday , 0 -- IsUSACorpHoliday END IF (DATEPART(dw, CAST(CAST(@Yr AS char(4)) + '1111' AS DATE)) = 1) BEGIN INSERT INTO @HolidayTable SELECT CAST(CAST(@Yr AS char(4)) + '1112' as int) , CAST(CAST(@Yr AS char(4)) + '1112' AS DATE) , 'Veterans Day' , 1 -- IsFedHoliday , 1 -- IsBankHoliday , 0 -- IsUSACorpHoliday END IF (DATEPART(dw, CAST(CAST(@Yr AS char(4)) + '1111' AS DATE)) = 7) BEGIN INSERT INTO @HolidayTable SELECT CAST(CAST(@Yr AS char(4)) + '1110' as int) , CAST(CAST(@Yr AS char(4)) + '1110' AS DATE) , 'Veterans Day' , 1 -- IsFedHoliday , 1 -- IsBankHoliday , 0 -- IsUSACorpHoliday END -- Thanksgiving Day logic -- 4th Thursday of November SET @offset = 5 - DATEPART(dw, @Nov1) SET @WeekNumberInMonth = 4 SET @ThanksgivingDay = DATEADD(dd, @offset + (@WeekNumberInMonth - CASE WHEN @offset >= 0 THEN 1 ELSE 0 END) * 7, @Nov1) INSERT INTO @HolidayTable SELECT CAST(CONVERT(char(8), @ThanksgivingDay, 112) as int) , @ThanksgivingDay , 'Thanksgiving Day' , 1 -- IsFedHoliday , 1 -- IsBankHoliday , 1 -- IsUSACorpHoliday -- Day after Thanksgiving Day logic -- Not observed by Fed Govt and Banks INSERT INTO @HolidayTable SELECT CAST(CONVERT(char(8), DATEADD(dd, 1, @ThanksgivingDay), 112) as int) , DATEADD(dd, 1, @ThanksgivingDay) , 'Day after Thanksgiving' , 0 -- IsFedHoliday , 0 -- IsBankHoliday , 1 -- IsUSACorpHoliday -- Christmas Eve logic -- Federal Govt and Banks do not celebrate Christmas Eve -- Logic can get complex when Christmas Day falls on a weekend. -- Using this logic, if Christmas Eve falls on Sunday, it will be observed on the following Tuesday. -- If Christmas Eve falls on Friday or Saturday, it will be observed on 12-23. -- Many companies do not use the following logic. IF (DATEPART(dw, CAST(CAST(@Yr AS char(4)) + '1224' AS DATE)) > 1) AND (DATEPART(dw, CAST(CAST(@Yr AS char(4)) + '1224' AS DATE)) < 6) BEGIN INSERT INTO @HolidayTable SELECT CAST(CAST(@Yr AS char(4)) + '1224' as int) , CAST(CAST(@Yr AS char(4)) + '1224' AS DATE) , 'Christmas Eve' , 0 -- IsFedHoliday , 0 -- IsBankHoliday , 1 -- IsUSACorpHoliday END IF (DATEPART(dw, CAST(CAST(@Yr AS char(4)) + '1224' AS DATE)) = 1) BEGIN INSERT INTO @HolidayTable SELECT CAST(CAST(@Yr AS char(4)) + '1226' as int) , CAST(CAST(@Yr AS char(4)) + '1226' AS DATE) , 'Christmas Eve' , 0 -- IsFedHoliday , 0 -- IsBankHoliday , 1 -- IsUSACorpHoliday END IF (DATEPART(dw, CAST(CAST(@Yr AS char(4)) + '1224' AS DATE)) > 5) BEGIN INSERT INTO @HolidayTable SELECT CAST(CAST(@Yr AS char(4)) + '1223' as int) , CAST(CAST(@Yr AS char(4)) + '1223' AS DATE) , 'Christmas Eve' , 0 -- IsFedHoliday , 0 -- IsBankHoliday , 1 -- IsUSACorpHoliday END -- Christmas Day logic IF (DATEPART(dw, CAST(CAST(@Yr AS char(4)) + '1225' AS DATE)) > 1) AND (DATEPART(dw, CAST(CAST(@Yr AS char(4)) + '1225' AS DATE)) < 7) BEGIN INSERT INTO @HolidayTable SELECT CAST(CAST(@Yr AS char(4)) + '1225' as int) , CAST(CAST(@Yr AS char(4)) + '1225' AS DATE) , 'Christmas Day' , 1 -- IsFedHoliday , 1 -- IsBankHoliday , 1 -- IsUSACorpHoliday END IF (DATEPART(dw, CAST(CAST(@Yr AS char(4)) + '1225' AS DATE)) = 1) BEGIN INSERT INTO @HolidayTable SELECT CAST(CAST(@Yr AS char(4)) + '1226' as int) , CAST(CAST(@Yr AS char(4)) + '1226' AS DATE) , 'Christmas Day' , 1 -- IsFedHoliday , 1 -- IsBankHoliday , 1 -- IsUSACorpHoliday END IF (DATEPART(dw, CAST(CAST(@Yr AS char(4)) + '1225' AS DATE)) = 7) BEGIN INSERT INTO @HolidayTable SELECT CAST(CAST(@Yr AS char(4)) + '1224' as int) , CAST(CAST(@Yr AS char(4)) + '1224' AS DATE) , 'Christmas Day' , 1 -- IsFedHoliday , 1 -- IsBankHoliday , 1 -- IsUSACorpHoliday END END SET @Yr = @Yr + 1 END DECLARE @Control_Date DATE --Current date in loop SET @Control_Date = @starting_dt WHILE @Control_Date <= @ending_dt BEGIN SET @Yr = DATEPART(yyyy, @Control_Date) INSERT INTO [DimDate] ([DateKey] , [FullDate] , [MonthNumberOfYear] , [MonthNumberOfQuarter] , [ISOYearAndWeekNumber] , [ISOWeekNumberOfYear] , [SSWeekNumberOfYear] , [ISOWeekNumberOfQuarter_454_Pattern] , [SSWeekNumberOfQuarter_454_Pattern] , [SSWeekNumberOfMonth] , [DayNumberOfYear] , [DaysSince1900] , [DayNumberOfFiscalYear] , [DayNumberOfQuarter] , [DayNumberOfMonth] , [DayNumberOfWeek_Sun_Start] , [MonthName] , [MonthNameAbbreviation] , [DayName] , [DayNameAbbreviation] , [CalendarYear] , [CalendarYearMonth] , [CalendarYearQtr] , [CalendarSemester] , [CalendarQuarter] , [FiscalYear] , [FiscalMonth] , [FiscalQuarter] , [FiscalYearMonth] , [FiscalYearQtr] , [QuarterNumber] , [YYYYMMDD] , [MM/DD/YYYY] , [YYYY/MM/DD] , [YYYY-MM-DD] , [MonDDYYYY] , [IsLastDayOfMonth] , [IsWeekday] , [IsWeekend] --, [IsWorkday] --, [IsFederalHoliday] --, [IsBankHoliday] --, [IsCompanyHoliday] ) SELECT CAST(CONVERT(char(8), @Control_Date,112) as int) AS [DateKey] , @Control_Date AS [FullDate] , DATEPART(mm, @Control_Date) AS [MonthNumberOfYear] , CASE DATEPART(mm, @Control_Date) WHEN 1 THEN 1 WHEN 2 THEN 2 WHEN 3 THEN 3 WHEN 4 THEN 1 WHEN 5 THEN 2 WHEN 6 THEN 3 WHEN 7 THEN 1 WHEN 8 THEN 2 WHEN 9 THEN 3 WHEN 10 THEN 1 WHEN 11 THEN 2 ELSE 3 END AS [MonthNumberOfQuarter] , CASE WHEN DATEPART(mm, @Control_Date) = 1 AND DATEPART(isoww, @Control_Date) > 50 THEN CAST(@Yr - 1 AS char(4)) + 'W' + RIGHT('0' + CAST(DATEPART(isoww, @Control_Date) AS varchar(2)), 2) WHEN DATEPART(mm, @Control_Date) = 12 AND DATEPART(isoww, @Control_Date) < 40 THEN CAST(@Yr + 1 AS char(4)) + 'W' + RIGHT('0' + CAST(DATEPART(isoww, @Control_Date) AS varchar(2)), 2) ELSE CAST(@Yr AS char(4)) + 'W' + RIGHT('0' + CAST(DATEPART(isoww, @Control_Date) AS varchar(2)), 2) END AS [ISOYearAndWeekNumber] , DATEPART(isoww, @Control_Date) AS [ISOWeekNumberOfYear] , DATEPART(wk, @Control_Date) AS [SSWeekNumberOfYear] , CASE WHEN DATEPART(isoww, @Control_Date) < 14 THEN DATEPART(isoww, @Control_Date) WHEN DATEPART(isoww, @Control_Date) > 13 AND DATEPART(isoww, @Control_Date) < 27 THEN DATEPART(isoww, @Control_Date) - 13 WHEN DATEPART(isoww, @Control_Date) > 26 AND DATEPART(isoww, @Control_Date) < 40 THEN DATEPART(isoww, @Control_Date) - 26 ELSE DATEPART(isoww, @Control_Date) - 39 END AS [ISOWeekNumberOfQuarter_454_Pattern] , CASE WHEN DATEPART(wk, @Control_Date) < 14 THEN DATEPART(wk, @Control_Date) WHEN DATEPART(wk, @Control_Date) > 13 AND DATEPART(wk, @Control_Date) < 27 THEN DATEPART(wk, @Control_Date) - 13 WHEN DATEPART(wk, @Control_Date) > 26 AND DATEPART(wk, @Control_Date) < 40 THEN DATEPART(wk, @Control_Date) - 26 ELSE DATEPART(wk, @Control_Date) - 39 END AS [SSWeekNumberOfQuarter_454_Pattern] , DATEDIFF(wk, DATEADD(mm, DATEDIFF(mm, 0, @Control_Date), 0), @Control_Date) + 1 AS [SSWeekNumberOfMonth] , DATEPART(dy, @Control_Date) AS [DayNumberOfYear] , DATEDIFF(dd, '18991231', @Control_Date) AS [DaysSince1900] , CASE -- 0ffset < 0 and start of fy < current year WHEN YEAR(DATEADD(mm, @FiscalYearMonthsOffset, @Control_Date)) < @Yr AND @FiscalYearMonthsOffset < 0 THEN DATEPART(dy, @Control_Date) + DATEPART(dy, -- Last day of previous year CAST(CAST(YEAR(DATEADD(mm, @FiscalYearMonthsOffset, @Control_Date)) AS CHAR(4)) + '1231' AS DATETIME)) - DATEPART(dy, -- Start date of Fiscal year DATEADD(mm, 1, CAST(CAST(CAST(YEAR(DATEADD(mm, @FiscalYearMonthsOffset, @Control_Date)) AS char(4)) + RIGHT('00' + CAST(@FiscalYearMonthsOffset * -1 AS varchar(2)), 2) + '01' AS char(8)) AS DATETIME)) - 1) -- 0ffset > 0 and start of fy < current year WHEN YEAR(DATEADD(mm, @FiscalYearMonthsOffset, @Control_Date)) - 1 < @Yr AND @FiscalYearMonthsOffset > 0 THEN DATEPART(dy, @Control_Date) + DATEPART(dy, -- Last day of previous year CAST(CAST(YEAR(DATEADD(mm, @FiscalYearMonthsOffset, @Control_Date)) - 1 AS CHAR(4)) + '1231' AS DATETIME)) - DATEPART(dy, -- Start date of Fiscal year CAST(CAST(CAST(YEAR(DATEADD(mm, @FiscalYearMonthsOffset, @Control_Date)) - 1 AS char(4)) + RIGHT('00' + CAST(13 - @FiscalYearMonthsOffset AS varchar(2)), 2) + '01' AS char(8)) AS DATETIME) - 1) -- 0ffset < 0 and start of fy = current year WHEN YEAR(DATEADD(mm, @FiscalYearMonthsOffset, @Control_Date)) = @Yr AND @FiscalYearMonthsOffset < 0 THEN DATEPART(dy, @Control_Date) - DATEPART(dy, -- Start date of Fiscal year DATEADD(mm, 1, CAST(CAST(CAST(YEAR(DATEADD(mm, @FiscalYearMonthsOffset, @Control_Date)) AS char(4)) + RIGHT('00' + CAST(@FiscalYearMonthsOffset * -1 AS varchar(2)), 2) + '01' AS char(8)) AS DATETIME)) - 1) -- 0ffset > 0 and start of fy = current year WHEN YEAR(DATEADD(mm, @FiscalYearMonthsOffset, @Control_Date)) - 1 = @Yr AND @FiscalYearMonthsOffset > 0 THEN DATEPART(dy, @Control_Date) - DATEPART(dy, -- Start date of Fiscal year CAST(CAST(CAST(YEAR(DATEADD(mm, @FiscalYearMonthsOffset, @Control_Date)) - 1 AS char(4)) + RIGHT('00' + CAST(13 - @FiscalYearMonthsOffset AS varchar(2)), 2) + '01' AS char(8)) AS DATETIME) - 1) ELSE DATEPART(dy, @Control_Date) END AS [DayNumberOfFiscalYear] , CASE WHEN DATEPART(mm, @Control_Date) = 1 OR DATEPART(mm, @Control_Date) = 4 OR DATEPART(mm, @Control_Date) = 7 OR DATEPART(mm, @Control_Date) = 10 THEN DATEPART(day, @Control_Date) WHEN DATEPART(mm, @Control_Date) = 2 OR DATEPART(mm, @Control_Date) = 5 OR DATEPART(mm, @Control_Date) = 8 OR DATEPART(mm, @Control_Date) = 11 THEN DATEPART(day, @Control_Date) + DAY(DATEADD (m, 1, DATEADD (d, 1 - DAY(CAST(CAST(@Yr AS char(4)) + RIGHT('0' + CAST(DATEPART(mm, @Control_Date) - 1 AS varchar(2)), 2) + '01' AS DATE)) , CAST(CAST(@Yr AS char(4)) + RIGHT('0' + CAST(DATEPART(mm, @Control_Date) - 1 AS varchar(2)), 2) + '01' AS DATETIME))) - 1) WHEN DATEPART(mm, @Control_Date) = 3 OR DATEPART(mm, @Control_Date) = 6 OR DATEPART(mm, @Control_Date) = 9 OR DATEPART(mm, @Control_Date) = 12 THEN DATEPART(day, @Control_Date) + DAY(DATEADD (m, 1, DATEADD (d, 1 - DAY(CAST(CAST(@Yr AS char(4)) + RIGHT('0' + CAST(DATEPART(mm, @Control_Date) - 1 AS varchar(2)), 2) + '01' AS DATE)) , CAST(CAST(@Yr AS char(4)) + RIGHT('0' + CAST(DATEPART(mm, @Control_Date) - 1 AS varchar(2)), 2) + '01' AS DATETIME))) - 1) + DAY(DATEADD (m, 1, DATEADD (d, 1 - DAY(CAST(CAST(@Yr AS char(4)) + RIGHT('0' + CAST(DATEPART(mm, @Control_Date) - 2 AS varchar(2)), 2) + '01' AS DATE)) , CAST(CAST(@Yr AS char(4)) + RIGHT('0' + CAST(DATEPART(mm, @Control_Date) - 2 AS varchar(2)), 2) + '01' AS DATETIME))) - 1) END AS [DayNumberOfQuarter] , DATEPART(day, @Control_Date) AS [DayNumberOfMonth] , DATEPART(dw, @Control_Date) AS [DayNumberOfWeek_Sun_Start] , DATENAME(month, @Control_Date) AS [MonthName] , LEFT(DATENAME(month, @Control_Date), 3) AS [MonthNameAbbreviation] , DATENAME(weekday, @Control_Date) AS [DayName] , LEFT(DATENAME(weekday, @Control_Date), 3) AS [DayNameAbbreviation] , @Yr AS [CalendarYear] , CONVERT(varchar(7), @Control_Date, 126) AS [CalendarYearMonth] , CAST(@Yr AS char(4)) + '-' + RIGHT('0' + CAST(DATEPART(qq, @Control_Date) AS char(1)), 2) AS [CalendarYearQuarter] , CASE (DATEPART(mm, @Control_Date)) WHEN 1 THEN 1 WHEN 2 THEN 1 WHEN 3 THEN 1 WHEN 4 THEN 1 WHEN 5 THEN 1 WHEN 6 THEN 1 ELSE 2 END AS [CalendarSemester] , DATEPART(qq, @Control_Date) AS [CalendarQuarter] , DATEPART(yyyy, DATEADD(mm, @FiscalYearMonthsOffset, @Control_Date)) AS [FiscalYear] , DATEPART(mm, DATEADD(mm, @FiscalYearMonthsOffset, @Control_Date)) AS [FiscalMonth] , DATEPART(qq, DATEADD(mm, @FiscalYearMonthsOffset, @Control_Date)) AS [FiscalQuarter] , CAST(DATEPART(yyyy, DATEADD(mm, @FiscalYearMonthsOffset, @Control_Date)) AS char(4)) + '-' + RIGHT('0' + CAST(DATEPART(mm, DATEADD(mm, @FiscalYearMonthsOffset, @Control_Date)) AS varchar(2)), 2) AS [FiscalYearMonth] , CAST(DATEPART(yyyy, DATEADD(mm, @FiscalYearMonthsOffset, @Control_Date)) AS char(4)) + 'Q' + RIGHT('0' + CAST(DATEPART(qq, DATEADD(mm, @FiscalYearMonthsOffset, @Control_Date)) AS varchar(2)), 2) AS [FiscalYearQtr] , CASE WHEN @Control_Date >= '19000101' THEN ((@Yr - 1900) * 4) + DATEPART(qq, @Control_Date) ELSE ((@Yr - 1900) * 4) - (5 - DATEPART(qq, @Control_Date)) END AS [QuarterNumber] , CONVERT(varchar(8), @Control_Date, 112) AS [YYYYMMDD] , CONVERT(varchar(10), @Control_Date, 101) AS [MM/DD/YYYY] , CONVERT(varchar(10), @Control_Date, 111) AS [YYYY/MM/DD] , REPLACE(CONVERT(varchar(10), @Control_Date, 111), '/', '-') AS [YYYY-MM-DD] , LEFT(DATENAME(month, @Control_Date), 3) + ' ' + RIGHT('0' + CAST(DATEPART(dd, @Control_Date) AS varchar(2)), 2) + ' ' + CAST(@Yr AS CHAR(4)) AS [MonDDYYYY] , CASE WHEN @Control_Date = DATEADD(d, -day(DATEADD(mm, 1, @Control_Date)), DATEADD(mm, 1, @Control_Date)) THEN 'Y' ELSE 'N' END AS [IsLastDayOfMonth] , CASE DATEPART(dw, @Control_Date) WHEN 1 THEN 'N' WHEN 7 THEN 'N' ELSE 'Y' END AS [IsWeekday] , CASE DATEPART(dw, @Control_Date) WHEN 1 THEN 'Y' WHEN 7 THEN 'Y' ELSE 'N' END AS [IsWeekend] SET @Control_Date = DATEADD(dd, 1, @Control_Date) END UPDATE dateTbl SET dateTbl.[IsWorkday] = CASE WHEN dateTbl.IsWeekday = 'Y' AND ISNULL(holTbl.IsUSACorpHoliday, 0) = 0 THEN 'Y' ELSE 'N' END , dateTbl.[IsFederalHoliday] = CASE WHEN ISNULL(holTbl.IsFedHoliday, 0) = 1 THEN 'Y' ELSE 'N' END , dateTbl.[IsBankHoliday] = CASE WHEN ISNULL(holTbl.IsBankHoliday, 0) = 1 THEN 'Y' ELSE 'N' END , dateTbl.[IsCompanyHoliday] = CASE WHEN ISNULL(holTbl.IsUSACorpHoliday, 0) = 1 THEN 'Y' ELSE 'N' END FROM dbo.[DimDate] dateTbl LEFT OUTER JOIN @HolidayTable holTbl ON dateTbl.[DateKey] = holTbl.HolidayKey WHERE DateKey BETWEEN CAST(CONVERT(varchar(8), @starting_dt, 112) AS int) AND CAST(CONVERT(varchar(8), @ending_dt, 112) AS int) GO |
With any dimension table we create, we should take time to document the data being captured. Here is the description of the data in the DimDate table.
Column Name | Notes |
---|---|
DateKey | This is our primary key, and represents each day. It is an integer in the format of YYYYMMDD. It allows efficient retrieval and JOINs and will allow us to use BETWEEN. |
FullDate | The date is stored as a DATE data type. |
MonthNumberOfYear | The numeric representation of the calendar month. |
MonthNumberOfQuarter | A value between 1 and 3 to represent the sequence of the month within the quarter. |
ISOYearAndWeekNumber | The year and ISO week number in the format of yyyyWnn where yyyy represents the year and nn represents the ISO week number. |
ISOWeekNumberOfYear | Represents the ISO week number. The ISO week date system is a calendar system that is part of the ISO 8601 date and time standard. Weeks start with Monday. The first week of a year is the week that contains the first Thursday of the year. |
SSWeekNumberOfYear | The SQL Server week number. Jan 1 is the first week of the year. After that, every Sunday (assuming DATEFIRST is set to 7) increments the week number by 1. (With this logic, there will be instances when Jan 31 has a week number of 6.) |
ISOWeekNumberOfQuarter_454_Pattern | Based on the concept that each quarter has 13 weeks, and first month of the quarter has 4 weeks, the next month, 5 weeks, and the last month 4 weeks, the weeks within the quarter are numbered accordingly based on the ISO week number. |
SSWeekNumberOfQuarter_454_Pattern | Based on the concept that each quarter has 13 weeks, and first month of the quarter has 4 weeks, the next month, 5 weeks, and the last month 4 weeks, the weeks within the quarter are numbered accordingly based on the SQL Server week number. |
SSWeekNumberOfMonth | The week number within the month as calculated by SQL Server. |
DayNumberOfYear | The day number of the calendar year. Jan 1 is the first day of the year. |
DayNumberOfFiscalYear | The day number of the fiscal year. |
DayNumberOfQuarter | The day number of the quarter. |
DayNumberOfMonth | The day number of the month. |
DayNumberOfWeek_Sun_Start | The day number of the week, where Sunday will have a value of 1 (assuming DATEFIRST is set to 7.) |
MonthName | The name of the month. |
MonthNameAbbreviation | The 3-character abbreviation of the month name. |
DayName | The name of the day. |
DayNameAbbreviation | The 3-character abbreviation of the day name. |
CalendarYear | The calendar year. |
CalendarYearMonth | The calendar year and month in the format yyyy-mm. |
CalendarYearQtr | The calendar year and quarter in the format yyyy-nn, where nn represents the number of the quarter. |
CalendarSemester | A value of 1 or 2 to represent the calendar semester. |
CalendarQuarter | A value between 1 and 4 to represent the calendar quarter. |
FiscalYear | The fiscal year. |
FiscalMonth | The numeric representation of the fiscal month. |
FiscalQuarter | A value between 1 and 4 to represent the fiscal quarter. |
FiscalYearMonth | The fiscal year and month in the format yyyy-mm. |
FiscalYearQtr | The fiscal year and quarter in the format yyyy-nn, where nn represents number of the fiscal quarter |
QuarterNumber | The number of the quarter where 1900-01-01 has a value of 1 and 2000-01-01 has a value of 401 |
YYYYMMDD | The calendar date in the format of YYYYMMDD. |
MM/DD/YYYY | The calendar date in the format of MM/DD/YYYY. |
YYYY/MM/DD | The calendar date in the format of YYYY/MM/DD. |
YYYY-MM-DD | The calendar date in the format of YYYY-MM-DD. |
MonDDYYYY | The calendar date in the format of mmm dd yyyy. |
IsLastDayOfMonth | A value of Y or N to indicate if a date is the last day of the month |
IsWeekday | A value of Y or N to indicate if a date falls on a weekday |
IsWeekend | A value of Y or N to indicate if a date falls on a weekend |
IsWorkday | A value of Y or N to indicate if a date is a workday |
IsFederalHoliday | A value of Y or N to indicate if a date is a Federal Government holiday |
IsBankHoliday | A value of Y or N to indicate if a date is a bank holiday |
IsCompanyHoliday | A value of Y or N to indicate if a date is a company holiday |