Подтвердить что ты не робот

Добавить рабочие дни на SQL без циклов

В настоящее время у меня есть функция в моей базе данных SQL, которая добавляет определенное количество рабочих дней к дате, например. если вы введете дату, которая находится в четверг, и добавьте два дня, она вернет дату следующего понедельника. Я не беспокоюсь о каких-либо праздниках, только выходные дни исключаются.

Проблема заключается в том, что в настоящее время это делается с использованием цикла 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
4b9b3361

Ответ 1

Этот ответ был значительно изменен с момента его принятия, поскольку оригинал был неправильным. Однако я уверен в новом запросе, и это не зависит от 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))[email protected] 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

Это лучше, если кто-то ищет решение 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)[email protected])/5)*2*SIGN(@n)[email protected]/7,@Date);
END

Ответ 3

С учетом ответа, принятого для этого вопроса, во всех случаях должна работать следующая пользовательская функция (UDF) - независимо от настройки для @@DateFirst.

ОБНОВЛЕНИЕ: как указано ниже, эта функция предназначена для того, чтобы FromDate был рабочим днем. Поведение undefined, когда выходной день передается как 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

Ответ 4

Задумывались ли вы о предварительном заполнении справочной таблицы, содержащей все рабочие дни (используя вашу функцию), например WorkDays (int DaySequenceId, Date WorkDate), вы можете использовать эту таблицу, выбрав DaySequenceId @fromDate и добавьте @daysToAdd, чтобы получить новую рабочую дату. Очевидно, что этот метод также имеет дополнительные накладные расходы при администрировании таблицы WorkDays, но вы можете предварительно заполнить его диапазоном ожидаемых дат. Другим недостатком являются рабочие даты, которые могут быть рассчитаны, только те, которые содержатся в таблице рабочих дней.

Ответ 5

* Я знаю, что это старый поток, но нашел что-то чрезвычайно полезное некоторое время назад, изменил его и получил это.

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

Обновление: Мне очень жаль, что вы нашли фрагмент кода (в одном заявлении) и, чтобы избежать использования функции, я разместил здесь неправильный код.

Бит, упомянутый выше, может использоваться, если количество дней, которое вы добавляете, равно 7 или меньше.

Я изменил код с требуемыми параметрами для лучшего понимания.

В любом случае, я закончил тем, что сказал "Nate Cook". И использовал его как одну строку кода. (Потому что я сдерживаюсь от использования функций)

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

Ответ 6

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

Ответы основаны на ответе @ElmerMiller.

Он фиксирует отрицательное значение в воскресном комментарии от @FistOfFury

Отрицательные значения не работают, если дата прошла в воскресенье

И комментарий настройки DATEFIRST от @Damien_The_Unbeliever

Но это действительно принимает конкретную настройку 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)-([email protected]@DATEFIRST))) AS BIT)
SET @DW=DATEPART(DW,@Date)-([email protected]@DATEFIRST)*(@f^1)[email protected]@DATEFIRST*(@f&1)
SET @d=4-SIGN(@n)*([email protected]);
RETURN DATEADD(D,@n+((ABS(@n)+(@d%(8+SIGN(@n)))-2)/5)*2*SIGN(@n)[email protected]/7,@Date);
END

Ответ 8

На данный момент у меня нет 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

Ответ 9

Спасибо Damien за код. В расчетах была небольшая ошибка в том, что она добавила только 1 день для воскресенья, и что, когда количество рабочих дней пересекло выходные (но не приземлилось в выходные дни), дополнительные 2 дня не были приняты во внимание. Вот модифицированная версия кода Damiens, которая работает с датой по умолчанию в начале 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

Ответ 10

Ответ, принятый на вопрос, дает неверные результаты. Например. select @fromDate = '03-11-1983', @DaysToAdd = 3 приводит к 03-14-1983, тогда как 03-16-1983 ожидается.

Я разместил рабочее решение здесь, но для полноты я также добавлю его здесь. Если вас интересуют детали этих двух методов, посетите мой оригинальный ответ. Если нет, просто скопируйте/вставьте это в свой проект SQL и используйте UTL_DateAddWorkingDays

Обратите внимание, что мое решение работает только в том случае, если для параметра DATEFIRST установлено значение по умолчанию 7.

Тест Script, используемый для тестирования различных методов

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
)

Ответ 11

Это то, что я использую:

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;" часть необходимо установить в понедельник в качестве первого дня недели.

Ответ 12

Чтобы развернуть комментарий 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))

Обратите внимание, что вы можете добавлять или вычитать дни, чтобы идти вперед и назад вовремя, соответственно.

Ответ 13

Я тестировал все предлагаемые здесь решения, и никто из них не работает. Вот несколько тестовых сценариев, которые нарушили множество вышеупомянутых решений. (предполагая, что суббота и воскресенье - дни, которые вы исключаете):

-Добавить 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

Ответ 14

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) 

Ответ 15

Я знаю это немного позже, возможно, кто-то еще наткнется на эту проблему. Я пробовал вышеупомянутое решение, но большинство из них не могут рассчитывать праздники.

Вот как я пробовал

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 [email protected],@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 [email protected] 


)
return @result 
end

Где Праздник - это таблица с holiday_date как ссылка для отпуска

Надеюсь, это поможет кому-то.

Ответ 16

Эта функция 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

Ссылка

Ответ 17

Для Германии ВСЕ ответы НЕ РАБОТАЮТ.

Единственная функция i, которую я тестировал и работает, - это перевод из старого формула excel в http://ms-excel.eu/formeln/beispiel-formeln-datum/excel-datum-plus-arbeitstage.html :

Set @EndDate=Dateadd(DAY,@DaysToAdd,@FromDate) +
Cast(((
         CASE WHEN 5 <= DATEPART(weekday, @FromDate)%7 
        THEN 5
         ELSE 
         DATEPART(weekday, @FromDate)%7
         END)
      -1 + @DaysToAdd )/5 
 as int) 
* 2 - 
   (Case when DAtepart(weekday, @FromDate)=6 then 1 else 0 end) 

Ответ 18

Я немного опаздываю на эту вечеринку, но я закончил писать свою собственную версию из-за недостатков в других решениях. В частности, эта версия учитывает обратный отсчет и начинается по выходным.

Там может возникнуть неоднозначная ситуация, если вы добавите нулевые рабочие дни в выходной день. Я сохранил дату одинаково, но вы можете оставить эту проверку, если вы всегда хотите принудительно вернуть рабочий день.

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

Ответ 19

Я только что протестировал принятый ответ и обнаружил, что он не работает, когда в воскресенье начинается день.

Вам нужно добавить следующие элементы под Select @Saturday:

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

Ответ 20

Вздох. Я не могу поверить, что после всех этих десятилетий все еще нет: a) стандартных "DateAddWorkDays" в Microsoft SQL Server (хотя Microsoft на протяжении веков имела функцию WorkDay в Excel) и b) очистить решение здесь или где-либо еще, я могу найти, что обрабатывает все проблемы, поднятые людьми.

Здесь разработано решение, в котором рассматриваются следующие проблемы, которые, казалось бы, все вышеперечисленные ответы здесь и в других местах, которые я смог найти, имеют один или несколько из них. Это обрабатывает:

  • Именованные имена идентификаторов.
  • Комментарии, объясняющие непонятный код.
  • Не проверять каждый рабочий день, требующий увеличения (т. намного меньше, чем O (n) сложность).
  • Отрицательный рабочий день увеличивается.
  • Разрешается пропускать часть времени, отличную от 12 утра (так что вам не нужно будет ее сначала снимать).
  • Сохранение переданной части времени, если таковая имеется, в результате (в случае, если вам нужно точное время x-деловых дней вперед/назад).
  • Имена выходных дней на других языках, кроме английского.
  • @@DateFirst значения, отличные от значения по умолчанию (7 aka U.S.).
  • Указание пользовательского списка нерабочих дней вне недели.
  • Предоставление списка нерабочих дней без выходных для работы, если прошедшая дата имеет нерабочее время 12 часов.
  • Возврат даты начала, если приращение # рабочего дня равно 0, даже если начальная дата-время находится в нерабочий день.
  • Переход к следующему/предыдущему рабочему дню сначала, прежде чем начинать увеличивать/уменьшать рабочие дни, соответственно. ПРИМЕЧАНИЕ. Это отличается от функции Excel WorkDay, но я считаю, что это более полезно и интуитивно. Ex. Если вы получите запрос/заказ в выходной день, и у вас есть SLA (то есть время ответа, дата доставки) в течение 1 рабочего дня, вам не придется отвечать/доставлять до тех пор, пока не пройдет 1 полный рабочий день (независимо от того, как многие смежные нерабочие дни превалировали).
  • Пропуск любых дополнительных выходных и/или нерабочих будних дней, которые могли быть заполнены после добавления каких-либо нерабочих будних дней назад, которые могут быть натянуты при добавлении начальных выходных дней, когда добавляется только количество рабочих дней - и повторяется до тех пор, пока нет дольше необходимо.

ПРЕДЛОЖЕНИЯ: Конечно, как и с любым рекурсивным алгоритмом, этот можно преобразовать в итеративный (путем реализации вашего собственного стека, то есть с помощью таблицы Temp), но я думаю, что 32 уровня вложенности более чем достаточно для подавляющее большинство случаев использования в реальном мире. Кроме того, конечно, вы можете сделать его более универсальным/переносным, передав в нерабочие дни недели в качестве параметра Table-Valued по сравнению с жестко закодированной ссылкой на таблицу.

`   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 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

`

Ответ 21

Недавно я решил эту проблему добавить два рабочих дня к текущей дате, создав значение 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