令人费解的是,这么多答案都提到了索引。实际上,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
子句,因此我们实际返回的值是随机的。但是如果我们在CASE
that 中添加一个会毒化评估的子句,我们可以看到发生了什么:
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()
在比较的相对日期方面。