使用WHILE的SQL函数需要很长时间才能运行

时间:2016-07-28 22:18:35

标签: sql sql-server tsql

我创建了一个SQL函数,它接受两个日期,然后查找它们之间的天数,不包括周末和假日。它可以工作,但问题是每行需要1.5秒才能运行。这是一个无赖,因为我们正在为可能长达数千行的报告运行此功能。

对于更多背景知识,cteTally表只是一个通用的计数表。 HLD1表是一个如下所示的表:

Calendar       StrDate    EndDate    
2016Holidays   1/1/16     1/1/16
2016Holidays   5/30/16    5/30/16
2016Holidays   7/4/16     7/4/16
2016Holidays   9/5/16     9/5/16
2016Holidays   11/24/16   11/25/16
2016Holidays   12/26/16   12/26/16

基本上它的意图是,当客户打电话给洗衣机的维修请求时,我们希望跟踪响应时间,但仅计算我们计数中的工作日。因此,例如,如果客户在6月30日下午4:30打电话,并且技术人员在7月5日上午8:30进行维修,那么我们将计算周五7/1(非假期,非周末日期),不计算周六7月2日或周日7/3(周末日),不计算周一7/4(节假日),并计算星期二7/5,以获得过夜的准确计数。

它将返回的答案是2。

为了做到这一点,它会查看功能中的日期并说“当这些天符合这些条件时,添加或减去一天,以便开始或结束日期不是周末或假日。”# 34;

我大约98.36%肯定必须有一个更好/更简单的方法来做到这一点,而且我想要过度思考正在发生的事情,但我无法做到我的生活决定了我能以不同的方式做些什么,而且我现在已经开关3天了。

编辑(在我发布之前):我现在已经把它减少到19秒,因为我正在使用该功能返回14560行。但是,在我修改函数假期之前帐户,它能够在不到一秒的时间内返回所有行。我所做的改变是限制"假期"在开始和结束日期之间返回假期。但是,我认为" WHILE"循环仍然是一些问题。

我还尝试创建一个临时表或一个cte表来存储假期,所以它只需要每行计算一次而不是每行计算4次,但它并没有显示出来在函数中是可能的。

我现在要继续努力,但我会非常乐意为所有人提供帮助。

编辑2:我把所有" SELECT @ variables"它们在同一个select语句中连续而不是具有单独的那些,并且将时间减少了9秒,持续了14000行。但是,如果可能的话,我仍然希望在2秒的屏障下,或者至少5秒的屏障。

这是函数的文本:

CREATE FUNCTION [dbo].[dateDiffHolidays] (
@startdaytime DATETIME,
@enddaytime DATETIME
)
RETURNS INT
AS
BEGIN

DECLARE @answer INT;
DECLARE @START Date;
DECLARE @END Date;
DECLARE @AddDays int;

SET @answer = 0

-- Strip Times
SELECT @START = dateadd(dd,0, datediff(dd,0,@StartDayTime))
SELECT @END = dateadd(dd,0, datediff(dd,0,@EndDayTime))
SELECT @AddDays = count(*) from (SELECT 
dateadd(dd,ctetally.n-1,@START) date1
from
cteTally
where dateadd(dd,ctetally.n-1,@START) <= @END) s1 where s1.date1 in (select cast( DATEADD(day, t.N - 1, StrDate) as date) as ResultDate 
from HLD1 s join cteTally t on t.N <= DATEDIFF(day, StrDate, EndDate) + 1)
or datepart(dw,s1.date1) in (1,7) 

-- handle end conditions
DECLARE @firstWeekDayInRange datetime, @lastWeekDayInRange datetime;
SET @firstWeekDayInRange = @START
set @lastWeekDayInRange = @END


WHILE @firstWeekDayInRange in (select cast( DATEADD(day, t.N - 1, StrDate) as date) as ResultDate 
from HLD1 s join cteTally t on t.N <= DATEDIFF(day, StrDate, EndDate) + 1)
or datepart(dw,@firstWeekDayInRange) in (1,7) 

BEGIN

SELECT @firstWeekDayInRange =
CASE
WHEN @firstWeekDayInRange in (select cast( DATEADD(day, t.N - 1, StrDate) as date) from HLD1 s join cteTally t on t.N <= DATEDIFF(day, StrDate, EndDate) + 1)
or datepart(dw,@firstWeekDayInRange) in (1,7) 
THEN dateadd(DAY,1,@firstWeekDayInRange)
ELSE @firstWeekDayInRange
END

END 


WHILE @lastWeekDayInRange in (select cast( DATEADD(day, t.N - 1, StrDate) as date) as ResultDate 
from HLD1 s join cteTally t on t.N <= DATEDIFF(day, StrDate, EndDate) + 1)
or datepart(dw,@lastWeekDayInRange) in (1,7) 

BEGIN

SELECT @lastWeekDayInRange =
CASE
WHEN @lastWeekDayInRange in (select cast( DATEADD(day, t.N - 1, StrDate) as date) from HLD1 s join cteTally t on t.N <= DATEDIFF(day, StrDate, EndDate) + 1)
or datepart(dw,@lastWeekDayInRange) in (1,7) 
THEN dateadd(DAY,-1,@lastWeekDayInRange)
ELSE @lastWeekDayInRange
END
END 




-- add one day to answer (to count Friday) if enddate was on a weekend

SELECT @answer = @answer +
CASE
-- triggered if start and end date are on same weekend
WHEN dateDiff(DAY,@firstWeekDayInRange,@lastWeekDayInRange) < 0 THEN (@answer * -1)
-- otherwise count the days and substract 2 days per weekend in between dates
ELSE (DateDiff(DAY, @firstWeekDayInRange, @lastWeekDayInRange) - @AddDays)
END


RETURN @answer
END 

GO

2 个答案:

答案 0 :(得分:1)

也许这会有所帮助。

Declare @HLD1 table (Calendar varchar(50),StrDate Date,EndDate Date)
Insert Into @HLD1 values
('2016Holidays','1/1/16','1/1/16'),
('2016Holidays','5/30/16','5/30/16'),
('2016Holidays','7/4/16','7/4/16'),
('2016Holidays','9/5/16','9/5/16'),
('2016Holidays','11/24/16','11/25/16'),
('2016Holidays','12/26/16','12/26/16')


Declare @Date1 Date = '2016-06-30 16:30:00'
Declare @Date2 Date = '2016-07-05 08:30:00'

Select DateDiff(DD,@Date1,@Date2)-sum(Excl)
 From (
        Select RetVal,Excl=max(Excl)
          From (
                Select *,Excl=IIF(DatePart(WEEKDAY,RetVal) in (7,1),1,0)  From [dbo].[udf-Create-Range-Date](@Date1,@Date2,'DD',1)
                Union All
                Select RetVal=StrDate,Excl=1 From @HLD1 Where StrDate Between @Date1 and @Date2
               ) A
         Group By RetVal
      ) A

返回

2
  

你仍然可以使用你的计数表,但我更喜欢我的UDF来创建   动态日期范围

CREATE FUNCTION [dbo].[udf-Create-Range-Date] (@DateFrom datetime,@DateTo datetime,@DatePart varchar(10),@Incr int)

Returns 
@ReturnVal Table (RetVal datetime)

As
Begin
    With DateTable As (
        Select DateFrom = @DateFrom
        Union All
        Select Case @DatePart
               When 'YY' then DateAdd(YY, @Incr, df.dateFrom)
               When 'QQ' then DateAdd(QQ, @Incr, df.dateFrom)
               When 'MM' then DateAdd(MM, @Incr, df.dateFrom)
               When 'WK' then DateAdd(WK, @Incr, df.dateFrom)
               When 'DD' then DateAdd(DD, @Incr, df.dateFrom)
               When 'HH' then DateAdd(HH, @Incr, df.dateFrom)
               When 'MI' then DateAdd(MI, @Incr, df.dateFrom)
               When 'SS' then DateAdd(SS, @Incr, df.dateFrom)
               End
        From DateTable DF
        Where DF.DateFrom < @DateTo
    )

    Insert into @ReturnVal(RetVal) Select DateFrom From DateTable option (maxrecursion 32767)

    Return
End

-- Syntax Select * from [dbo].[udf-Create-Range-Date]('2016-10-01','2020-10-01','YY',1) 
-- Syntax Select * from [dbo].[udf-Create-Range-Date]('2016-10-01','2020-10-01','DD',1) 
-- Syntax Select * from [dbo].[udf-Create-Range-Date]('2016-10-01','2016-10-31','MI',15) 
-- Syntax Select * from [dbo].[udf-Create-Range-Date]('2016-10-01','2016-10-02','SS',1) 

答案 1 :(得分:0)

标量UDF可能很慢:http://dataeducation.com/scalar-functions-inlining-and-performance-an-entertaining-title-for-a-boring-post/

如果您可以将其转换为值为UDF的内联表,则查询可能会更快。虽然查看了您的UDF代码,但可能不容易/不容易。