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
子句的个数?
很难看出SQL Server如何计算一次调用。调用有一个列作为参数,因此必须对每一行求值。
因此,您的条件最好写成:
when DateOfSale < dateadd(day, -5, getdate()) then '5'
在这种情况下,差异很小。日期计算很便宜。
函数调用起作用的经典示例是在日期列上有索引的表上的where
条件。例如,在(dt)
上有索引的YourTable
。这个查询将允许使用索引:
select * from YourTable where dt < dateadd(day, -5, getdate())
而这个查询不会:
select * from YourTable where datediff(day, DateOfSale, getDate()) > 5
令人费解的是,这么多的答案都提到了索引。事实上,DATEDIFF
不是可扫描的,但这在这里完全无关紧要,因为CASE WHEN不会导致SQL Server中的查询优化器考虑索引使用情况(而不是试图找到覆盖可扫描的路径)。据我所知,涉及dateff的表达式是否适合用于索引路径与这个问题完全无关。
很容易证明,一旦发现第一个真谓词,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.'
的结果。但是,在我的服务器上运行这个查询会产生10行,Jack
列中都有'5'。
如果我们拿走TOP 10,我们肯定会得到一些行,然后得到Divide by zero
错误。因此,我们可以有把握地得出结论,SQL Server正在对CASE语句进行提前退出评估。
除此之外,文档还告诉我们:
CASE语句按顺序计算其条件,并在满足条件的第一个条件处停止。
也许这个问题是想问是否从所有CASE
语句中提升公共DATEDIFF()
子表达式,计算一次,然后在每个谓词的上下文中求值。通过观察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()
当然,但不是在您的情况下(表达式基于每一行更改的表列值),但在任何情况下,不要对表列值执行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