I\'m using SQL Server 2012 and I would like to know if I write the sentence:
SELECT MyDateTimeColumn
FROM MyTable
WHERE CAST(MyDateTimeColumn AS DATE) = \'
So, lets use Anon's example. I changed it to have both a Primary Key on a row number and Non Clustered index on date.
Also, I choose to create random dates instead of a simple incremental update.
Here is the code below to create the test database.
-- Do not save in physical database
USE [tempdb]
GO
-- Drop table
IF EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[test2]') AND type in (N'U'))
DROP TABLE test2
GO
-- Create table
CREATE TABLE test2 (my_num int NOT NULL, my_dt datetime NOT NULL);
GO
-- Add data
INSERT test2
SELECT
TOP 100000
ROW_NUMBER() OVER(ORDER BY (SELECT 1)) as my_num,
DATEADD(minute, RAND() * 500 * ROW_NUMBER() OVER(ORDER BY (SELECT 1)), '2000-01-01') as my_dt
FROM
master.dbo.spt_values t1, master.dbo.spt_values t2
GO
-- Add primary key
ALTER TABLE Test2 ADD CONSTRAINT pk_My_Num PRIMARY KEY (my_num);
GO
-- Add nc index
CREATE INDEX ix_My_Dt ON Test2 (my_dt);
GO
Now, lets take a look at each solution. The pros and cons.
I am going to use trace flags to look at the algebraic query parse tree as well as the query plan.
Solution 1: Index scan which is bad and needs to apply conversion to each date field.
-- Show output to message screen
DBCC TRACEON(3604)
-- 1 - Not sargable, applies conversion to each date field
SELECT count(*)
FROM Test2
WHERE CONVERT(varchar(10), my_dt, 120) >= '2000-02-01'
AND CONVERT(varchar(10), my_dt, 120) < '2000-02-02'
OPTION (RECOMPILE, QUERYTRACEON 8607)
Solution 2: Index seek which is good and no conversion on date field. However, cultural specific date format.
-- 2 - Sargable
SELECT count(*)
FROM Test2
WHERE my_dt >= '2000-02-01' AND my_dt < '2000-02-02'
OPTION (RECOMPILE, QUERYTRACEON 8607)
Solution 3: Index seek which is good and no conversion on date field. However, you have to convert date to integer.
-- 3 - Implicit conversion, still Sargable
SELECT COUNT(*) FROM Test2
WHERE my_dt >= 36555 AND my_dt < 36556
OPTION (RECOMPILE, QUERYTRACEON 8607)
Solution 4: Index seek which is good and no conversion on date field. Date is in cultural (country) neutral format. Best solution!
-- 4 - Sargable
SELECT count(*)
FROM Test2
WHERE my_dt >= '20000201' AND my_dt < '20000202'
OPTION (RECOMPILE, QUERYTRACEON 8607);
Solution 5: Index seek which is good. Applies conversion to each date field which is bad. Cultural specific date constant. Most complicated query plan.
-- 5 - Explicit conversion, still sargable
-- applies conversion to each date field
SELECT COUNT(*)
FROM Test2
WHERE CAST(my_dt AS date) >= '2000-02-01'
AND CAST(my_dt AS date) < '2000-02-02'
OPTION (RECOMPILE, QUERYTRACEON 8607);
In summary, use solution 4 which takes advantage of the index and is not cultural specific.
Using cast() is not a good suggestion. It uses the index but extra time is spent on converting each index value during the comparison.
Note to self, make sure I explain in detail what I mean.
Here are some good reads on the topic!
References - All about dates.
http://karaszi.com/the-ultimate-guide-to-the-datetime-datatypes
Reference - Aaron's suggestions on date usage.
https://sqlblog.org/2009/10/16/bad-habits-to-kick-mis-handling-date-range-queries
What is SARGABLE.
http://en.wikipedia.org/wiki/Sargable
In SQL 2008+, CAST(foo AS date)
is sargable, along with a few other manipulations. Look at the execution plans in the sqlfiddle.
SQL Fiddle Demo
Use this proven method to "zero out" the time component of a datetime:
select MyDateTimeColumn
from MyTable
where DATEADD(dd, DATEDIFF(dd, 0, MyDateTimeColumn), 0) = CONVERT(date, '07-09-2014', 110)
Avoid casting to different date types if there's a faster way (like the trick above), and definitely try to avoid using string literals for dates without wrapping them in a CONVERT() to ensure your string format will get interpreted correctly.
If you have performance concerns and want to force it to use an index, I would suggest adding a column that is of type date
(fill it with the existing column's value minus the time part), and index/search on that, or create an indexed view that accomplishes the same thing.