我目前在我的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
答案 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-1983
而03-16-1983
为expected。
我发布了一个有效的解决方案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开始,但是由于我无法正常工作而出现了很大的分歧。
注意:我的公司将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)在这里或其他任何我能找到的处理人们提出的所有问题的清晰解决方案。
这是我开发的一个解决方案,它解决了以下问题,这些问题似乎在这里以及我能够找到的其他地方有一个或多个问题。这处理:
WorkDay
功能不同,但我相信这更有用,更直观。防爆。如果您在周末获得查询/订单,并且您有1个工作日的SLA(即响应时间,交付日期),则您不应该在1个完整工作日之前回复/交付(无论如何)在其之前有多少相邻的非工作日)。建议:当然,与任何递归算法一样,这个算法可以转换为迭代算法(通过实现自己的堆栈,即使用临时表),但我认为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
答案 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