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
子句的数量?
答案 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