I find that it can be advantageous to use a Dates table, especially when doing a lot of date calculations. Learn what a Dates table is and how to create one, and then try it out for yourself.
What is a Dates table?
A Dates table stores a range of dates. Dates tables are very common in a DateWarehouse as a dimension table. You can also use Dates tables in OLTP databases for lookups. When programmers use Dates tables, they don't have to worry about using or designing functions for handling or formatting dates in the database. It is a precompilation of a wide range of date values and their associated month, quarter, year, etc.
Creating a Dates table
It's simple to create a Dates table -- it only takes a little TSQL programming. The script below creates the DateLookup table, which I will use throughout the rest of the example.
CREATE TABLE DateLookup
(
DateKey INT PRIMARY KEY,
DateFull DATETIME,
CharacterDate VARCHAR(10),
FullYear CHAR(4),
QuarterNumber TINYINT,
WeekNumber TINYINT,
WeekDayName VARCHAR(10),
MonthDay TINYINT,
MonthName VARCHAR(12),
YearDay SMALLINT,
DateDefinition VARCHAR(30),
WeekDay TINYINT,
MonthNumber TINYINT
)
As you can see from the field names, the table contains detailed information regarding parts of a date, such as the name of the month, name of the day of the weekend, the quarter number, etc. It's very useful to have this information stored in a table for date searches based on certain months, quarters, and similar information.
The script below populates my DateLookup table with date information from the year 1900 through the end of 2100. I enter this large range of dates because I am not sure what type of dates I am going to handle in my tables, so I like to have a wide range available. This range likely won't cover erroneous dates in my tables, but it should do a pretty good job covering a large percentage of them.
DECLARE @Date DATETIME
SET @Date = \'1/1/1900\'
WHILE @Date < \'1/1/2100\'
BEGIN
INSERT INTO DateLookup
(
DateKey, DateFull, FullYear,
QuarterNumber, WeekNumber, WeekDayName,
MonthDay, MonthName, YearDay,
DateDefinition,
CharacterDate,
WeekDay,
MonthNumber
)
SELECT
CONVERT(VARCHAR(8), @Date, 112), @Date, YEAR(@Date),
DATEPART(qq, @Date), DATEPART(ww, @Date), DATENAME(dw, @Date),
DATEPART(dd, @Date), DATENAME(mm, @Date), DATEPART(dy,@Date),
DATENAME(mm, @Date) + \' \' + CAST(DATEPART(dd, @Date) AS CHAR(2)) + \',
\' + CAST(DATEPART(yy, @Date) AS CHAR(4)),
CONVERT(VARCHAR(10), @Date, 101),
DATEPART(dw, @Date),
DATEPART(mm, @Date)
SET @Date = DATEADD(dd, 1, @Date)
END
Using the DateLookup table
Once I load data into my DateLookup table, I can run queries against it. For example, the following query lists the number of Wednesdays in the year 2003:
SELECT WeekDayName, DayCount = COUNT(*)
FROM DateLookup
WHERE FullYear = 2003 AND
WeekDayName = \'Wednesday\'
GROUP BY WeekDayName
There were 52 Wednesdays in 2003.
The real power of using a Dates table comes when you use the table in conjunction with other tables. In the following example, I use my DateLookup table and join it to my SalesHistory table (from my article about generating dynamic SQL statements in SQL Server on the SaleDate. Performing this join makes it easier for me to analyse my sales information based on the date the sale occurred.
The following query ranks the month with the highest sales per product line:
SELECT * FROM ( SELECT dd.MonthName, Product, RecordCount = COUNT(*), Ranking = DENSE_RANK() OVER ( PARTITION BY Product ORDER BY COUNT(*) DESC, NEWID() DESC ) FROM DateLookup dd JOIN SalesHistory s ON dd.DateKey = CONVERT(VARCHAR(8), SaleDate, 112) GROUP BY dd.MonthName, Product ) a WHERE Ranking = 1This query ranks product sales per quarter per year:
SELECT FullYear, QuarterNumber, Product, SaleCount = COUNT(*) FROM DateLookup dd JOIN SalesHistory s ON dd.DateKey = CONVERT(VARCHAR(8), SaleDate, 112) GROUP BY FullYear, QuarterNumber, Product ORDER BY FullYear, QuarterNumberWithout the DateLookup table, this query would have to be written like this, which I think is more difficult:
SELECT DATEPART(yy, SaleDate), DATEPART(qq, SaleDate), Product, SaleCount = COUNT(*) FROM SalesHistory s GROUP BY DATEPART(yy, SaleDate), DATEPART(qq, SaleDate), Product ORDER BY DATEPART(yy, SaleDate), DATEPART(qq, SaleDate)
Drawback
The main drawback to using the Dates table is with the way in which I have to join it back to my OLTP tables. My Dates table does not contain time data, and my OLTP data almost certainly will; this means that joining the two tables requires some conversion on my part to do in the join. While this conversion in the join is not necessarily a huge deal, it may lead to poor performance under certain conditions.




1
Jack Corbett - 06/05/08
You could fix your conversion issue by storing the date start 1/1/1900 00:00:00 and a date end 1/2/1900 00:00:00 and then you could use >= and < in your join.
Also with the new date and time data types in SQL 2008 you could include start time and end time columns
» Report offensive content
2
Kent Butler - 06/05/08
A great advantage of this however, which is not explicitly stated, is that this can be applied to existing tables for the endless reporting scenarios. Great idea.
I wonder how the performance of this technique compares to the re-calculation of DATEPARTs over and over, which I would guess a lot of us are doing. Have you measured this?
» Report offensive content
3
Wayne - 07/05/08
Interesting. I had to do something recently where I needed the number of days elapsed between two dates but I had to exclude weekend days and holidays. It made it a bit of a challenge, ended up using a UDF with a date table. I also had to include the time component in the comparison, which was an additional level of difficulty.
I'll have to load this up and play with it, I might be able to simplify my process. I can see the benefit in a DW, but we're strictly OLTP right now. Still, it'll be interesting to experiment with.
» Report offensive content
4
Wayne - 07/05/08
You might also consider a "numbers" table, you can go from a 77,000 iteration loop doing one insert per pass to a single insert statement.
» Report offensive content
5
James Horsley - 07/05/08
Your "no time" drawback could be helped bay having a NextDateFull datetime column as well (being 1 day after your date - so if date is 1 May 2008 with time 00:00:00.000 NextDateFull is 2 May 2008 with time 00:00:00.000 you can then join on (OtherTableDateCol>=DateFull AND OtherTableDateCol<NextDateFull) and things will fall into correct row regardless of whether or not there are times on them
» Report offensive content
6
James Horsley - 07/05/08
Sorry - should have read other comments before posting mine - Jack has already suggested much the same thing
» Report offensive content
7
James Horsley - 07/05/08
Another idea - add a bit field IsHoliday that can be set true on all public holidays for working day counts and maybe a varchar HolidayName field too
» Report offensive content
8
Mark - 07/05/08
The Public Holiday issue is a complex one. Because not only do different countries have different holidays, different states also have different holidays. You really need a separate table for holidays, because you can't even rely on timezones to sort that mess out.
» Report offensive content
9
Wayne - 08/05/08
Public holidays is definitely a complex one. For my elapsed days function I have an insert statement for the fixed-date American holidays: 12/25, 1/1, 11/11, 7/4; and if they fall on a weekend, well that record is already there and they're not inserted. The woman who uses the DB knows that after city council approves next year's holidays, she needs to make sure that they are added to the table.
If you had to take into account other countries, I'd go for a table consisting of date, holiday name, and ISO country code. Have you ever added the Japanese or Hebrew holidays to Outlook? It's a heck of a list. You'd definitely need knowledge of the nation to know which are actual holidays and which are observances/non-holidays.
» Report offensive content
10
ABIRLAL SEN - 08/05/08
HOW TO WRITE TRIGGER IN SQL SERVER 2005?
» Report offensive content
11
Ulrich Boeck - 09/05/08
yes... but:
isn't it a big deal to mimic dwh behaviour on an oltp database with something like a fact table?.
In order to get your grouping that way you have to
- populate the dates into the dates table ("who")
- establish a join with your "fact" table with a convert(varchar.. and implicitely a cast to int
i have the feeling that this does not perform well on huge data amounts
» Report offensive content
12
Payur - 24/05/08
I am getting the following error when I execute the second script that is DECLARE @Date DATETIME
SET @Date = \'1/1/1900\' :
This is on SQL 2005.
Msg 102, Level 15, State 1, Line 2
Incorrect syntax near '1/1/1900\'.
Please advice
» Report offensive content
13
HanShi - 23/06/08
@Payur
Remove both backslashes (\) and it'll work
» Report offensive content