0
    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 是否足够聪明,可以datediff在 case 语句中评估一次函数调用,然后为每个when子句使用该值?还是该函数被调用了“n”次,其中“n”是when子句的数量?

4

3 回答 3

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
于 2012-12-31T15:05:32.240 回答
2

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

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

为了证明这一事实,让我们制作一些示例数据:

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子句,因此我们实际返回的值是随机的。但是如果我们在CASEthat 中添加一个会毒化评估的子句,我们可以看到发生了什么:

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.'. 但是,对我的服务器运行此查询会产生 10 行,列中都包含“5” Jack

如果我们去掉 TOP 10,果然我们得到了一些行,然后得到了Divide by zero错误。因此,我们可以安全地得出结论,SQL Server 正在对 CASE 语句进行提前退出评估。

最重要的是,文档还告诉我们:

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()在比较的相对日期方面。

于 2012-12-31T15:16:36.077 回答
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
于 2012-12-31T15:08:01.497 回答