例如:
@dtBegin = '2012-06-29'
@input = 20
我希望输出为'2012-07-27'
.
我不得不在我的项目中解决同样的问题。Gordon Lionoff 的解决方案让我走上了正确的道路,但并不总是产生正确的结果。我还必须考虑从周末开始的日期。即在星期六或星期日增加 1 个工作日应该是星期一。这是处理工作日计算的最常用方法。
我基于 Gordon Linoff 的函数和Patrick McDonald 的优秀 C# 等价物创建了自己的解决方案
注意:我的解决方案仅在 DATEFIRST 设置为默认值 7 时才有效。如果您使用不同的 DATEFIRST 值,则必须更改1
,7
和(1,7,8,9,10)
位。
我的解决方案包含两个功能。处理边缘情况的“外部”函数和执行实际计算的“内部”函数。这两个函数都是表值函数,因此它们实际上将扩展为实现查询并通过查询优化器提供。
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
)
“内部”函数类似于 Gordon Linoff 的解决方案,不同之处在于它考虑了跨越周末边界但不跨越整周边界的日期。
最后,我创建了一个测试脚本来测试我的功能。预期值是使用Patrick McDonald 出色的 C# 等价物生成的,我用这个流行的计算器随机交叉引用了这些数据。
您可以在不借助日历表或用户定义函数的情况下执行此操作:
dateadd(d,
(@input / 5) * 7 + -- get complete weeks out of the way
mod(@input, 5) + -- extra days
(case when ((@input%5) + datepart(dw, @dtbegin)%7) in (7, 1, 8) or
((@input%5) + datepart(dw, @dtbegin)%7) < (@input%5)
then 2
else 0
end),
@dtbegin
)
我不是说这很漂亮。但有时算术比连接或循环更可取。
这是我尝试过的:
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
作为假期的参考。
您可以使用下面提到的代码排除周末
go
if object_id('UTL_DateAddWorkingDays') is not null
drop function UTL_DateAddWorkingDays
go
create FUNCTION [dbo].[UTL_DateAddWorkingDays]
(
@date datetime,
@daysToAdd int
)
RETURNS date
as
begin
declare @daysToAddCnt int=0,
@Dt datetime
declare @OutTable table
(
id int identity(1,1),
WeekDate date,
DayId int
)
while @daysToAdd>0
begin
set @Dt=dateadd(day,@daysToAddCnt,@date)
--select @daysToAddCnt cnt,@Dt date,DATEPART(weekday,@Dt) dayId,@daysToAdd daystoAdd
if(DATEPART(weekday,@Dt) <>7 and DATEPART(weekday,@Dt)<>1)
begin
insert into @outTable (WeekDate,DayId)
select @Dt,DATEPART(weekday,DATEADD(day,@daysToAddCnt,@Dt))
set @daysToAdd=@daysToAdd-1
end
set @daysToAddCnt=@daysToAddCnt+1
end
select @Dt=max(WeekDate) from @outTable
return @Dt
end
我意识到我参加这个派对大约晚了 8 年......但我接受了马丁的回答并将其更新为:
1. 单个标量函数而不是 2 个嵌套的表值函数
我使用他的原始测试脚本进行了测试,我的功能也通过了。此外,似乎重建为标量函数对性能有轻微的积极影响。两个版本似乎都从“缓冲区缓存”中受益,标量版本的非缓存性能提高了 25%,缓存提高了 40%。免责声明:我只是运行了两个版本并记录了时间,我没有进行任何体面的性能测试。
2. 包括对 DATEFIRST 的支持是星期一、星期六或星期日
我觉得 UDF 应该与datefirst
设置无关。我在欧洲,星期一是这里的默认设置。如果不进行调整,原始功能将无法工作。
根据维基百科的说法,周一、周六和周日是一周中唯一真实的第一天。可以很容易地添加对其他的支持,但会使代码更加庞大,而且我很难想象一个真实的用例。
CREATE FUNCTION dbo.fn_addWorkDays
(
@date datetime,
@days int
)
RETURNS DATETIME
AS
BEGIN
IF NOT @@DATEFIRST IN (1,6,7) BEGIN --UNSUPPORTED DATE FIRST
RETURN NULL
/* MONDAY = FRIST DAY */
END ELSE IF @days = 0 BEGIN
RETURN @date
END ELSE IF @@DATEFIRST = 1 AND DATEPART(dw, @date) = 7 BEGIN --SUNDAY
SET @date = DATEADD(d, 1, @date)
SET @days = @days - 1
END ELSE IF @@DATEFIRST = 1 AND DATEPART(dw, @date) = 6 BEGIN --SATURDAY
SET @date = DATEADD(d, 2, @date)
SET @days = @days - 1
/* SATURDAY = FRIST DAY */
END ELSE IF @@DATEFIRST = 7 AND DATEPART(dw, @date) = 2 BEGIN --SUNDAY
SET @date = DATEADD(d, 1, @date)
SET @days = @days - 1
END ELSE IF @@DATEFIRST = 7 AND DATEPART(dw, @date) = 1 BEGIN --SATURDAY
SET @date = DATEADD(d, 2, @date)
SET @days = @days - 1
/* SUNDAY = FRIST DAY */
END ELSE IF @@DATEFIRST = 7 AND DATEPART(dw, @date) = 1 BEGIN --SUNDAY
SET @date = DATEADD(d, 1, @date)
SET @days = @days - 1
END ELSE IF @@DATEFIRST = 7 AND DATEPART(dw, @date) = 7 BEGIN --SATURDAY
SET @date = DATEADD(d, 2, @date)
SET @days = @days - 1
END
DECLARE @return AS dateTime
SELECT @return =
DATEADD(d
, (@days / 5) * 7
+ (@days % 5)
+ (CASE
/* MONDAY = FRIST DAY */
WHEN @@DATEFIRST = 1 AND ((@days%5) + DATEPART(dw, @date)) IN (6,7,8,9) THEN 2
/* SATURDAY = FRIST DAY */
WHEN @@DATEFIRST = 7 AND ((@days%5) + DATEPART(dw, @date)) IN (1,2,8,9,10) THEN 2
/* SUNDAY = FRIST DAY */
WHEN @@DATEFIRST = 7 AND ((@days%5) + DATEPART(dw, @date)) IN (1,7,8,9,10,11) THEN 2
ELSE 0
END)
, @date)
RETURN @return
END
我希望这可能会使某人受益!