到目前为止的铸造是值得肯定的,但这是一个好主意吗?


47

在SQL Server 2008中,添加了日期数据类型。

datetime强制转换为date精简并且可以在datetime列上使用索引。

select *
from T
where cast(DateTimeCol as date) = '20130101';

您还有另一个选择是使用范围。

select *
from T
where DateTimeCol >= '20130101' and
      DateTimeCol < '20130102'

这些查询是否同样好,还是应该优先于另一个?


4
执行计划怎么说?
a_horse_with_no_name

3
我不禁注意到LINQ2SQL where cast(date_column as date) = 'value'在与C相似的C#出现时会生成SQL where obj.date_column.Date == date_variable
GSerg

6
这是一个很棒的Connect项目。:)
Rob Farley

1
该连接部位已被删除,以及优化搜索维基百科
Ivanzinho

Answers:


59

迄今为止,可铸造性背后的机制称为动态寻道

SQL Server调用内部函数GetRangeThroughConvert来获取范围的开始和结束。

出人意料的是,这是相同的范围内为您的文字值。

创建一个表,每页一行,每天1440行

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,
       Dates 

然后跑步

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'; 

第一个查询已1443读取,第二个查询已读取,2883因此它正在读取整整一整天,然后针对剩余谓词将其丢弃。

计划显示寻找谓词是

Seek Keys[1]: Start: DateTimeCol > Scalar Operator([Expr1006]), 
               End: DateTimeCol < Scalar Operator([Expr1007])

因此,而不是>= '20130101' ... < '20130102'读取> '20121231' ... < '20130102'然后丢弃所有2012-12-31行。

依赖它的另一个缺点是基数估计可能不如传统范围查询准确。这可以在SQL Fiddle的修订版中看到。

现在,表中的所有100行都与谓词匹配(日期时间在同一天都相隔1分钟)。

第二个(范围)查询正确估计100将匹配并使用聚簇索引扫描。该CAST( AS DATE)查询错误地估计只有一行会匹配,并生成带有关键查询的计划。

统计信息不会被完全忽略。如果表中的所有行都相同datetime,并且与谓词(例如20130101 00:00:0020130101 01:00:00)匹配,那么该计划将显示一个聚集索引扫描,估计有31.6228行。

100 ^ 0.75 = 31.6228

因此,在这种情况下,估计值似乎是从此处的公式得出

如果表中的所有行都相同datetime且不匹配谓词(例如20130102 01:00:00),则它会退回到估计的行数1和带有查找的计划。

对于表具有多个DISTINCT值的情况,估计的行似乎与查询正在寻找的行相同20130101 00:00:00

如果统计直方图恰好有一个台阶,2013-01-01 00:00:00.000则估计将基于EQ_ROWS(即不考虑该日期的其他时间)。否则,如果没有任何步骤,则看起来好像使用AVG_RANGE_ROWS了周围步骤中的。

由于datetime在许多系统中精度约为3毫秒,因此实际的重复值将很少,而该数字将为1。


1
嗨,马丁,您能添加一个TL;DR带有几个要点的零件,并在不同情况下添加,在这种情况下,到目前为止的演员表是否是个好主意?
TT。

6
@TT。我认为关键是这不是一个好主意。为什么要使用需要备忘单的方法?
亚伦·伯特兰

10

我知道它具有Martin长期以来的GreatAnswer®,但我想在SQL Server的较新版本中对行为进行一些更改。这似乎仅在2008R2之前经过了测试。

借助新的USE HINT,可以进行一些基数估计,从而可以看到何时发生了变化。

使用与SQL Fiddle中相同的设置。

CREATE TABLE T ( ID INT IDENTITY PRIMARY KEY, DateTimeCol DATETIME, Filler CHAR(8000) NULL );

CREATE INDEX IX_T_DateTimeCol ON T ( DateTimeCol );


WITH E00(N) AS (SELECT 1 UNION ALL SELECT 1),
     E02(N) AS (SELECT 1 FROM E00 a, E00 b),
     E04(N) AS (SELECT 1 FROM E02 a, E02 b),
     E08(N) AS (SELECT 1 FROM E04 a, E04 b),
     Num(N) AS (SELECT ROW_NUMBER() OVER (ORDER BY E08.N) FROM E08)
INSERT INTO T(DateTimeCol)
SELECT TOP 100 DATEADD(MINUTE, Num.N, '20130101')
FROM Num;

我们可以像这样测试不同的级别:

SELECT *
FROM   T
WHERE  CAST(DateTimeCol AS DATE) = '20130101'
OPTION ( USE HINT ( 'QUERY_OPTIMIZER_COMPATIBILITY_LEVEL_100' ));
GO

SELECT *
FROM   T
WHERE  CAST(DateTimeCol AS DATE) = '20130101'
OPTION ( USE HINT ( 'QUERY_OPTIMIZER_COMPATIBILITY_LEVEL_110' ));
GO 

SELECT *
FROM   T
WHERE  CAST(DateTimeCol AS DATE) = '20130101'
OPTION ( USE HINT ( 'QUERY_OPTIMIZER_COMPATIBILITY_LEVEL_120' ));
GO 

SELECT *
FROM   T
WHERE  CAST(DateTimeCol AS DATE) = '20130101'
OPTION ( USE HINT ( 'QUERY_OPTIMIZER_COMPATIBILITY_LEVEL_130' ));
GO 

SELECT *
FROM   T
WHERE  CAST(DateTimeCol AS DATE) = '20130101'
OPTION ( USE HINT ( 'QUERY_OPTIMIZER_COMPATIBILITY_LEVEL_140' ));
GO 

所有这些的计划都在这里。兼容级别100和110都给出了关键查找计划,但是从兼容级别120开始,我们开始获得具有100行估计的相同扫描计划。直到兼容级别140都是如此。

坚果

坚果

坚果

>= '20130101', < '20130102'计划的基数估计仍为100,这是预期的。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.