Добавить рабочие дни на 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
Ответы
Ответ 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