在SQL中添加工作日,无需循环

时间:2011-03-29 11:07:04

标签: sql date

我目前在我的SQL数据库中有一个函数,它在一个日期中添加了一定的工作日,例如如果您输入星期四的日期并添加两天,它将返回下一个星期一的日期。我对任何假期都不感到烦恼,只有周末才被排除在外。

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

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

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

25 个答案:

答案 0 :(得分:29)

如果有人在寻找TSQL解决方案,那就更好了。没有循环,没有表格,没有案例陈述和负面作品。谁能打败那个?

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

答案 1 :(得分:15)

这个答案自被接受后发生了重大变化,原因是错误的。我对新查询更有信心,并且它不依赖于DATEFIRST


我认为这应该涵盖它:

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

我们将时间分成若干周和一周内的几天。然后,我们使用一个小数字表来计算,如果添加这几天将导致我们打一个星期六。如果确实如此,那么我们需要再增加2天。

答案 2 :(得分:7)

根据此问题接受的答案,以下用户定义函数(UDF)应该适用于所有情况 - 无论@@DateFirst的设置如何。

更新:如下面的评论所示,此功能是为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

答案 3 :(得分:6)

此答案基于@ElmerMiller's answer

它修复了来自@FistOfFury

的星期日评论的负值
  

如果传入的日期是星期日

,则负值不起作用

来自@Damien_The_Unbeliever的DATEFIRST设置评论

  

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

现在已更正的功能

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

答案 4 :(得分:5)

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

答案 5 :(得分:4)

*我知道这是一个旧线程,但前段时间发现了一些非常有用的东西,修改了它并得到了它。

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

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

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

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

无论如何,我最终使用了'Nate Cook'上面提到的内容。并将其用作单行代码。 (因为我禁止使用功能)

Nate的代码

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()))
)

答案 6 :(得分:2)

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;

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

答案 7 :(得分:1)

我目前没有测试Sql Server,但这是个主意:

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

set datefirst 1
set @toDate = dateadd(day, @daysToAdd, @fromDate)
set @dw = datepart(dw, @toDate)

if @dw > 5 set @toDate = dateadd(day, 8 - @dw, @toDate)

RETURN @toDate

END

答案 8 :(得分:1)

5*sizeof(int)

答案 9 :(得分:1)

这是一个旧线程,但是我只是创建了一个包含所有日期的表,然后执行了此操作:

SELECT Count(*) 
FROM Date_Table
WHERE [day] BETWEEN @StartDate and @EndDate
    AND DATENAME(weekday, [day]) NOT IN ('Sunday', 'Saturday')

答案 10 :(得分:1)

我已经测试了这里提出的所有解决方案,但没有一个能够正常工作。 以下是一些打破了上述许多解决方案的测试场景。 (假设星期六和星期日是你排除的日子):

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

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

- 添加1天到星期五 - 预期结果=下周一

- 添加1天到星期六 - 预期结果=下周一

- 添加1天到星期日 - 预期结果=下周一

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

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

- 星期五增加5天 - 预期结果=下周五

- 从星期一开始收取1天 - 预期结果=上周五

- 从星期日开始收取1天 - 预期结果=上周五

- 从星期六开始收取1天 - 预期结果=上周五

- 从星期一开始3天 - 预期结果=上周三

- 从星期六开始5天 - 预期结果=上周一

- 从星期一开始5天 - 预期结果=上周一

以下是我在阅读完整个帖子并选择好的逻辑后写的内容:

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

答案 11 :(得分:1)

为了扩展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))

请注意,您可以分别按时间添加或减去天数。

答案 12 :(得分:1)

这就是我使用的:

SET DATEFIRST 1;

SELECT DATEADD(dw, (**NumberToAdd**/5)*7+(**NumberToAdd** % 5) + 
(CASE WHEN DATEPART(dw,**YourDate**) + (**NumberToAdd** % 5) > 5 
THEN 2 ELSE 0 END), **YourDate**) AS IncrementedDate
FROM YourTable t

“SET DATEFIRST 1;”将星期一设定为一周的第一天是必要的。

答案 13 :(得分:1)

问题的接受答案会产生错误的结果。例如。 select @fromDate = '03-11-1983', @DaysToAdd = 3会产生03-14-198303-16-1983expected

我发布了一个有效的解决方案here,但为了完整起见,我还会在此处添加。如果您对这两种方法的细节感兴趣,请访问我的原始答案。如果没有,只需将其复制/添加到您的SQL项目中并使用UTL_DateAddWorkingDays

请注意,仅当DATEFIRST设置为默认值7时,我的解决方案才有效。

Test Script used to test various methods

CREATE FUNCTION [dbo].[UTL_DateAddWorkingDays]
(   
    @date datetime,
    @days int
)
RETURNS TABLE AS RETURN 
(
    SELECT 
        CASE 
            WHEN @days = 0 THEN @date
            WHEN DATEPART(dw, @date) = 1 THEN (SELECT Date FROM [dbo].[UTL_DateAddWorkingDays_Inner](DATEADD(d, 1, @date), @days - 1))
            WHEN DATEPART(dw, @date) = 7 THEN (SELECT Date FROM [dbo].[UTL_DateAddWorkingDays_Inner](DATEADD(d, 2, @date), @days - 1))
            ELSE (SELECT Date FROM [dbo].[UTL_DateAddWorkingDays_Inner](@date, @days))
        END AS Date
)

CREATE FUNCTION [dbo].[UTL_DateAddWorkingDays_Inner]
(   
    @date datetime,
    @days int
)
RETURNS TABLE AS RETURN 
(
    SELECT 
        DATEADD(d
        , (@days / 5) * 7 
          + (@days % 5) 
          + (CASE WHEN ((@days%5) + DATEPART(dw, @date)) IN (1,7,8,9,10) THEN 2 ELSE 0 END)
        , @date) AS Date
)

答案 14 :(得分:1)

感谢Damien的代码。在计算中存在轻微错误,因为它在周日仅增加了1天,并且当工作日数超过周末(但没有在周末降落)时,额外的2天没有被考虑在内。这是Damiens代码的修改版本,可以使用默认的datefirst 7.希望这会有所帮助。

CREATE FUNCTION [dbo].[fn_AddBusinessDays]  
(  
    @StartDate datetime,  
    @BusinessDays int  
) 
RETURNS datetime  
AS  
BEGIN 
DECLARE @EndDate datetime

SET @EndDate = DATEADD(day, @BusinessDays%5 + 
           CASE         
        WHEN DATEPART(weekday,@StartDate) +  @BusinessDays%5 > 6 THEN 2                  
        ELSE 0 
           END,     
   DATEADD(week,@BusinessDays/5,@StartDate))    

   RETURN @EndDate
END  
GO

答案 15 :(得分:0)

我从Microsoft Docs找到了一种更为优雅的方法。它考虑了跳过多个周末。超级干净。

CREATE FUNCTION DAYSADDNOWK(@addDate AS DATE, @numDays AS INT)
RETURNS DATETIME
AS
BEGIN
    WHILE @numDays>0
    BEGIN
       SET @addDate=DATEADD(d,1,@addDate)
       IF DATENAME(DW,@addDate)='saturday' SET @addDate=DATEADD(d,1,@addDate)
       IF DATENAME(DW,@addDate)='sunday' SET @addDate=DATEADD(d,1,@addDate)

       SET @numDays=@numDays-1
    END

    RETURN CAST(@addDate AS DATETIME)
END
GO

运行测试

SELECT dbo.DAYSADDNOWK(GETDATE(), 15)

答案 16 :(得分:0)

- 重构我的原始答案...如果开始日期恰好是周末,我添加了定义计算起点的选项:从周末开始或转移到最近的工作日取决于三角洲的方向。

DECLARE
    @input DATE = '2019-06-15', -- if null, then returns null
    @delta INT = 1, -- can be positive or negative; null => zero
    @startFromWeekend BIT = 1 -- null => zero

-- input is null, delta is zero/null
IF @input IS NULL OR ISNULL(@delta, 0) = 0
    SELECT @input

-- input is not null and has delta
ELSE
BEGIN
    DECLARE
        @input_dw INT = (DATEPART(DW, @input) + @@DATEFIRST - 1) % 7, -- input day of week
        @weeks    INT = @delta / 5, -- adjust by weeks
        @days     INT = @delta % 5  -- adjust by days

    -- if input is a weekend day, offset it for proper calculation
    -- !!important!!: depends on *your* definition of the starting date to perform calculation from
    DECLARE @offset INT =
        -- start calc from weekend day that is nearest to a weekday depending on delta direction
        -- pos delta: effectively Sunday of the weekend   (actual: prev Friday)
        -- neg delta: effectively Saturday of the weekend (actual: next Monday)
        CASE WHEN ISNULL(@startFromWeekend, 0) = 1
        THEN CASE WHEN @delta > 0
            THEN CASE @input_dw
                WHEN 0 THEN -2
                WHEN 6 THEN -1
                END
            ELSE CASE @input_dw
                WHEN 0 THEN  1
                WHEN 6 THEN  2
                END
            END
        -- start calc from nearest weekday depending on delta direction
        -- pos delta: next Monday from the weekend
        -- neg delta: prev Friday from the weekend
        ELSE CASE WHEN @delta > 0
            THEN CASE @input_dw
                WHEN 0 THEN  1
                WHEN 6 THEN  2
                END
            ELSE CASE @input_dw
                WHEN 0 THEN -2
                WHEN 6 THEN -1
                END
            END
        END

    -- calculate: add weeks, add days, add initial correction offset
    DECLARE @output DATE = DATEADD(DAY, @days + ISNULL(@offset, 0), DATEADD(WEEK, @weeks, @input))

    -- finally, if output is weekend, add final correction offset depending on delta direction
    SELECT
        CASE WHEN (DATEPART(DW, @output) + @@DATEFIRST - 1) % 7 IN (0,6)
        THEN CASE 
            WHEN @delta > 0 THEN DATEADD(DAY,  2, @output)
            WHEN @delta < 0 THEN DATEADD(DAY, -2, @output)
            END
        ELSE @output
        END
END

答案 17 :(得分:0)

我找不到我能理解的令人满意的解决方案,所以我最终大部分时间自己写了一个书。从结构上类似于Damien_The_Unbeliever's answer开始,但是由于我无法正常工作而出现了很大的分歧。

我的要求

  • 我正在使用Redshift,所以必须在那里工作。
  • 负天数输入必须有效(例如,添加-1或-12个工作日)。
  • 当输入日期为工作日时(例如,星期一+ 2→星期三;星期二-4→以前的星期三),输出必须正确。
  • 解决方案必须记录在案。

必备品

  • 周末的正常产量。 (我选择将周末休整到下一个星期一,例如周日+ 1 ==星期一+ 1 ==周二。)

解决方案

注意:我的公司将Periscope Data用于BI,它具有类似于C宏的语法糖来定义称为“代码段”的内联文本替换(请参见docs)。不过,应该可以轻松地将其翻译为纯SQL -如果您完成了翻译,请随时建议对我的答案进行修改。

摘录:add_business_days(date,num_days)

(dateadd(
  day
  , (
    7 * (([num_days]) / 5)                                    -- add whole weeks
    + (([num_days]) % 5)                                      -- add remaining days after taking out whole weeks
    + case when (                                             -- if (
        extract(dow from [roll_forward_to_weekday("[date]")]) --   day of week of "rolled forward" date (i.e. weekends → Monday)
         + (([num_days]) % 5)                                 --   + remaining days after taking out whole weeks
        not between 1 and 5                                   --   is NOT a weekday of the same week
      )                                                       -- )
        then sign([num_days])::int * 2                        -- then increase magnitude of num_days by 2 to jump over the weekend
        else 0
      end
  )                                                           -- start from the "rolled forward" date because adding business days to ..
  , [roll_forward_to_weekday("[date]")]                       -- Saturday or Sunday is equivalent to adding them to the following Monday.
                                                              -- (note: due to ^, add_business_days(Saturday or Sunday,0) == Monday)
))

摘录:roll_forward_to_weekday(date)

(dateadd(
  day
  , case extract(dayofweek from([date]))
      when 6 /* Saturday */ then 2
      when 0 /* Sunday   */ then 1
                            else 0
    end
  , ([date])
))

答案 18 :(得分:0)

叹息。在这几十年之后,我无法相信仍然没有:a)标准&#34; DateAddWorkDays&#34;在Microsoft SQL Server中(即使Microsoft在Excel中永远有WorkDay函数)和b)在这里或其他任何我能找到的处理人们提出的所有问题的清晰解决方案。

这是我开发的一个解决方案,它解决了以下问题,这些问题似乎在这里以及我能够找到的其他地方有一个或多个问题。这处理:

  1. 助记符标识符名称。
  2. 评论解释不清楚的代码。
  3. 不检查每个需要递增的工作日(即 比O(n)复杂度低得多。
  4. 负工作日增量。
  5. 允许非上午12点的时间部分传入(所以你不必先取消它)。
  6. 在结果中保留传入的时间部分(如果有的话)(如果您需要提前x个工作日的确切时间)。
  7. 周末以英语以外的语言命名。
  8. @@ DateFirst除默认值之外的值(7又称美国)。
  9. 指定非周末非工作日的自定义列表。
  10. 允许非周末非工作日列表,如果传入日期为非上午12点。
  11. 如果#工作日增量为0,则返回开始日期时间,即使开始日期时间是非工作日。
  12. 分别在开始增加/减少工作日之前,先移至下一个/上一个工作日。注意:这与Excel的WorkDay功能不同,但我相信这更有用,更直观。防爆。如果您在周末获得查询/订单,并且您有1个工作日的SLA(即响应时间,交付日期),则您不应该在1个完整工作日之前回复/交付(无论如何)在其之前有多少相邻的非工作日)。
  13. 在添加任何非工作工作日之后可能已经跨越的任何额外周末和/或非工作工作日,当添加单独工作日数时添加初始周末时,可能已跨越 - 并重复直至否更长的必要。
  14. 建议:当然,与任何递归算法一样,这个算法可以转换为迭代算法(通过实现自己的堆栈,即使用临时表),但我认为32个嵌套级别绰绰有余绝大多数现实世界的用例。当然,通过将非工作工作日日期作为表值参数与硬编码表引用相结合,您可以使其更通用/可移植。

    SET ANSI_NULLS ON
    GO
    
    SET QUOTED_IDENTIFIER ON
    GO
    
    -- ===================================================================================================================================
    -- Author:      Tom
    -- Create date: 03/13/2017
    -- Description: Add specified # of working days (+/-) to a specified date-time assuming existence of a list of non-work weekday 
    --  dates (incl. holidays, weather days, utility outage days, fire days, etc.) in the 'NonWorkDayDate' Column of a 'NonWorkWeekday' 
    --  Table. If specified # working days is 0, the specified date-time is returned.  Working days are not added until the specified 
    --  date-time has first been incremented (+/-) to the next working day in the direction of the working days increment.
    --  NOTE: Uses a forumla (vs. O(n) loop) that uses recusion whenever days incremented (incl. weekends) spans non-work weekdays.
    --  !!!WARNING!!!: Will exceed SQL Server nesting level (32) if abs (# of working days) < ~1 / 32 adjacent non-working days.
    -- Parameters:
    --  @RefDateTime    DateTime:   Reference date-time to which to add '@WorkDaysIncrement'.
    --  @WorkDaysIncrement  Int:    # of working days (+/-) to add # to the '@RefDateTime'.
    -- Returns:
    --  1. Result of @RefDateTime + @WorkDaysIncrement (skipping weekend and holiday dates and retaining the @RefDateTime's time).
    -- ===================================================================================================================================
    CREATE FUNCTION [dbo].[AddWorkDays_Recursive] 
    (
        -- Add the parameters for the function here
        @RefDateTime datetime,
        @WorkDaysIncrement int
    )
    RETURNS DateTime
    AS
    BEGIN
    
    -- If no days to increment, return passed in date-time (even if weekend day).
        if (@WorkDaysIncrement = 0) return @RefDateTime
    
    -- Set the one-day increment used to add or subtract one calendar/work day.
        declare @OneDayIncrement int = sign(@WorkDaysIncrement)
    
    -- Initialize # of calendar days added to 0.
        declare @DaysAdded int = 0
    
    -- Set reference date to date (i.e. excl. time) of reference date-time.
        declare @RefDate datetime = convert
            (
                date,
                convert
                (
                    varchar(10),
                    @RefDateTime,
                    101
                )
            ) 
        --end declare @RefDate 
    
    -- Initialize result date to reference date
        declare @ResultDate datetime = @RefDate
    
    -- Set U.S. Weekday # to the 1-based U.S. weekday # result date.
        declare @USWeekdayNumber tinyint = ((datepart(weekday, @ResultDate) + @@datefirst - 1) % 7) + 1 -- Sun to Sat = 1 to 7
    
    -- If result date is now on a weekend day, set #  of weekend days increment so that we can move it +/- 1 to 2 days to next weekday.
        declare @WeekendDaysInc smallint = 
        (
            case (@USWeekdayNumber)
    
                when 1 then --Sunday 
                    case
                        when (@OneDayIncrement > 0) then 1
                        else -2 
                    end 
                --end when 1 --Sunday
    
                when 7 then --Saturday 
                    case
                        when (@OneDayIncrement > 0) then 2
                        else -1
                    end 
                --end when 7 then --Saturday 
    
                else 0 -- Not Weekend Day #
    
            end -- case (@USWeekdayNumber)
        ) -- end declare @WeekendDaysInc smallint = 
    
    -- Increment # of calendar days added by #  of weekend days increment
        set @DaysAdded += @WeekendDaysInc
    
    -- Increment result date by #  of weekend days increment
        set @ResultDate += @WeekendDaysInc 
    
    -- Set # of work weeks increment to # of full 5-day increments in the # (+/-) of work days to increment.
        declare @WorkWeeksIncrement int = @WorkDaysIncrement / 5
    
    -- Increment # of calendar days added by 7 times # of work weeks increment, i.e. to add weekday + weekend days for full weeks.
        set @DaysAdded += @WorkWeeksIncrement * 7
    
    -- Set result date after full weeks added to reference date + # of calendar days 
        declare @AfterFullWeeksResultDate datetime = @ResultDate + @DaysAdded
    
    -- Set # partial-work week days to # (+/-) of work days to increment left after adding full weeks.
        declare @PartialWorkWeekDays int = @WorkDaysIncrement % 5
    
    -- Increment # of calendar days added by # partial-work week days
        set @DaysAdded += @PartialWorkWeekDays
    
    -- Set result date after partial week added to result date after full weeks added + # partial work week days
        declare @AfterPartialWeekResultDate datetime = @AfterFullWeeksResultDate + @PartialWorkWeekDays
    
    --Set result date to result date after partial week.
        set  @ResultDate = @AfterPartialWeekResultDate
    
    -- Set After Full Weeks U.S. Weekday # to the 1-based U.S. weekday # result date.
        declare @AfterFullWeeksUSWeekdayNumber tinyint = 
            (
                ((datepart(weekday, @AfterFullWeeksResultDate) + @@datefirst - 1) % 7) + 1 -- Sun to Sat = 1 to 7
            )
    
    -- Set After Partial Week U.S. Weekday # to the 1-based U.S. weekday # result date.
        declare @AfterPartialWeekUSWeekdayNumber tinyint = 
            (
                ((datepart(weekday, @AfterPartialWeekResultDate) + @@datefirst - 1) % 7) + 1 -- Sun to Sat = 1 to 7
            )
    
    --If (incrementing and After Full Weeks U.S. Weekday # > @AfterPartialWeekUSWeekdayNumber) 
    --  or (decrementing and After Full Weeks U.S. Weekday # < @AfterPartialWeekUSWeekdayNumber), increment by (+/-) 2 to account for 
    --  the weekend that was spanned when partial-work week days were added.
        if 
        (
            (
                (@OneDayIncrement > 0)
                and (@AfterFullWeeksUSWeekdayNumber > @AfterPartialWeekUSWeekdayNumber)
            )
            or (
                (@OneDayIncrement < 0)
                and (@AfterFullWeeksUSWeekdayNumber < @AfterPartialWeekUSWeekdayNumber)
            )
    
        )
        begin
            set @WeekendDaysInc = 2 * @OneDayIncrement
            set @DaysAdded += @WeekendDaysInc
            set @ResultDate += @WeekendDaysInc
        end -- if need to increment to account for weekend spanned by partial-work week days,
    
    -- Set U.S. Weekday # to the 1-based U.S. weekday # result date.
        set @USWeekdayNumber = ((datepart(weekday, @ResultDate) + @@datefirst - 1) % 7) + 1 -- Sun to Sat = 1 to 7
    
    -- If result date is now on a weekend day, set #  of weekend days increment so that we can move it +/- 1 to 2 days to next weekday.
        set @WeekendDaysInc = 
        (
            case (@USWeekdayNumber)
    
                when 1 then --Sunday 
                    case
                        when (@OneDayIncrement > 0) then 1
                        else -2 
                    end 
                --end when 1 --Sunday
    
                when 7 then --Saturday 
                    case
                        when (@OneDayIncrement > 0) then 2
                        else -1
                    end 
                --end when 7 then --Saturday 
    
                else 0 -- Not Weekend Day #
    
            end -- case (@USWeekdayNumber)
        ) -- end declare @WeekendDaysInc smallint = 
    
    -- Increment # of calendar days added by #  of weekend days increment
        set @DaysAdded += @WeekendDaysInc
    
    -- Increment result date by #  of weekend days increment
        set @ResultDate += @WeekendDaysInc 
    
    -- Set non-work weedays count to # Rows where NonWorkDayDate between RefDate and ResultDate (if # of work days to increment > 0), else between 
    --  ResultDate and RefDate.
        declare @NonWorkWeekdaysCount int =
        (
            select count(nw.NonWorkDayDate) 
                from NonWorkWeekday as nw
                where 
                    (
                        (@OneDayIncrement > 0)
                        and (nw.NonWorkDayDate between @RefDate and @ResultDate)
                    )
                    or (
                        (@OneDayIncrement < 0)
                        and (nw.NonWorkDayDate between @ResultDate and @RefDate)
                    )
            --end select count(nw.NonWorkDayDate) from Holidate as nw 
        ) -- end declare @HolidaysSpanned int =
    
    -- Set result date-time to reference date-time + # of calendar days added
        declare @ResultDateTime datetime = @RefDateTime + @DaysAdded 
    
    -- Set result date-time equal to result of adding (# of holidays x one-day increment).
        set @ResultDateTime = dbo.AddWorkDays_Recursive
            (
                @ResultDateTime, -- @RefDateTime
                @NonWorkWeekdaysCount * @OneDayIncrement -- @WorkDaysIncrement
            )
        --end set @ResultDateTime = 
    
        -- Return the result of the function
        RETURN @ResultDateTime
    
    END
    
    GO
    

答案 19 :(得分:0)

我刚刚测试了接受的答案,发现当星期天是开始日时它不起作用。

您需要在Select @Saturday订单项下添加以下内容:

SELECT @fromDate = CASE WHEN DATEPART(weekday,@fromDate) = 1 THEN DATEADD(day,1,@fromDate) ELSE @fromDate END

答案 20 :(得分:0)

我参加这个派对有点晚了,但由于其他解决方案的缺点,我最终写了我自己的版本。具体来说,这个版本解决倒计时,并从周末开始。

如果您将零工作日添加到周末日期,则可能会出现模糊不清的情况。我保持相同的日期,但如果你总是想要强制退回工作日,你可以省略这个检查。

CREATE FUNCTION [dbo].[fn_AddBusinessDays]
(
    @date datetime,
    @businessDays int
)
RETURNS datetime
AS
BEGIN
    --adjust for weeks first
    declare @weeksToAdd int = @businessDays / 7
    declare @daysToAdd int = @businessDays % 7

    --if subtracting days, subtract a week then offset
    if @businessDays < 0 begin
        set @daysToAdd = @businessDays + 5
        set @weeksToAdd = @weeksToAdd - 1
    end

    --saturday becomes zero using the modulo operator
    declare @originalDayOfWeek int = datepart(dw, @date) % 7
    declare @newDayOfWeek int = datepart(dw, dateadd(d, @daysToAdd, @date)) % 7

    --special case for when beginning date is weekend
    --adding zero on a weekend keeps the same date. you can remove the <> 0 check if you want Sunday + 0 => Monday
    declare @dateOffset int = case
        when @businessDays <> 0 and @originalDayOfWeek = 0 then 2
        when @businessDays <> 0 and @originalDayOfWeek = 1 then 1
        when @businessDays <> 0 and @newDayOfWeek < @originalDayOfWeek then 2
        else 0
    end

    -- Return the result of the function
    return dateadd(d, @daysToAdd + @dateOffset, dateadd(ww, @weeksToAdd, @date))

END

答案 21 :(得分:0)

德国的所有答案都不起作用。

我测试和工作的唯一功能是从旧的Excel表单here翻译:

#include <iostream>
#include <string>
#include <vector>
#include <stdio.h>
#include <sstream>


using namespace std;

template <int num_rows, int num_cols, int patR, int patC>
int search_for_pattern(int (&grid)[num_rows][num_cols], int indexR, int indexC, int (&pattern)[patR][patC]){
    for(int m = 0, i = indexR; m < patR && i < num_rows; m++, i++){
        for(int n = 0, j = indexC; n < patC && j < num_cols; n++, j++){
            if(grid[i][j] != pattern[m][n]){
                return 0;
            }
        }
    }
    return 1;
}

int main(){


  int T;
        cin>>T;
    while(T--){
        int R, C;
        cin>>R>>C;
        string* arry = new string[R];
        int** int_arry = new int*[R];
        for(int i = 0; i < R; ++i)
            int_arry[i] = new int[C];

        for(int i = 0; i < R; i++){
           getline(cin>>ws, arry[i]); //cin >> ws gets rid of leading whitespace
                                       //first so that getline won't think that it's already
                                       //reached the end of the line
                                      //It doesn't seem to take the above input string
        }
       for(int i = 0; i < R; i++){
        for(int j = 0; j < arry[i].length(); j++){
            int_arry[i][j] = (int)(arry[i][j] - '0');
            //cout<<int_arry[i][j]<<" ";
        }
        //cout<<endl;
       }

        //Pattern Array input 
        int pattern_R, pattern_C;
        cin>>pattern_R>>pattern_C;

        string* pattern_arry = new string[pattern_R];
        int** int_pattern_arry = new int*[pattern_R];
        for(int i = 0; i < pattern_R; ++i)
            int_pattern_arry[i] = new int[pattern_C];

        for(int i = 0; i < pattern_R; i++){
           getline(cin>>ws, pattern_arry[i]); 
        }

        for(int i = 0; i < pattern_R; i++){
        for(int j = 0; j < pattern_arry[i].length(); j++){
            int_pattern_arry[i][j] = (int)(pattern_arry[i][j] - '0');
           // cout<<int_pattern_arry[i][j]<<" ";
        }
        //cout<<endl;
       }

        int flag = 0, patternTrue = 0;
        for(int i = 0; i <= R - pattern_R; i++){
            for(int j = 0; j <= C - pattern_C; j++){
                if(int_arry[i][j] == int_pattern_arry[i][j]){
                        flag = 1;
                        patternTrue = search_for_pattern(int_arry, i, j, int_pattern_arry);
                        if(patternTrue){
                            cout<<"YES";
                            break;
                        }
                    }
                }
            if(patternTrue) break;
            cout<<endl;

        }

        if(flag == 0){
            cout<<"NO";
        }

       //Delete Grid 
       for(int i = 0; i < R; ++i) {
        delete [] int_arry[i];
    }
    delete [] int_arry;
    delete [] arry;

        //Delete Pattern
        for(int i = 0; i < pattern_R; ++i) {
        delete [] int_pattern_arry[i];
    }
    delete [] int_pattern_arry;
    delete [] pattern_arry;
        cout<<endl;
   }
  return 0;
}

答案 22 :(得分:0)

此SQL函数的工作方式类似于Excel WORKDAY函数。 希望它可以帮到你。

CREATE FUNCTION [dbo].[BusDaysDateAdd] 
(
   @FromDate date,
   @DaysToAdd int
)
RETURNS date
AS
BEGIN
   DECLARE @Result date
   DECLARE @TempDate date
   DECLARE @Remainder int
   DECLARE @datePartValue int

   SET @TempDate = (DATEADD(week, (@DaysToAdd / 5), @FromDate))
   SET @Remainder = (@DaysToAdd % 5)
   SET @datePartValue = DATEPART(weekday, @TempDate)
   SET @Result = DATEADD(day,@Remainder + CASE WHEN @Remainder > 0 AND @datePartValue = 7 THEN 1
                                                WHEN @Remainder >= 1 AND @datePartValue = 6 THEN 2
                                                WHEN @Remainder >= 2 AND @datePartValue = 5 THEN 2
                                                WHEN @Remainder >= 3 AND @datePartValue = 4 THEN 2
                                                WHEN @Remainder >= 4 AND @datePartValue = 3 THEN 2
                                                WHEN @Remainder >= 5 AND @datePartValue = 2 THEN 2
                                                ELSE 0 END, @TempDate)
   RETURN @Result
END
GO

Reference

答案 23 :(得分:0)

我知道这有点晚了,也许其他人偶然发现了这个问题。 我已经尝试了上述解决方案但是,大多数都无法计算假期。

这是我试过的方式

CREATE function [dbo].[DateAddWorkDay]
(@days int,@FromDate Date)
returns Date
as
begin
declare @result date
set @result = (
select b
from
(
    SELECT
    b,
       (DATEDIFF(dd, a, b))
      -(DATEDIFF(wk, a, b) * 2)
      -(CASE WHEN DATENAME(dw, a) = 'Sunday' THEN 1 ELSE 0 END)
      -(CASE WHEN DATENAME(dw, b) = 'Saturday' THEN 1 ELSE 0 END)
      -COUNT(o.Holiday_Date) 
      as workday
    from
    (
    select 
    @FromDate as a,
    dateadd(DAY,num +@days,@FromDate) as b
    from (select row_number() over (order by (select NULL)) as num
          from Information_Schema.columns
         ) t
    where num <= 100 
    ) dt
    left join Holiday o on o.Holiday_Date between a and b and DATENAME(dw, o.Holiday_Date) not in('Saturday','Sunday') 
    where DATENAME(dw, b) not in('Saturday','Sunday')
          and b not in (select Holiday_Date from OP_Holiday where Holiday_Date between a and b) 

    group by a,b
) du
where workday =@days 


)
return @result 
end

其中Holiday是一个以holiday_date作为假期参考的表

希望这可以帮助一些人。

答案 24 :(得分:-1)

我最近解决了这个问题,通过创建一个INT值@DaysToAdd,在当前日期增加了两个工作日 - 在2008/2012年测试并运行良好。

DECLARE @DaysToAdd INT

SELECT @DaysToAdd = CASE  
   WHEN DATEPART(WEEKDAY,GETDATE()) =  1 THEN 3 -- Sunday -> Wednesday
   WHEN DATEPART(WEEKDAY,GETDATE()) =  5 THEN 4 -- Thursday -> Monday
   WHEN DATEPART(WEEKDAY,GETDATE()) =  6 THEN 4 -- Friday -> Tuesday
   WHEN DATEPART(WEEKDAY,GETDATE()) =  7 THEN 4 -- Saturday -> Wednesday
   ELSE 2 END

SELECT DATEADD(DAY, @DaysToAdd, GETDATE()) AS TwoWorkingDaysTime