CREATE function [dbo].[fn_GetDateOnly](@dateWithTime datetime)
returns datetime WITH SCHEMABINDING
as
begin
return DATEADD(DAY, DATEDIFF(DAY, 0, @dateWithTime), 0)
end
SELECT stuff, count(ff.id)
FROM dbo.foo AS ff
where
--DATEADD(DAY, DATEDIFF(DAY, 0, ff.startDate), 0) <= @curdate --6700ms
--dbo.fn_GetDateOnly(ff.startDate) <= @curdate --9300ms
group by stuff
order by stuff desc
为什么调用fn_getdateonly比内联它慢得多? SQL是否内联函数调用?
答案 0 :(得分:3)
列上的表达
首先,无论您使用什么函数,将其放在WHERE子句或表中列的JOIN条件中都是次优的。对常数进行数学运算并进行比较。您的WHERE子句应如下所示:
ff.startDate < DateAdd(day, 1, @curdate) -- if @curdate has time portion removed
ff.startDate < DateAdd(day, 1, dbo.fn_GetDateOnly(@curdate)) -- if @curdate has time
对于在给定日期一般查找项目,请使用以下模式:
WHERE
DateCol >= '20120901'
AND DateCol < '20120902'
将任何函数放在等号的另一侧作为列,它应该是唯一的。它可以帮助您查找如何使表达式SARGable。如果列必须位于两侧,则将所有表达式放在执行计划中的“左”输入的一侧(其数据首先出现,是LOOP JOIN的外循环或者是“表”的一侧) HASH JOIN)。例如,如果您尝试这样做:
WHERE dbo.fn_getDateOnly(A.DateCol) = dbo.fn_getDateOnly(B.DateCol)
然后假设A.DateCol在执行计划中排在第一位,将其切换为:
WHERE
B.DateCol >= DateAdd(day, DateDiff(day, 0, A.DateCol), 0)
AND B.DateCol < DateAdd(day, DateDiff(day, 0, A.DateCol), 0)
(或者使用下面函数的内联版本,但我发现它同样笨拙,因此没有额外的间接值)。
如果要在所涉及的表上频繁地进行这种查询,那么可能需要进行一些重新设计,要么添加一个持久的计算列,其中删除了时间部分(可能已编入索引),以分割日期时间分成日期和时间字段,或者将其存储为开始日期(如果您确实不需要日期时间)。
注意:当然,对dbo.fn_getDateOnly的引用可以简单地替换为DateAdd(day, DateDiff(day, 0, DateCol), 0)
。此外,如果值具有datetime
数据类型,则您可以DateCol + 1
而不是使用DateAdd
(但要小心,因为这不适用于date
SQL 2008及更高版本中的数据类型。
UDF的内联能力
对于SELECT子句中函数的性能,SQL Server只内联“内联函数”,而不是标量函数。更改函数以返回单行记录集CREATE FUNCTION ... RETURNS TABLE AS RETURN (SELECT ...)
并按如下方式使用它:
SELECT
(SELECT DateValue FROM dbo.fn_GetDateOnly(Col)),
...
它与参数化视图没什么不同。
在WHERE子句中使用此内联版本将会非常笨拙。做DateDiff几乎就更好了。在SQL 2008中,当然只使用Convert(date, DateCol)
但仍然遵循有关计算表达式放置位置的规则(在与列相等的另一侧)。
一定要投票给inline scalar UDFs on Microsoft Connect!你是唯一一个认为这个功能非常缺乏的人。