patternsqlCritical
Cast to date is sargable but is it a good idea?
Viewed 0 times
ideabutsargabledatecastgood
Problem
In SQL Server 2008 the date datatype was added.
Casting a
The other option you have is to use a range instead.
Are these queries equally good or should one be preferred over the other?
Casting a
datetime column to date is sargable and can use an index on the datetime column.select *
from T
where cast(DateTimeCol as date) = '20130101';The other option you have is to use a range instead.
select *
from T
where DateTimeCol >= '20130101' and
DateTimeCol < '20130102'Are these queries equally good or should one be preferred over the other?
Solution
The mechanism behind the sargability of casting to date is called dynamic seek.
SQL Server calls an internal function
Somewhat surprisingly this is not the same range as your literal values.
Creating a table with a row per page and 1440 rows per day
Then running
The first query has
The plan shows the seek predicate is
So instead of
SQL Server calls an internal function
GetRangeThroughConvert to get the start and end of the range.Somewhat surprisingly this is not the same range as your literal values.
Creating a table with a row per page and 1440 rows per day
CREATE TABLE T
(
DateTimeCol DATETIME PRIMARY KEY,
Filler CHAR(8000) DEFAULT 'X'
);
WITH Nums(Num)
AS (SELECT number
FROM spt_values
WHERE type = 'P'
AND number BETWEEN 1 AND 1440),
Dates(Date)
AS (SELECT {d '2012-12-30'} UNION ALL
SELECT {d '2012-12-31'} UNION ALL
SELECT {d '2013-01-01'} UNION ALL
SELECT {d '2013-01-02'} UNION ALL
SELECT {d '2013-01-03'})
INSERT INTO T
(DateTimeCol)
SELECT DISTINCT DATEADD(MINUTE, Num, Date)
FROM Nums,
DatesThen running
SET STATISTICS IO ON;
SET STATISTICS TIME ON;
SELECT *
FROM T
WHERE DateTimeCol >= '20130101'
AND DateTimeCol < '20130102'
SELECT *
FROM T
WHERE CAST(DateTimeCol AS DATE) = '20130101';The first query has
1443 reads and the second 2883 so it is reading an entire additional day then discarding it against a residual predicate.The plan shows the seek predicate is
Seek Keys[1]: Start: DateTimeCol > Scalar Operator([Expr1006]),
End: DateTimeCol < Scalar Operator([Expr1007])So instead of
>= '20130101' ... '20121231' ...
Conjuncts
Cardinality
Selectivity
1
N^(3/4)
N^(-1/4)
2
N^(11/16)
N^(-5/16)
3
N^(43/64)
N^(-21/64)
4
N^(171/256)
N^(-85/256)
5
N^(170/256)
N^(-86/256)
6
N^(169/256)
N^(-87/256)
7
N^(168/256)
N^(-88/256)
...
175
N^(0/256)
N^(-1)
If all rows in the table have the same datetime and it doesn't match the predicate (e.g. 20130102 01:00:00) then it falls back to the estimated row count of 1 and the plan with lookups.
For the cases where the table has more than one DISTINCT value the estimated rows seems to be the same as if the query was looking for exactly 20130101 00:00:00.
If the statistics histogram happens to have a step at 2013-01-01 00:00:00.000 then the estimate will be based on the EQ_ROWS (i.e. not taking into account other times on that date). Otherwise if there is no step it looks as though it uses the AVG_RANGE_ROWS from the surrounding steps.
As datetime` has a precision of approx 3ms in many systems there will be very few actual duplicate values and this number will be 1.Code Snippets
CREATE TABLE T
(
DateTimeCol DATETIME PRIMARY KEY,
Filler CHAR(8000) DEFAULT 'X'
);
WITH Nums(Num)
AS (SELECT number
FROM spt_values
WHERE type = 'P'
AND number BETWEEN 1 AND 1440),
Dates(Date)
AS (SELECT {d '2012-12-30'} UNION ALL
SELECT {d '2012-12-31'} UNION ALL
SELECT {d '2013-01-01'} UNION ALL
SELECT {d '2013-01-02'} UNION ALL
SELECT {d '2013-01-03'})
INSERT INTO T
(DateTimeCol)
SELECT DISTINCT DATEADD(MINUTE, Num, Date)
FROM Nums,
DatesSET STATISTICS IO ON;
SET STATISTICS TIME ON;
SELECT *
FROM T
WHERE DateTimeCol >= '20130101'
AND DateTimeCol < '20130102'
SELECT *
FROM T
WHERE CAST(DateTimeCol AS DATE) = '20130101';Seek Keys[1]: Start: DateTimeCol > Scalar Operator([Expr1006]),
End: DateTimeCol < Scalar Operator([Expr1007])100 ^ 0.75 = 31.6228Context
StackExchange Database Administrators Q#34047, answer score: 84
Revisions (0)
No revisions yet.