在没有循环的情况下在 SQL 中添加迄今为止的工作日

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/5471524/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-01 09:55:35  来源:igfitidea点击:

Add business days to date in SQL without loops

sqldate

提问by Matt King

I currently have a function in my SQL database that adds a certain amount of business days to a date, e.g. if you enter a date that is a Thursday and add two days, it will return the date of the following Monday. I'm not bothered about any holidays, only weekends are excluded.

我目前在我的 SQL 数据库中有一个函数可以将一定数量的工作日添加到某个日期,例如,如果您输入一个星期四的日期并添加两天,它将返回下一个星期一的日期。我不关心任何假期,只有周末被排除在外。

The problem is that this is currently done using a while loop, and it appears to be massively slowing down the stored procedure that uses it while generating a table. Does anyone know if there is any way to perform this calculation without while loops or cursors?

问题是目前这是使用 while 循环完成的,并且它似乎大大减慢了在生成表时使用它的存储过程的速度。有谁知道是否有任何方法可以在没有 while 循环或游标的情况下执行此计算?

Just for information, this is the current function:

仅供参考,这是当前的功能:

ALTER FUNCTION [dbo].[AddWorkDaysToDate]
(   
@fromDate       datetime,
@daysToAdd      int
)
RETURNS datetime
AS
BEGIN   
DECLARE @toDate datetime
DECLARE @daysAdded integer

-- add the days, ignoring weekends (i.e. add working days)
set @daysAdded = 1
set @toDate = @fromDate

while @daysAdded <= @daysToAdd
begin
    -- add a day to the to date
    set @toDate = DateAdd(day, 1, @toDate)
    -- only move on a day if we've hit a week day
    if (DatePart(dw, @toDate) != 1) and (DatePart(dw, @toDate) != 7)
    begin
        set @daysAdded = @daysAdded + 1
    end
end

RETURN @toDate

END

采纳答案by Damien_The_Unbeliever

This answer has been significantly altered since it was accepted, since the original was wrong. I'm more confident in the new query though, and it doesn't depend on DATEFIRST

这个答案自从被接受以来已经有了很大的改变,因为原来是错误的。不过,我对新查询更有信心,而且它不依赖于DATEFIRST



I think this should cover it:

我认为这应该涵盖它:

declare @fromDate datetime
declare @daysToAdd int

select @fromDate = '20130123',@DaysToAdd = 4

declare @Saturday int
select @Saturday = DATEPART(weekday,'20130126')

;with Numbers as (
    select 0 as n union all select 1 union all select 2 union all select 3 union all select 4
), Split as (
    select @DaysToAdd%5 as PartialDays,@DaysToAdd/5 as WeeksToAdd
), WeekendCheck as (
    select WeeksToAdd,PartialDays,MAX(CASE WHEN DATEPART(weekday,DATEADD(day,n.n,@fromDate))=@Saturday THEN 1 ELSE 0 END) as HitWeekend
    from
    Split t
        left join
    Numbers n
        on
            t.PartialDays >= n.n
group by WeeksToAdd,PartialDays
)
select DATEADD(day,WeeksToAdd*7+PartialDays+CASE WHEN HitWeekend=1 THEN 2 ELSE 0 END,@fromDate)
from WeekendCheck

We split the time to be added into a number of weeks and a number of days within a week. We then use a small numbers table to work out if adding those few days will result in us hitting a Saturday. If it does, then we need to add 2 more days onto the total.

我们将要添加的时间分为几周和一周内的几天。然后我们使用一个小数字表来计算添加这几天是否会导致我们到达星期六。如果是这样,那么我们需要在总数上再增加 2 天。

回答by ElmerMiller

This is better if anyone is looking for a TSQL solution. No loops, no tables, no case statements AND works with negatives. Can anyone beat that?

如果有人正在寻找 TSQL 解决方案,这会更好。没有循环,没有表格,没有 case 语句,并且适用于否定。有人能打败它吗?

CREATE FUNCTION[dbo].[AddBusinessDays](@Date date,@n INT)
RETURNS DATE AS 
BEGIN
DECLARE @d INT;SET @d=4-SIGN(@n)*(4-DATEPART(DW,@Date));
RETURN DATEADD(D,@n+((ABS(@n)+@d-2)/5)*2*SIGN(@n)-@d/7,@Date);
END

回答by Nate Cook

Building off of the answer that was accepted for this question, the following user-defined function (UDF) should work in all cases--regardless of the setting for @@DateFirst.

基于此问题已接受的答案,以下用户定义函数 (UDF) 应适用于所有情况——无论@@DateFirst.

UPDATE: As comments below indicate, this function is designed for the FromDate to be a weekday. The behavior is undefined when a weekend day is passed in as the FromDate.

更新:正如下面的评论所示,这个函数是为 FromDate 设计的,它是一个工作日。当周末作为 FromDate 传入时,行为未定义。

ALTER FUNCTION [dbo].[BusinessDaysDateAdd] 
(
   @FromDate datetime,
   @DaysToAdd int
)
RETURNS datetime
AS
BEGIN
   DECLARE @Result datetime

   SET @Result = DATEADD(day, (@DaysToAdd % 5) + CASE ((@@DATEFIRST + DATEPART(weekday, @FromDate) + (@DaysToAdd % 5)) % 7)
                                                 WHEN 0 THEN 2
                                                 WHEN 1 THEN 1
                                                 ELSE 0 END, DATEADD(week, (@DaysToAdd / 5), @FromDate))

   RETURN @Result
END

回答by Juan M. Elosegui

This answers is based on @ElmerMiller's answer.

此答案基于@ElmerMiller 的答案

It fixes the negative value on Sunday comment from @FistOfFury

它修复了@FistOfFury 周日评论的负值

Negative values don't work if the date passed in is Sunday

如果传入的日期是星期日,则负值不起作用

And the DATEFIRST setting comment from @Damien_The_Unbeliever

来自@Damien_The_Unbeliever 的 DATEFIRST 设置评论

But this one does assume a particular DATEFIRST setting (7), which some of the others don't need.

但是这个确实假设了一个特定的 DATEFIRST 设置 (7),而其他一些不需要。

Now the corrected function

现在修正的功能

CREATE FUNCTION[dbo].[AddBusinessDays](@Date DATE,@n INT)
RETURNS DATE AS 
BEGIN
DECLARE @d INT,@f INT,@DW INT;
SET @f=CAST(abs(1^SIGN(DATEPART(DW, @Date)-(7-@@DATEFIRST))) AS BIT)
SET @DW=DATEPART(DW,@Date)-(7-@@DATEFIRST)*(@f^1)+@@DATEFIRST*(@f&1)
SET @d=4-SIGN(@n)*(4-@DW);
RETURN DATEADD(D,@n+((ABS(@n)+(@d%(8+SIGN(@n)))-2)/5)*2*SIGN(@n)-@d/7,@Date);
END

回答by Vick

*I know this is an old thread but found something extremely useful a while ago, modified it and got this.

*我知道这是一个旧线程,但不久前发现了一些非常有用的东西,对其进行了修改并得到了这个。

select ((DATEADD(d,DATEDIFF(d,0,(DATEADD (d,2,@fromDate))),@numbOfDays)))*

Update: I am sorry in a haste to find a piece of code (in a single statement) and to avoid using a function, I posted incorrect code here.

更新:我很抱歉匆忙找到一段代码(在单个语句中)并避免使用函数,我在这里发布了错误的代码。

Bit mentioned above can be used if the number of days you are adding is 7 or less.

如果您添加的天数为 7 或更少,则可以使用上述位。

I have changed the code with required parameters for better understanding.

为了更好地理解,我已使用所需参数更改了代码。

Anyway, I ended up using what 'Nate Cook' has mentioned above. And used it as a single line of code. (Because I am restraining from using functions)

无论如何,我最终使用了上面提到的“Nate Cook”。并将其用作单行代码。(因为我限制使用函数)

Nate's code

内特的代码

select(
DATEADD(day, (@days % 5) + 
CASE ((@@DATEFIRST + DATEPART(weekday, GETDATE()) + (@days % 5)) % 7)
WHEN 0 THEN 2
WHEN 1 THEN 1
ELSE 0 END, DATEADD(week, (@days / 5), GETDATE()))
)

回答by bleeeah

Have you thought about pre-populating a look-up table that contains all of the working days (using your function) , for example WorkingDays(int DaySequenceId, Date WorkingDate), you can then use this table by selecting the DaySequenceId of the @fromDate and add @daysToAdd to get the new working date. Obviously this method also has the additional overhead of administering the WorkingDays table, but you could pre-populate it with the range of dates you expect. The other downside is the working dates that can be calculated will only be those contained within the WorkingDays table.

您是否考虑过预先填充包含所有工作日的查找表(使用您的函数),例如 WorkingDays(int DaySequenceId, Date WorkingDate),然后您可以通过选择 @fromDate 的 DaySequenceId 来使用此表并添加@daysToAdd 以获取新的工作日期。显然,此方法还具有管理 WorkingDays 表的额外开销,但您可以使用您期望的日期范围预先填充它。另一个缺点是可以计算的工作日期只能是 WorkingDays 表中包含的那些。

回答by FistOfFury

To expand on Amine's comment and Nate cook's answer above, the one-liner solution to this is:

为了扩展上面 Amine 的评论和 Nate Cook 的回答,对此的单行解决方案是:

declare @DaysToAdd int , @FromDate datetime 
set @DaysToAdd=-5      --5 days prior is 3/28/14
set @FromDate='4/4/14'
select 
    DATEADD(day, (@DaysToAdd % 5) 
    +   CASE 
        WHEN ((@@DATEFIRST + DATEPART(weekday, @FromDate)) % 7 + (@DaysToAdd % 5)) > 6 THEN 2 
        ELSE 0 
        END
    , DATEADD(week, (@DaysToAdd / 5), @FromDate))

Note you can add or subtract days to go forwards and backwards in time, respectively.

请注意,您可以添加或减去天数以分别在时间上前进和后退。

回答by Arjen

CREATE FUNCTION DateAddBusinessDays
(
    @Days int,
    @Date datetime  
)
RETURNS datetime
AS
BEGIN
    DECLARE @DayOfWeek int;

    SET @DayOfWeek = CASE 
                        WHEN @Days < 0 THEN (@@DateFirst + DATEPART(weekday, @Date) - 20) % 7
                        ELSE (@@DateFirst + DATEPART(weekday, @Date) - 2) % 7
                     END;

    IF @DayOfWeek = 6 SET @Days = @Days - 1
    ELSE IF @DayOfWeek = -6 SET @Days = @Days + 1;

    RETURN @Date + @Days + (@Days + @DayOfWeek) / 5 * 2;
END;

This function can add and subtract business days regardless of the value of @@DATEFIRST. To subtract business days use a negative number of days.

无论@@DATEFIRST 的值如何,此函数都可以添加和减去工作日。要减去工作日,请使用负天数。

回答by Deepak

WITH get_dates
AS
(
    SELECT getdate() AS date, 0 as DayNo 
    UNION ALL
    SELECT date + 1 AS date, case when DATEPART(DW, date + 1) IN (1,7) then DayNo else DayNo + 1 end
    FROM get_dates
    WHERE DayNo < 4
)
SELECT max(date) FROM get_dates
OPTION (MAXRECURSION 0) 

回答by Davin C

I have tested all of the solutions proposed here and none of them work. Here are some test scenarios that broke a lot of the above solutions. (assuming Saturday and Sunday are the days you are excluding):

我已经测试了这里提出的所有解决方案,但没有一个有效。以下是一些测试场景,它们打破了上述许多解决方案。(假设周六和周日是您排除的天数):

-Add 0 days to a Saturday - Expected result = Saturday

- 将 0 天添加到星期六 - 预期结果 = 星期六

-Add 0 days to a Sunday - Expected result = Sunday

- 将 0 天添加到星期日 - 预期结果 = 星期日

-Add 1 day to Friday - Expected result = the following Monday

- 周五增加 1 天 - 预期结果 = 下周一

-Add 1 day to Saturday - Expected result = the following Monday

- 周六增加 1 天 - 预期结果 = 下周一

-Add 1 day to Sunday - Expected result = the following Monday

- 周日增加 1 天 - 预期结果 = 下周一

-Add 3 days to Friday - Expected result = the following Wednesday

- 周五增加 3 天 - 预期结果 = 下周三

-Add 5 days to Saturday - Expected result = the following Friday

- 星期六增加 5 天 - 预期结果 = 下一个星期五

-Add 5 days to Friday - Expected result = the following Friday

- 增加 5 天到星期五 - 预期结果 = 下一个星期五

-Subtract 1 day from Monday - Expected result = the previous Friday

- 从星期一减去 1 天 - 预期结果 = 前一个星期五

-Subtract 1 day from Sunday - Expected result = the previous Friday

- 从星期日减去 1 天 - 预期结果 = 前一个星期五

-Subtract 1 day from Saturday - Expected result = the previous Friday

- 从星期六减去 1 天 - 预期结果 = 前一个星期五

-Subtract 3 days from Monday - Expected result = the previous Wednesday

- 从星期一减去 3 天 - 预期结果 = 前一个星期三

-Subtract 5 days from Saturday - Expected result = the previous Monday

- 从星期六减去 5 天 - 预期结果 = 前一个星期一

-Subtract 5 days from Monday - Expected result = the previous Monday

- 从星期一减去 5 天 - 预期结果 = 前一个星期一

Here is what I wrote after reading this entire thread and picking the good pieces of logic:

这是我在阅读了整个线程并挑选了好的逻辑部分后写的:

CREATE FUNCTION [dbo].[BusinessDateAdd]
(
    @FromDate DATE
    ,@DaysToAdd INT
)
RETURNS DATE 
AS 
BEGIN

    --If there are no days to add or subtract, return the day that was passed in
    IF @DaysToAdd = 0 RETURN @FromDate

    DECLARE @Weeks INT
    DECLARE @DMod INT
    DECLARE @FromDateIndex INT

    --number of weeks
    SET @Weeks = @DaysToAdd/5

    --remainder of days
    SET @dmod = @DaysToAdd%5

    --Get the FromDate day of the week, this logic standardizes the @@DateFirst to Sunday = 1
    SET @FromDateIndex = (DATEPART(weekday, @FromDate) + @@DATEFIRST - 1) % 7 + 1

    /*Splitting the addition vs subtraction logic for readability*/

    --Adding business days
    IF @DaysToAdd > 0 
        BEGIN 

            --If the FromDate is on a weekend, move it to the previous Friday
            IF @FromDateIndex IN(1,7) 
                BEGIN
                    SET @FromDate = DATEADD(dd,CASE @FromDateIndex WHEN 1 THEN -2 WHEN 7 THEN -1 END,@FromDate)
                    SET @FromDateIndex = 6
                END

            SET @FromDate = DATEADD(dd, 
                CASE 
                    --If the mod goes through the weekend, add 2 days to account for it
                    WHEN 
                        ((@FromDateIndex = 3 --Tuesday
                        AND @dmod > 3) --Days until Friday
                        OR
                        (@FromDateIndex = 4  --Wednesday
                        AND @dmod > 2)--Days until Friday
                        OR 
                        (@FromDateIndex = 5 --Thursday
                        AND @dmod > 1)--Days until Friday
                        OR 
                        (@FromDateIndex = 6 --Friday
                        AND @dmod > 0))--Days until Friday
                        THEN 
                            @DMod+2 
                    --Otherwise just add the mod
                    ELSE 
                        @DMod 
                END, @FromDate)

        END

    --Subtracting business days
    IF @DaysToAdd < 0 
        BEGIN 

            --If the FromDate is on a weekend, move it to the next Monday
            IF @FromDateIndex IN(1,7) 
                BEGIN
                    SET @FromDate = DATEADD(dd,CASE @FromDateIndex WHEN 1 THEN 1 WHEN 7 THEN 2 END,@FromDate)
                    SET @FromDateIndex = 2
                END

            SET @FromDate = DATEADD(dd, 
                CASE 
                    --If the mod goes through the weekend, subtract 2 days to account for it
                    WHEN 
                        ((@FromDateIndex = 5 --Thursday
                        AND @dmod < -3) --Days until Monday
                        OR
                        (@FromDateIndex = 4  --Wednesday
                        AND @dmod < -2)--Days until Monday
                        OR 
                        (@FromDateIndex = 3 --Tuesday
                        AND @dmod < -1)--Days until Monday
                        OR 
                        (@FromDateIndex = 2 --Monday
                        AND @dmod < 0))--Days until Monday
                        THEN 
                            @DMod-2 
                    --Otherwise just subtract the mod
                    ELSE 
                        @DMod 
                END, @FromDate)

        END

    --Shift the date by the number of weeks
    SET @FromDate = DATEADD(ww,@Weeks,@FromDate)

    RETURN @FromDate

END