【问题标题】:Help with slow UDF in SQL Server 2005帮助解决 SQL Server 2005 中缓慢的 UDF
【发布时间】:2010-10-26 15:18:03
【问题描述】:

我有一个名为 [BadDates] 的日期表,它只有一列,其中每条记录都是要排除的日期。我有一个如下的UDF:

CREATE FUNCTION [dbo].[udf_GetDateInBusinessDays]
(
  @StartDate datetime,  --Start Date
  @NumberDays int           --Good days ahead
)
RETURNS datetime
AS
BEGIN
-- Declare the return variable here
DECLARE @ReturnDate datetime
SET @ReturnDate = @StartDate
DECLARE @Counter int
SET @Counter = 0
WHILE   @Counter < @NumberDays
BEGIN
    SET @ReturnDate = DateAdd(d,1,@ReturnDate)
    IF ((SELECT COUNT(ID)
        FROM dbo.[BadDates]
        WHERE StartDate = @ReturnDate) = 0)
    BEGIN
        SET @Counter = @Counter + 1
    END
END
RETURN @ReturnDate
END

此 UDF 效果很好,但处理速度很慢。使用它的存储过程在每条记录中运行 UDF。是否有其他方法可以更快地提供相同的功能。

非常感谢任何帮助!

【问题讨论】:

  • 您能用高级英语描述您要完成的工作吗?

标签: sql recursion performance user-defined-functions


【解决方案1】:

您可能希望在 BadDates.StartDate 上放置一个索引,但可能还有其他更好的解决方案。

【讨论】:

    【解决方案2】:

    好的,您为什么要计算何时可以使用 EXISTS 关键字?如果是因为您可以在 Badates 中有多个相同类型的日期,这似乎是错误的。当您只需要排除 1 时,COUNT 可能会查看整个表以计算 startdate 的实例。

    您是否查看过查询计划以了解发生了什么?

    【讨论】:

    • 我尝试将 IF ((SELECT COUNT(ID) FROM dbo.[BadDates] WHERE StartDate = @ReturnDate) = 0) 替换为 IF (NOT EXISTS(SELECT ID FROM dbo.[BadDates] WHERE StartDate = @ReturnDate)) 但这并没有太大帮助,2 秒的改进。
    【解决方案3】:

    您似乎正在使用此 UDF 来计算两个日期之间的差异。如果我解释正确,那么我建议您使用内置的 datediff 函数。

    【讨论】:

    • 不,没有区别。基本上,如果@StartDate 等于 2009 年 1 月 1 日和 @NumberDays = 3,那么它会查看 2009 年 1 月 2 日是否在 BadDates 中,如果不是,那么它将增加计数器,否则它将移动到 2009 年 1 月 3 日.这会一直发生,直到增加 3 个好的日期。如果 2009 年 1 月 3 日是 baddates 中的唯一日期,则输出将为 2009 年 1 月 5 日。
    • 那么,为什么不查询给定日期范围内坏日期的记录数,然后在必要时使用 dateadd 增加日期?
    【解决方案4】:

    我假设您要做的是计算给定日期过去 x 个工作日的日期。例如从今天起 10 个工作日是几号。我还假设您的 baddates 表包含非工作日,例如周末和银行假日。

    我过去也遇到过类似的要求,通常会得到包含所有可能日期的日期表以及指示特定日期是否为工作日的标志。

    然后我使用该表通过选择开始日期后 x 天的记录来计算距提供日期 x 个工作日的日期。

    像这样的

     CREATE TABLE all_days (  
      dated DATETIME,  
      day_state CHAR(1)  
      )
    

    day_state 的值是
    D - 工作日
    W - 周末
    B - 银行假日

    查询x个工作日后的日期的SQL就变成了

    SELECT MAX(dated)
    FROM (
      SELECT TOP(@number_days) dated
      FROM all_days
      WHERE day_state = 'D'
      AND dated >= @start_date
      ORDER by dated ASC
    )
    

    此代码未经测试,但应该可以为您提供总体思路。您可能不想区分周末和公共假期,在这种情况下,您可以将 day_state 重命名为 working_day 并将其设为 BIT 字段。

    您应该在 dated 和 day_state 上创建一个复合唯一索引。

    【讨论】:

    • 就是这样,旧版本的工作方式如前所述。不幸的是,新的要求是只安排糟糕的约会,而不是好的约会。
    • 也许编写该设计规范的人应该检查一下 SO - 我们将奖励他们 20 WTF 积分。
    • 我认为要求与以往一样,但有人希望看到不同的实现,大概是因为维护全天表的痛苦。
    【解决方案5】:

    我没有对此进行测试,但理论上它应该可以工作。我把天数加起来。然后我检查该范围内是否有任何错误。如果有,我会添加糟糕的天数,并检查我刚刚添加的范围内是否还有糟糕的日期。重复直到没有错误的日期。

    CREATE FUNCTION [dbo].[udf_GetDateInBusinessDays]
    (
      @StartDate datetime,  --Start Date
      @NumberDays int           --Good days ahead
    )
    RETURNS datetime
    AS
    BEGIN
    -- Declare the return variable here
    DECLARE @ReturnDate datetime
    SET @ReturnDate = dateadd(d, @NumberDays, @StartDate);
    
    
    DECLARE @d int;
    SET @d = (select count(1) from baddates where startdate >= @StartDate and startdate <= @ReturnDate);
    
    declare @t datetime;
    
    WHILE   @d > 0
    BEGIN
        set @t = @ReturnDate;
        set @ReturnDate = dateadd(d, @d, @ReturnDate);
        SET @d = (select count(1) from baddates where startdate > @t and startdate <= @ReturnDate);
    END
    
    RETURN @ReturnDate
    END
    

    【讨论】:

    • 这对我有用,仍然不是超级快,但在我最糟糕的情况下,我的查询从 6 分钟多到 1 1/2 分钟。非常感谢!!!!!!
    • 欢迎您,我会清理一下。我总是忘记聚合函数总是会返回一行。所以真的不需要 isnull。
    猜你喜欢
    • 1970-01-01
    • 2010-10-13
    • 2011-04-23
    • 2011-07-10
    • 2012-03-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多