SQL语句功能与案例声明

时间:2012-12-31 14:58:33

标签: sql-server

    select top 10 *, case     
    when datediff(day,DateOfSale, getDate())  > 5 then '5'
        when datediff(day,DateOfSale, getDate())  > 10 then '10'
... (more datediff clauses)
...
...
        else '20'
        end as jack
        from Foo

SQL Server是否足够智能在case语句中评估datediff函数调用一次,然后对每个when子句使用该值?或者该函数被称为'n'次,其中'n'是when子句的数量?

3 个答案:

答案 0 :(得分:3)

很难看出SQL Server如何评估一次调用。该调用有一列作为参数,因此必须对每一行进行评估。

因此,你的情况写得更好:

when DateOfSale < dateadd(day, -5, getdate()) then '5'

在这种情况下,差异很小。日期计算很便宜。

函数调用很重要的典型示例是表上的where条件,日期列上有索引。例如,YourTable的索引位于(dt)。此查询将允许使用索引:

select * from YourTable where dt < dateadd(day, -5, getdate())

虽然此查询不会:

select * from YourTable where datediff(day, DateOfSale, getDate()) > 5

答案 1 :(得分:2)

令人费解的是,很多答案都提到索引。实际上,DATEDIFF不是SARGable,但这完全不相关,因为CASE WHEN不会导致SQL Server中的查询优化器考虑索引使用(除了尝试查找覆盖可扫描路径之外)。据我所知,DATEDIFF涉及的索引路径表达式的候选资格完全与这个问题无关。

一旦找到第一个真正的谓词,很容易证明SQL Server确实停止在CASE语句中评估谓词。

为了证明这一点,我们来做一些样本数据:

CREATE TABLE Diffy (SomeKey INTEGER NOT NULL IDENTITY(1,1), DateOfSale DATE);

DECLARE @ThisOne AS DATE;
SET @ThisONe = '2012-01-01';
WHILE @thisONe < '2013-01-01'
BEGIN
    INSERT INTO Diffy (DateOfSale) VALUES(@ThisOne);
    SET @ThisOne = DateAdd(d, 1, @ThisOne);
END;

然后,让它SELECT在原始问题的模式中。请注意,原始问题指定了没有TOP 10子句的ORDER BY子句,因此我们实际返回的值是随机的。但是如果我们在CASE添加一个会导致评估的条款,我们可以看到发生了什么:

SELECT TOP 10 *, CASE 
WHEN datediff(day,DateOfSale, getDate())  > 5 then '5'
WHEN datediff(day,DateOfSale, getDate())  > 10 then '10'
WHEN 1/0  > 1then 'boom'
ELSE '20' END
AS Jack
FROM Diffy;

请注意,如果我们评估过1/0 > 1,那么我们会期望像'Divide by zero error encountered.'这样的东西。但是,对我的服务器运行此查询会产生十行,Jack列中的所有行都为'5'。

如果我们拿走TOP 10,肯定会得到一些行然后得到Divide by zero错误。因此,我们可以安全地得出结论,SQL Server正在对CASE语句进行早期退出评估。

除此之外,the documentation也告诉我们:

  

CASE语句按顺序评估其条件,并在条件满足的第一个条件下停止。

也许这个问题的目的是询问是否从所有DATEDIFF()语句中提取公共CASE子表达式,计算一次,然后在每个谓词的上下文中进行求值。通过观察SET SHOWPLAN_TEXT ON的输出,我认为我们可以得出结论并非如此:

   |--Compute Scalar(DEFINE:([Expr1004]=CASE WHEN datediff(day,CONVERT_IMPLICIT(datetimeoffset(7),[Scratch3].[dbo].[Diffy].[DateOfSale],0),CONVERT_IMPLICIT(datetimeoffset(3),getdate(),0))>(5) THEN '5'  ELSE CASE WHEN datediff(day,CONVERT_IMPLICIT(datetimeoffset(7),[Scratch3].[dbo].[Diffy].[DateOfSale],0),CONVERT_IMPLICIT(datetimeoffset(3),getdate(),0))>(10) THEN '10' ELSE CASE WHEN (1)/(0)>(1)  THEN 'boom' ELSE '20' END END END))
        |--Index Scan(OBJECT:([Scratch3].[dbo].[Diffy].[DiffyHelper]))

由此,我们可以得出结论,此查询的结构意味着为每行和每个谓词计算DATEDIFF(),因此O(rows * predicates)调用最坏的情况。这会导致查询出现一些CPU负载,但DATEDIFF()并不完全 昂贵且不应该是一个问题。实际上,如果它导致性能问题,则有一些方法可以从查询中手动提升计算。例如,比较的日期相对方DATEDIFF()

答案 2 :(得分:1)

当然,但不是在您的情况下(表达式基于每行更改的表列值),但无论如何,不​​要对表列值执行datediff,在谓词上运行dateadd (比较)值,因此您的查询仍然可以使用DateOfSale上的任何现有索引...

  select top 10 *, 
      case When DateOfSale < dateadd(day, -20, getDate()) then '20'
           When DateOfSale < dateadd(day, -15, getDate()) then '15'
           When DateOfSale < dateadd(day, -10, getDate()) then '10' 
           When DateOfSale < dateadd(day, -5, getDate()) then '5' 
          else '20' end jack
  from Foo