Using DateDiff with SQL Server
Working with DateDiff In SQL Server
This article is going to discuss the SQL Server DateDiff function and how to use them to find records that fall within a certain date range.
I get lots of questions about dates in SQL Server. One common type of question centers around trying to find dates that fall within a date range.
These questions usually stem from a lack of understanding about how SQL Server actually stores DateTime data.
Data Types
To take a quick step back, there are actually 7 date/time focused data types that Microsoft has implemented in SQL Server 2017. Those can be found here.DateTime is deprecated as of 2008 and current and future work should use DateTime2.
DateTime2 is described here . It has a enormous range going from January 1, 0001 (1 CE) to December 31, 9999. Dates coming in BCE will be discussed in a future article.
SmallDateTime is described here. Its range is quite a bit smaller. It ranges from 1/1/1900 to 06/06/2079. While this range is probably enough for most applications, I encourage you to think positive, assume your work is going to be around for another 60 years and choose datetime2 instead.
Datetime2 uses between 6 and 8 bytes of storage while SmallDateTime uses 4 bytes. I don't consider this difference in storage to be significant, but your data model and hardware requirements may lead you to a different conclusion.
With Datetime, Datetime2, and SmallDateTime, the date component is stored on the left of the decimal while the time component is stored to the right of the decimal.
My query won't return records in a date range...
So one of the common questions I get is that the following query does not return the expected values
Select * from RFQ where RequestDate = '4/5/2018'
"Greg, " they say. I know for a fact that we had RFQs come in yesterday. Look, I can order them by RequestDate
Id CompanyName RequestDate RequestDescription
1 Able Enterprises 2018-02-25 12:25:32.077 Please design a widget
2 Baker Enterprises 2018-03-10 12:25:32.077 Please manufacture 100 dongles
3 Charter Enterprises 2018-03-23 12:25:32.077 Please build a mouse trap
4 Drake Enterprises 2018-04-05 12:25:32.077 We need a flying boat
The reason is very simple. The request for the flying boat did not come in at midnight, so the RequestDate value is not EQUAL to '4/5/2018'
Usually the query is expressed in terms of a date range, but you get the idea.
DateDiff to the rescue
The DateDiff function was written to handle exactly this problem. The full documentation for the DateDiff function can be found here. In essence, the date diff function serves to help you do Add/Subtract math on dates. You have a variety of units of time that you can concern yourself
The syntax for a call to DateDiff is this
DateDiff( datepart , startDate, endDate )
For example
select DateDiff( d , '01/01/2018', '01/31/2018') as DayDiff
The datepart parameter is a bit odd. It looks like a string , but you do not enclose it in single ticks.
As you can see, they pretty much have this one covered.
| year | yy, yyyy |
| quarter | qq, q |
| month | mm, m |
| dayofyear | dy, y |
| day | dd, d |
| week | wk, ww |
| hour | hh |
| minute | mi, n |
| second | ss, s |
| millisecond | ms |
| microsecond | mcs |
| nanosecond | ns |
The second and third parameters have to be valid dates. They can either be variables, or they can be string constants that represent dates, as in my example above.
The best way to keep things straight in your head is to remember that if the first date comes before the second date, then the resulting value will be positive.
Conversely, if the first date comes after the second date , then the result will be negative.
Returning to our example
select DateDiff( d , '01/01/2018', '01/31/2018') as DayDiff
will yield the number 30. While
select DateDiff( d , '01/01/2018', '01/31/2018') as DayDiff
will yield the number -30.
Why 30 and not 31? DateDiff counts the boundaries that are crossed. Since we have asked for days (d) , the function will consider moving past midnight as 1 day. If we were to change the query so the second date included a time of 5pm, it would still return a 30 because we have not increased the number of midnights that transpire between the two bookmark times.
select DateDiff( d , '01/01/2018', '01/31/2018 17:00:00') as DayDiff
Probably a better way to demonstrate this is to look at some examples with year as the unit of time
NOTE: Don't fall for the mistake of thinking 'y' indicates year. It indicates Julian Date.
select DateDiff(yy, '12/30/2018', '12/31/2018') --returns 0 because they are both in the same year
select DateDiff(yy, '12/31/2018', '1/1/2019') -- returns 1 because we have crossed a new year
select DateDiff(yy, '12/31/2018', '12/31/2019') -- returns 1. We have only crossed 1 new year
Datediff'ing seems like a trivial problem that you could handle on your own , but its deceiving. Datediff removes all the complexities relatted to the different lengths of months and leap years.
Records falling in a Date Range
So back to our task of finding RFQs that happened in a date range, lets look for dates that happened in March.
Declare @from datetime2 = '3/1/2018'
Declare @to datetime2 = '3/31/2018'
select * from RFQ where
datediff( d, @from, RFQDate ) >=0
and datediff( d, RFQDate , @To ) >=0
This will return the desired rowset
Id CompanyName RequestDate RequestDescription
2 Baker Enterprises 2018-03-10 12:25:32.077 Please manufacture 100 dongles
3 Charter Enterprises 2018-03-23 12:25:32.077 Please build a mouse trap
There are multiple ways you can express the filter. You could keep the RFQDate field as the third parameter in both calls, and just switch the sign on the second one.
select * from RFQ where
datediff( d, @from, RFQDate ) >=0
and datediff( d, @To , RFQDate ) <=0 -- syntax changes highlighted
I prefer the first style but really offer nothing beyond preference to suggest it is fundamentally better. I would suggest that you and your team select one style and remain consistent. Clearly there is room here for silly mistakes, either in typing or assuming one style when looking at another.
The end dates can be inclusive by keeping the equal sign in the comparison ( <=, >= ) or exclusive by dropping the equal sign from the comparison ( < , > )
--show me March RFQs that did not fall on Month Boundaries
select * from RFQ where
datediff( d, @from, RFQDate ) > 0
and datediff( d, RFQDate , @To ) > 0
Between
Between is a SQL keyword that seems to accomplish the same thing. Lets try it out
--Show me March RFQs
select * from rfq where requestdate between '3/1/2018' and '3/31/2018'
Here you will get the rowset you would expect from our overly simple example. The date range brackets our result set, we are lulled into thinking we did good...
Id CompanyName RequestDate RequestDescription
2 Baker Enterprises 2018-03-10 12:25:32.077 Please manufacture 100 dongles
3 Charter Enterprises 2018-03-23 12:25:32.077 Please build a mouse trap
But tighten up the query and ask for dates that are included in our data set...
--I can't remember that datediff syntax, can you just show me March RFQs please
select * from rfq where requestdate between '3/10/2019' and '3/23/2019'
Id CompanyName RequestDate RequestDescription
2 Baker Enterprises 2018-03-10 12:25:32.077 Please manufacture 100 dongles
Now you have set yourself up for the same problem we had simply using the EQUAL comparison.
In fact, if you read the doc on BETWEEN you will see that it is implemented by using <= and >= with the 2 parameters.
So the 3/10/2018 row is returned because it is greater than 3/10/2018 at midnight, but the 3/23/2018 row is not returned because it is greater than 3/23/2018 at midnight.
Conclusion
Datediff is a great function that really, really should be part of your go to toolbox of functions. You need to be able to do this in your sleep.
By using DateDiff consistently, you will never have to worry about silly boundary cases where you included or excluded rows by mistake.
Thanks for reading. Let me know what you think.
Comments
Post a Comment