Добавьте рабочие дни в SQL без циклов - PullRequest
30 голосов
/ 29 марта 2011

В настоящее время в моей базе данных 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

Ответы [ 23 ]

30 голосов
/ 13 февраля 2014

Это лучше, если кто-то ищет решение 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
15 голосов
/ 29 марта 2011

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

7 голосов
/ 30 августа 2012

На основе ответа, который был принят для этого вопроса, следующая пользовательская функция (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
6 голосов
/ 17 марта 2016

Этот ответ основан на @ ответе 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)-(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
5 голосов
/ 29 марта 2011

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

4 голосов
/ 21 августа 2013

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

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

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

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

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

Во всяком случае, я использовал то, что упомянуло «Нейт Кук» выше. И использовал его как одну строку кода. (Потому что я воздерживаюсь от использования функций)

код Нейта

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()))
)
2 голосов
/ 04 апреля 2014

Чтобы расширить комментарий Амина и ответ Нейта Кука выше, однострочное решение этого вопроса:

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

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

2 голосов
/ 26 июля 2013
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.Для вычитания рабочих дней используйте отрицательное количество дней.

1 голос
/ 29 марта 2011

У меня нет 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
1 голос
/ 20 ноября 2013

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...