Quantcast
Channel: BI Bits.co
Viewing all articles
Browse latest Browse all 15

DimDate - The Date Dimension

$
0
0

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 NameNotes
DateKeyThis 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.
FullDateThe date is stored as a DATE data type.
MonthNumberOfYearThe numeric representation of the calendar month.
MonthNumberOfQuarterA value between 1 and 3 to represent the sequence of the month within the quarter.
ISOYearAndWeekNumberThe year and ISO week number in the format of yyyyWnn where yyyy represents the year and nn represents the ISO week number.
ISOWeekNumberOfYearRepresents 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.
SSWeekNumberOfYearThe 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_PatternBased 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_PatternBased 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.
SSWeekNumberOfMonthThe week number within the month as calculated by SQL Server.
DayNumberOfYearThe day number of the calendar year. Jan 1 is the first day of the year.
DayNumberOfFiscalYearThe day number of the fiscal year.
DayNumberOfQuarterThe day number of the quarter.
DayNumberOfMonthThe day number of the month.
DayNumberOfWeek_Sun_StartThe day number of the week, where Sunday will have a value of 1 (assuming DATEFIRST is set to 7.)
MonthNameThe name of the month.
MonthNameAbbreviationThe 3-character abbreviation of the month name.
DayNameThe name of the day.
DayNameAbbreviationThe 3-character abbreviation of the day name.
CalendarYearThe calendar year.
CalendarYearMonthThe calendar year and month in the format yyyy-mm.
CalendarYearQtrThe calendar year and quarter in the format yyyy-nn, where nn represents the number of the quarter.
CalendarSemesterA value of 1 or 2 to represent the calendar semester.
CalendarQuarterA value between 1 and 4 to represent the calendar quarter.
FiscalYearThe fiscal year.
FiscalMonthThe numeric representation of the fiscal month.
FiscalQuarterA value between 1 and 4 to represent the fiscal quarter.
FiscalYearMonthThe fiscal year and month in the format yyyy-mm.
FiscalYearQtrThe fiscal year and quarter in the format yyyy-nn, where nn represents number of the fiscal quarter
QuarterNumberThe number of the quarter where 1900-01-01 has a value of 1 and 2000-01-01 has a value of 401
YYYYMMDDThe calendar date in the format of YYYYMMDD.
MM/DD/YYYYThe calendar date in the format of MM/DD/YYYY.
YYYY/MM/DDThe calendar date in the format of YYYY/MM/DD.
YYYY-MM-DDThe calendar date in the format of YYYY-MM-DD.
MonDDYYYYThe calendar date in the format of mmm dd yyyy.
IsLastDayOfMonthA value of Y or N to indicate if a date is the last day of the month
IsWeekdayA value of Y or N to indicate if a date falls on a weekday
IsWeekendA value of Y or N to indicate if a date falls on a weekend
IsWorkdayA value of Y or N to indicate if a date is a workday
IsFederalHolidayA value of Y or N to indicate if a date is a Federal Government holiday
IsBankHolidayA value of Y or N to indicate if a date is a bank holiday
IsCompanyHolidayA value of Y or N to indicate if a date is a company holiday

Viewing all articles
Browse latest Browse all 15

Trending Articles