Comparing equality of date and datetime in SQL Server - sql

I am returning rows based on a date field equaling a datetime field. They obviously only directly match when in the format of dd/MM/yyyy = dd/MM/yyyy 00:00:00 but I am looking to disregard the time.
There are 3 methods which I have tried, they all work, but I am wondering what is best.
1 - CONVERT(varchar(10),MyDate,103) = CONVERT(varchar(10),MyDateTime,103))
2 - MyDate = CONVERT(date,MyDateTime)
3 - MyDate = CAST(MyDateTime AS date)
4 - MyDate = DATEADD(dd, DATEDIFF(dd, 0, MyDateTime), 0)
To me, #1 should be the slowest, converting to string then using string comparison surely should be least efficient. But in tests it is the fastest! Below is my tests:
1 - 303ms average
2 - 284ms average
3 - 273ms average
4 - 1745ms average
Test is from a sample size of ~300,000
Is there a reason for this? Is the first option genuinely the best option?
EDIT: Changed the test values to reflect the tests being ran 10 times each for 300k records. Changes the outcome to show all are pretty similar apart from the DATEADD/DATEDIFF method Tim Schmelter mentioned below. That seems to be by far the least efficient.

I would say that #3 is the best choice. Here are my reasons.
You have already performed the performance work, so I won't redo it. Your updated numbers show options 1-3 to be very similar so we can put performance aside, except to rule out #4.
Once performance is settled, it's on to best practices and readability. #1 is definitely to most code and the hardest to read so I would rule that out. This same reason applies to the, already ruled out, #4.
This leaves us with #2 and #3. My selection goes to #3 because CAST is part of the SQL standard and is more portable than CONVERT. So, I would recommend always using CAST, whenever you do not need the special features of CONVERT.

If MyDate is a parameter then there is a fifth option:
Check if MyDateTime lies between [MyDate, MyDate + 1 DAY). If there is an index on that column then this query can use index seek instead of index scan.
DECLARE #MyDate1 AS DATETIME = '2015-01-01' -- 2015-01-01 00:00:00
DECLARE #MyDate2 AS DATETIME = DATEADD(DAY, 1, #MyDate1) -- 2015-01-02 00:00:00
SELECT ... WHERE MyDateTime >= #MyDate1 AND MyDateTime < #MyDate2

Related

SQL Server: Using between with getdate

What does the below query mean?
CONVERT(date,GETDATE()-1) between d.baslangictarihi and d.bitistarihi
I know how to use between by first selecting the column name and then giving the value. but here it is given the value first and then called 2 columns.
As others explained, this somewhat quirky condition checks whether yesterday's date CONVERT(date,GETDATE()-1) is between two date fields baslangictarihi and bitistarihi. More importantly, it does so without preventing the server from using any indexes that cover baslangictarihi and bitistarihi.
Indexes are created based on the actual stored values, so applying a function to a field prevents the server from using indexes to speed up searching.
So while baslangictarihi <= GETDATE() can use any indexes that cover that field to limit processing only to the matching table rows, dateadd(d,1,baslangictarihi) <= GETDATE() would have to process all table rows, calculate the result and compare it against GETDATE(). In a large table, this can be very slow.
SQL Server Date quirks
The first part has some quirks too, due to SQL Server's somewhat quirky date support. To be fair all databases and programming languages have quirks when it comes to dates.
GETDATE() returns the legacy datetime type which often behaves as a float, with the integral part an offset from 1899-12-30 (no typo, it really is December 30), and the fractional representing time. That's how dates were stored in Visual Basic in the 1990s and Excel (OADate format)
Since GETDATE() acts as a float, it's possible to subtract days by subtracting integers, so GETDATE()-1 is equivalent to DATEADD('d',GETDATE(),-1).
SQL Server has no interval type, so in some quirky code you'll even see people storing intervals as datetime, eg 0000-00-01 01:00 and adding two dates directly. None of the "new" date types introduced in ... 2005 (datetime2,datetimeoffset,date) allows this.
Finally, convert(date,....) converts datetime to date, a type that only contains a date. Effectively, this truncates the time part returned by GETDATE()
The same expression without quirks would be CONVERT(date,DATEADD(d,-1,GETDATE()))
Well you could "explode" the BETWEEN expression, such that this:
CONVERT(date, GETDATE() - 1) BETWEEN d.baslangictarihi AND d.bitistarihi
becomes this:
CONVERT(date, GETDATE() - 1) >= d.baslangictarihi AND
CONVERT(date, GETDATE() - 1) <= d.bitistarihi
This is just checking if yesterday's date happens to be in between baslangictarihi and bitistarihi, both ends included.
lets consider a sample data to understand this better.
membership_dim
id
name
dob
membership_start_date
membership_end_date
1
abc
19-05-1976
01-05-2020
31-12-2022
2
efg
10-01-1990
21-01-2018
31-12-2021
3
xyz
31-01-1990
12-01-2022
31-12-2022
Your Query
CONVERT(date,GETDATE()-1) between d.baslangictarihi and d.bitistarihi
rewriting to match the above sample data
select * from membership_dim where CONVERT(date,GETDATE()-1) between membership_start_date and membership_end_date
Result set
id
name
dob
membership_start_date
membership_end_date
1
abc
19-05-1976
01-05-2020
31-12-2022
3
xyz
31-01-1990
12-01-2022
31-12-2022
Explanation:
lets breakdown the code
CONVERT(date,GETDATE()-1)
-> getdate()-1 = returns yesterday's date in datetime format (01-23-2022 xx:xx:xx.xxx)
-> convert = converts the datatime to date (01-23-2022)
-> between = a comparison operator
01-23-2022 between 01-05-2020 and 31-12-2022 - returns true
01-23-2022 between 21-01-2018 and 31-12-2021 - returns false
01-23-2022 between 21-01-2018 and 31-12-2022 - returns true
Just understand that everything in a predicate like this is an expression. CONVERT(date,GETDATE()-1) means yesterday without the time component. Those two columns are whatever values are on the row that's being considered at the time. You know what it means if there's a column on the left, but this is no different. It gets evaluated just the same.

Using DATEADD instead of less than or equal to when testing against parameters

I'm newer to SQL and I'm playing around with some existing queries in my database in SSMS - something I've been coming across lately is this line:
WHERE DE.Modified >=#FromDate
AND DE.Modified < DATEADD(DAY,1,#ToDate)
Where FromDate and ToDate are given parameters. What I'm wondering is why one might write the second line instead of:
AND DE.Modified <=#ToDate
Is it a best practice in SQL to only use the less than operator and test against a date + 1, or are these the exact same?
EDIT:
FromDate and ToDate are declared as DATETIME:
DECLARE #FromDate DATETIME
, #ToDate DATETIME
You would write this to handle any time component on Modified.
In your version, anything that happens during the day of #ToDate would be missed.
The two are different:
DE.Modified <=#ToDate
checks if the Modified date is less than the ToDate, while
DE.Modified < DATEADD(DAY,1,#ToDate)
checks if the Modified date is less than the day after the ToDate. That is, the ToDate plus one day.
Just a tip when dealing with dates, usually try to compare
WHERE DATEDIFF('d',DE.Modified,#FromDate) >= 0
AND DATEDIFF('d',DE.Modified,#ToDate) <= 0
It won't mater much here but its a good habit because when you will be working on procedures or queries where you can build a calculated column, that column can then be indexed. And it will make your applications that much faster.
Also, you clearly know what you are comparing (days, hours, etc) as Gordon said above.

Convert to datetime in SQL Server

I have a simple SQL statement
select convert(datetime, '1/1/2018')
when I look at the output of it I see it is getting converted to 2018-01-01 00:00:00.000. Is it possible in the time section it gets the end of day time rather than the beginning of day?
I am using this to fetch data based on the converted date and it fails to retrieve this record 1/1/2018 15:10:43 because of the time thing.
Any suggestions?
Thanks
Update
Looks like I can do SELECT DATEADD(ms, -3, '5/31/2018') + 1 to solve my issue..Got the idea from Here
When I look at the output of it I see it is getting converted to
2018-01-31 00:00:00.000
I can't reproduce your result. select convert(datetime,'1/1/2018') doesn't return Jan 31st. It returns Jan 1st.
I am using this to fetch data based on the converted date and it fails
to retrieve this record 1/1/2018 15:10:43 because of the time thing
Since you are converting it to a DATETIME, it gets a time of 00:00:00 which is midnight. Thus, it fails to retrieve anything after midnight, like 15:10 on the same day. The easiest thing is to make your operator < the next day... so you don't have to account for hours, minutes, seconds, milliseconds...
where fetch < '20180102'
Notice I didn't use convert since SQL Server will handle that for us, but feel free to add it if it makes it clearer for you.
where fetch < convert(datetime,'20180102')
Also note that I used ANSI standars of YYYYMMDD. Other methods, which will cause issues when you use DATETIME2 or want a more precise measurement, is to add seconds to your date and use <=.
select dateadd(second,86399,convert(datetime,'20180101'))
Notice this has milliseconds of 000 though, so this can creep up on you later which is why I suggest using the next day.
For milliseconds...
select dateadd(millisecond,86399999,convert(datetime2,'20180101'))
If you are going to use a converted datetime to compare you need to be aware that it will always receive a time of 00:00:00.000. This will cause anything on that given date but with a greater time to be excluded from your results set.
To solve this issue you need to set the time on the field you are searching on to match. The code below will make every result in your datetime field have a time of 00:00:00.000, the same as your converted date.
(DATEADD(dd, DATEDIFF(dd, 0, my_col)

Storing "Time" in database- What to use DateTime/Interger/VarChar

I want to save Date and Time of the user on various activities performed. For date I have decided to use DateTime Column in Database and for Time I am in dilemma what datatype to go for.
I know in sql server 2008 Time datatype has been introduced but I am using older version i.e. Sql Server 2005 so I need your suggest to prove my understanding true or false.
I have seen people using varchar or DateTime for storing time into database. But I am looking towards usage of Integer datatype.
Reason for my selection is performance.
Following is the justification that I am giving to myself.
Assumptions
Any data saved into database must agree following rules
Date will be stored in format mm/dd/yyyy hh:MM:ss where hh:MM:ss will always be 00:00:00
Time will be stored in valid format (from hh:MM:ss as hhMMss)
if hh is 00
then MMss
and if MM is 00
then ss
and if ss is 00
then 0
hh will range in between 0-23
MM will range in between 0-59
ss will range in between 0-59
i.e. few examples
00:00:00 = 0
00:01:00 = 100
01:00:00 = 10000
13:00:00 = 130000
Personal thought why it will perform better.
SELECT * FROM Log WHERE loginDate = '05/23/2011'
AND loginTime BETWEEN 0 AND 235959 --Integer Comparison
When using JOINS on the basis of DateTime considering join for Date part only.
JOIN two tables on the basis of Common Dates irrespective of Time.I think Type Conversion would heavily impact in such cases if using DateTime as the storage datatype.
Since Sql will have to do an integer comparison and no typecasting would be required hence it should perform better.
EDIT
One drawback I just identified is when I want to get the difference between two times that how much time has been spent between 3 days, hopefully then it would become a nightmare to manage throughout the application.
So why do you need 2 columns. If the DateTime column (loginDate) has an empty time 00:00:00 why not just use that empty space for loginTime and have one column.
WHERE loginDate >= '05/23/2011' AND loginDate < '05/24/2011'
If you're intent on using an integer, there's nothing wrong with it.
Bearing your edit in mind, your ideal solution is to put both date and time in the same column, a DATETIME:
You can then trivially figure the difference between start and end times with DATEDIFF
You can easily establish just the date portion with CONVERT(varchar(10), loginDate, 101)
You can easily establish just the time portion with CONVERT(varchar(10), loginDate, 108)
Storage issues might be resolved by using SMALLDATETIME, if precision < 1minute isn't required. SMALLDATETIME requires four bytes per column, which is the same as INTEGER, so you're making a significant net gain over using two columns.

Convert SQL server datetime fields to compare date parts only, with indexed lookups

I've been doing a convert(varchar,datefield,112) on each date field that I'm using in 'between' queries in SQL server to ensure that I'm only accounting for dates and not missing any based on the time part of datetime fields.
Now, I'm hearing that the converts aren't indexable and that there are better methods, in SQL Server 2005, to compare the date part of datetimes in a query to determine if dates fall in a range.
What is the optimal, indexable, method of doing something like this:
select * from appointments
where appointmentDate>='08-01-2008' and appointmentDate<'08-15-2008'
The best way to strip the time portion of a datetime field is using datediff and dateadd functions.
DateAdd(day, datediff(day,0, MydateValue), 0)
This takes advantedge of the fact that SQL Server stores dates as two integers, one representing the number of days since day "0" - (1 jan 1900), and the second one which represents the number of ticks (each tick is about 3.33 ms) since midnight (for the time) *.
the formula above simply has to only read the first integer. There is no conversion or processing required, so it is extremely fast.
To make your queries use an index... use this formula on the input filtering parameters first, or on the "other" side of the equal sign from the tables date time field, so that the query optimizer does not have to run the calculation on every datetime field in the table to determine which rows satisfy the filter predicate. This makes your search argument "SARG-able" (Search ARGument)
Where MyDateTimeColumn > DateAdd(day,
datediff(day,0, #MydateParameter), 0) -- SARG-able
rather than
Where DateAdd(day, datediff(day,0,
MyDateTimeColumn ), 0) > #MydateParameter -- Not SARG-able
* NOTE. Internally, the second integer (the time part) stores ticks. In a day there are 24 x 60 X 60 X 300 = 25,920,000 ticks (serendipitously just below the max value a 32 bit integer can hold). However, you do not need to worry about this when arithmetically modifying a datetime... When adding or subtracting values from datetimes you can treat the value as a fraction as though it was exactly equal to the fractional portion of a day, as though the complete datetime value was a floating point number consisting of an integer portion representing the date and the fractional portion representing the time). i.e.,
`Declare #Dt DateTime Set #Dt = getdate()
Set #Dt = #Dt + 1.0/24 -- Adds one hour
Select #Dt
Set #Dt = #Dt - .25 -- Moves back 6 hours
Select #Dt`
Converting numeric types to string values (a type of Boxing) is not the best performing method of doing what you are looking for. Its not really about index-able, because the actual column type is date time.
If you are looking for the best way query for dates, then your example is right, but you may want to take into account the 3 ms precision difference in MSSQL. It can mean that records from one day can show up in another day's result.
This
select * from appointments where appointmentDate>='08-01-2008' and appointmentDate<'08-15-2008'
Should be this
select * from appointments where appointmentDate>='08-01-2008' and appointmentDate<='08-14-2008 23:59:59.996'
It's correct - doing the conversion will execute the conversion for every row queried. It's better to leave the date columns as dates, and pass in your where clauses as dates:
select * from appointments where appointmentdate between
'08/01/2008' AND '08/16/2008'
Note: Leaving off the time means midnight (00:00.000), so you will include all times for 08/01, and all times from 08/15, and anything that is exactly 08/16/2008 00:00:00
Have a computed persisted column calculate the expression you need. If columns are computed and persisted, they can also be indexed.
There is also the way described at http://www.stillnetstudios.com/comparing-dates-without-times-in-sql-server/
SELECT CAST(FLOOR(CAST( getdate() AS float )) AS datetime)