Breaking apart the DateTime datatype – Separating Dates from Times in your Tables
Update: More on dates and times in T-SQL here.
I don't like storing dates along with the time portion in my database tables.
Sure, I use the datatype, but I wish SQL provided a “Date” datatype and a “Time” datatype that were separate. It really sometimes requires too much work in T-SQL to separate the date portion from the time portion, and often in a WHERE clasue you don't care about the time, you just want data for a single day. A common question asked at SQLTeam is:
Why doesn't the condition WHERE DateTimeColumn = '1/1/2004' return any results? I can see there is definitely data in that table for Jan 1st, 2004 !
The answer, of course, is because “DateTimeColumn” contains a time portion as well. Some solutions that are given often look like this:
A) WHERE DateDiff(dd, DateTimeColumn, '1/1/2004') = 0
B) WHERE Convert(varchar(20), DateTimeColumn, 101) = '01/01/2004'
C) WHERE DateTimeColumn LIKE '1/1/2004%'
D) WHERE Year(DateTimeColumn) = 2004 AND Month(DateTimeColumn) = 1 and Day(DateTimeColumn)=1
I don't really like any of those, to be honest. Many rely on system settings for date formats. Others rely on conversions to varchars. None are able to make use of any indexes on the DateTimeColumn so they will not be as efficient as possible.
The best solution in terms of performance isn't even really that good, because it requires a range even though we wish to return data just for a particular day:
E) WHERE DateTimeColumn >= '1/1/2004' AND DateTimeColumn < '1/2/2004'
If you encounter this situation often, here's an idea:
Don't store the time with the date in your database! Keep them in separate columns. You can always combine them extremely easily just by adding them together to recreate your original DateTime value. But for all those other times when you wish to just filter for a particular day, you can use a quick and efficient = or BETWEEN comparison on the date only.
To do this, you need to :
1) Create two columns instead of one -- a Date column and a Time column
2) Enforce that the Date column never has a time stored in it:
DateCol datetime check (dateAdd(dd,datediff(dd,0,DateCol),0) = DateCol)
3) Enforce that the Time column never has a date portion that is a date other than the “base date” (which is the datetime that the numeric value 0 converts to):
TimeCol datetime check (datediff(dd,0,TimeCol) = 0))
Once that is done, any time you wish to return the Date along with the Time, you can just add them together! That's right, just SELECT DateCol + TimeCol as DateTimeVal FROM YourTable.
The key here is now you can index your DateCol and filter off of it directly, without worrying about conversions or time issues, which results in efficient execution plans. With the constraints, you are guaranteed that only dates are stored there, so you never have to worry about it. And, you can effortlessly retrieve the original DateTime value with a simple addition of the two columns (no conversions needed!).
If you like, you can create a View of your table and have that view return your DateTime column as well as the Date and the Time broken out if you like. You can even allow for this View to be updateable so that client applications don't have to worry about manually breaking out the Date and the Time themselves. See the code below for an example of this trigger, along with the constraints and some sample data.
-- Here's our sample table; just an ID column with a Date and a Time.
-- Note the CHECK constraints on the datetime columns:
create table Test_table
(ID int primary key,
TransDate datetime
check (dateAdd(dd,datediff(dd,0,TransDate),0) = TransDate),
TransTime datetime
check (datediff(dd,0,TransTime) = 0)
)
go
-- Here's our view of the table which presents a DateTime column
-- that looks “normal“, along with the date and the time broken out
create view Test_View as
select ID,
TransDate,
TransTime,
TransDate + TransTime as TransDateTime
from
Test_Table
go
-- Here's our trigger for when rows are added to the view;
-- you can do a similiar thing for an UPDATE trigger as well.
create trigger test_view_ins on Test_View
instead of insert
as
begin
insert into
Test_table (ID, TransDate, TransTime)
select
ID, JustDate, TransDateTime - JustDate
from
(
select ID, dateAdd(dd,datediff(dd,0,TransDateTime),0) as JustDate,
TransDateTime
from
inserted
) a
end
go
-- Insert some sample data using the View; the trigger
-- will break out the date from the time:
insert into Test_View (ID, TransDateTime)
select 1, getdate() union
select 2, '12/19/1972 10:00 AM' union
select 3, getDate() - 1.1 union
select 4, getdate() + 2.2
-- Let's see what we've got:
select * from Test_View
select * from test_Table
-- Clean it up:
go
drop trigger test_view_ins
drop view Test_View
drop table Test_Table
A couple of notes:
1) This does, of course, require more memory since you now require twice as much storage for your DateTime values.
2) If, on occasion, you do wish to SELECT from this table and filter by a specific date and time range, then the view will not be especially efficient if you simply use:
WHERE DateTimeCol BETWEEN @StartDateTime AND @EndDateTime
This is because now DateTimeCol is now a formula. You can either make use of Indexed Views to index this column, or you can use what I call an efficiently redundant WHERE clause like this:
WHERE DateCol BETWEEN (@StartDateTime-1) AND (@EndDateTime+1) AND
DateTimeCol BETWEEN @StartDateTime AND @EndDateTime
That will allow SQL Server to use the index on the DateCol for the initial filter, and then from there, the second part of the WHERE clause will have many fewer rows to scan.
see also:
- Working with Time Spans and Durations in SQL Server
- Group by Month (and other time periods)
- Working with Date and/or Time values in SQL Server: Don't Format, Don't Convert -- just use DATETIME
- Data Types -- The Easiest Part of Database Design
- How to format a Date or DateTime in SQL Server
- Breaking apart the DateTime datatype -- Separating Dates from Times in your Tables
- Date Only and Time Only data types in SQL Server 2005 (without the CLR)
- Essential SQL Server Date, Time and DateTime Functions
Legacy Comments
Brett
2004-12-03 |
re: Breaking apart the DateTime datatype -- Separating Dates from Times in your Tables Well I don't see date in Express beta 2...damn I betcha it was too much work for them to rewrite all the functions |
Adam Machanic
2004-12-03 |
re: Breaking apart the DateTime datatype -- Separating Dates from Times in your Tables The DATE and TIME datatypes were removed from 2005 due to some implementation problems. As I understand it, the issue was that MS decided to implement them as CLR UDTs (behind the scenes) and had a bunch of issues making them fully compatible with the various datetime functions (DATEADD/DATEDIFF/etc), so they dropped them altogether from the spec. I'm sure you could still write your own UDTs to implement it, but I question the real value of such an implementation. |
David Cook
2004-12-06 |
re: Breaking apart the DateTime datatype -- Separating Dates from Times in your Tables Thanks, Jeff. Very useful stuff! I was just pondering this very problem this morning. |
Shishir Gupta
2007-09-27 |
re: Breaking apart the DateTime datatype -- Separating Dates from Times in your Tables This seems to be an outdated article. Regards Shishir Gupta http://www.naukrisalah.com |
Manny
2008-11-05 |
re: Breaking apart the DateTime datatype -- Separating Dates from Times in your Tables This is great! I've have now the date and time columns been seperated. Now my other challenge is how can I convert them (currenty GMT) into an EST? Any comments and or help is definitely appreciated. Thanks, manny |
Paul TN
2010-07-30 |
re: Breaking apart the DateTime datatype -- Separating Dates from Times in your Tables Your links have /jeffs/jeffs/ inserted into the URL instead of just one /jeffs/. just fyi |