1
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 不是内联函数调用吗?

4

1 回答 1

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 的外循环或 a 的“表”侧哈希连接)。例如,如果您尝试这样做:

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)仍然遵循关于将计算表达式放在何处的规则(在列的等号的另一侧)

请务必在 Microsoft Connect 上为内联标量 UDF投票!您远不是唯一一个认为此功能严重缺乏的人。

于 2012-07-23T15:37:59.540 回答