4

我已经看到许多关于如何创建一个 SQL Server 函数的帖子,该函数将给一个日期添加给定的工作日数。但是,他们都没有完全按照我需要的方式计算。我们有一个当前在代码中完成的函数,但我想转移到本机 SQL Server 函数(供存储过程和查询中使用)。在我们开始评估仓储费用之前,我们会给客户 5 个工作日来收集货物。5 个工作日不包括周末和节假日(我们有一个包含节假日日期的表格)。这里的诀窍是我需要在 5 个工作日后立即获取日期,无论是周末还是节假日。所以我需要这个函数来返回最后一个工作日,而不是之后的第一个工作日。因此,例如:

Oct 20th (Sat) plus 5 working days = Oct 26th (Fri)
Oct 21st (Sun) plus 5 working days = Oct 26th (Fri)
Oct 22nd (Mon) plus 5 working days = Oct 29th (Mon)
May 19th (Sat) plus 5 working days with May 21st a holiday = May 28th

5 个工作日是当前的分配,但将来可能会改变,因此需要将工作日数作为参数。此外,该函数可用于相当大的数据集,因此我更喜欢在没有循环的情况下执行此操作。我们正在运行 SQL Server 2008。

编辑:这不是“在没有循环的 SQL 中添加迄今为止的工作日”的副本,因为他们希望结束日期是工作日。我希望我的结束日期是紧随最后宽限日之后的任何日期(即:周一至周五的 5 个工作日我希望返回周六日期,而不是下一个周一)。

4

3 回答 3

3
create table holidays (
  date date);
GO

create function dbo.findWorkDayAfter(@date datetime, @days int)
returns date as
begin
return (
  select thedate
  from (
  select thedate=dateadd(d,v.day,cast(@date as date)),
         rn=row_number() over (order by v.day)
  from (values(1),(2),(3),(4),(5),(6),(7),(8),(9),(10))v(day)
  left join holidays h on h.date = dateadd(d,v.day,cast(@date as date))
  where h.date is null and left(datename(dw,dateadd(d,v.day,cast(@date as date))),1) <> 'S'
  ) x
  where @days = rn
  )
end
GO

除非你有长假,否则 10 天应该足够找到下一个工作日的第 5 个。如果需要,请增加它。

如果您从某个日期开始需要更多的工作日,您可以使用它来满足一年或三年的需求。

alter function dbo.findWorkDayAfter(@date datetime, @days int)
returns date as
begin
return (
  select thedate
  from (
  select thedate=dateadd(d,v.number,cast(@date as date)),
         rn=row_number() over (order by v.number)
  from master..spt_values v
  left join holidays h on h.date = dateadd(d,v.number,cast(@date as date))
  where h.date is null and left(datename(dw,dateadd(d,v.number,cast(@date as date))),1) <> 'S'
    and v.number >= 1 and v.type='p'
  ) x
  where @days = rn
  )
end
GO
于 2012-10-12T15:45:38.223 回答
2

我之前有类似的要求,因此共享以下函数,该函数在将给定的天数(仅工作日)添加到给定日期后返回新日期,它还提供了从周末排除星期六的选项。

ALTER FUNCTION [dbo].[AddDaysAndWeekends](
    @StartDate DATETIME,
    @NoOfDays INT,
    @IsSatrudayHoliday bit
)

RETURNS DATETIME AS BEGIN
    while (@NoOfDays>0)
    begin
        --add 1 day
        set @StartDate = DateAdd(day,1,@StartDate)
        --skip weekends
        while (DATEPART(dw, @StartDate) = 1 or (@IsSatrudayHoliday = 1 and DATEPART(dw, @StartDate) = 7))
        begin
            set @StartDate = DateAdd(day,1,@StartDate)
        end
        set @NoOfDays = @NoOfDays-1
    end
    --declare @dateadded int = DATEDIFF(day,@BaseDate,@StartDate)
    RETURN @StartDate
END

注意:如果@StartDate 是周末,上面的函数不会考虑将@StartDate 更改为下一个工作日期。

如果有一个带有这样声明的假期的表:

CREATE TABLE Holiday(ID INT IDENTIFY, HolidayDate Date, ...)

然后函数考虑将@StartDate 更改为像这样的下一个工作日期

ALTER FUNCTION [dbo].[AddDaysAndWeekends](
    @StartDate DATETIME,
    @NoOfDays INT,
    @IsSatrudayHoliday bit
)

RETURNS DATETIME AS BEGIN
    while (@NoOfDays>0)
    begin
        --add 1 day
        set @StartDate = DateAdd(day,1,@StartDate)
        --skip weekends
        while (
            DATEPART(dw, @StartDate) = 1 or
            (@IsSatrudayHoliday = 1 and DATEPART(dw, @StartDate) = 7) or
            exists (select 1 from Holiday where HolidayDate = convert(date, @StartDate))
        )
        begin
            set @StartDate = DateAdd(day,1,@StartDate)
        end
        set @NoOfDays = @NoOfDays-1
    end
    --declare @dateadded int = DATEDIFF(day,@BaseDate,@StartDate)
    RETURN @StartDate
END
于 2014-10-29T09:53:55.447 回答
1

所有归功于 Bogdan Maxim 和 Peter Mortensen计算两个日期之间的工作日。这是他们的帖子,我只是在函数中添加了假期(假设您有一个带有日期时间字段“HolDate”的表“tblHolidays”。)对于新手,最后有一个测试脚本。快乐编码!

--Changing current database to the Master database allows function to be shared by everyone.
USE MASTER
GO
--If the function already exists, drop it.
IF EXISTS
(
    SELECT *
    FROM dbo.SYSOBJECTS
    WHERE ID = OBJECT_ID(N'[dbo].[fn_WorkDays]')
    AND XType IN (N'FN', N'IF', N'TF')
)

DROP FUNCTION [dbo].[fn_WorkDays]
GO
 CREATE FUNCTION dbo.fn_WorkDays
--Presets
--Define the input parameters (OK if reversed by mistake).
(
    @StartDate DATETIME,
    @EndDate   DATETIME = NULL --@EndDate replaced by @StartDate when DEFAULTed
)

--Define the output data type.
RETURNS INT

AS
--Calculate the RETURN of the function.
BEGIN
    --Declare local variables
    --Temporarily holds @EndDate during date reversal.
    DECLARE @Swap DATETIME

    --If the Start Date is null, return a NULL and exit.
    IF @StartDate IS NULL
        RETURN NULL

    --If the End Date is null, populate with Start Date value so will have two dates (required by DATEDIFF below).
    IF @EndDate IS NULL
        SELECT @EndDate = @StartDate

    --Strip the time element from both dates (just to be safe) by converting to whole days and back to a date.
    --Usually faster than CONVERT.
    --0 is a date (01/01/1900 00:00:00.000)
    SELECT @StartDate = DATEADD(dd,DATEDIFF(dd,0,@StartDate), 0),
            @EndDate   = DATEADD(dd,DATEDIFF(dd,0,@EndDate)  , 0)

    --If the inputs are in the wrong order, reverse them.
    IF @StartDate > @EndDate
        SELECT @Swap      = @EndDate,
               @EndDate   = @StartDate,
               @StartDate = @Swap

    --Calculate and return the number of workdays using the input parameters.
    --This is the meat of the function.
    --This is really just one formula with a couple of parts that are listed on separate lines for documentation purposes.
    RETURN (
        SELECT
        --Start with total number of days including weekends
        (DATEDIFF(dd,@StartDate, @EndDate)+1)
        --Subtact 2 days for each full weekend
        -(DATEDIFF(wk,@StartDate, @EndDate)*2)
        --If StartDate is a Sunday, Subtract 1
        -(CASE WHEN DATENAME(dw, @StartDate) = 'Sunday'
            THEN 1
            ELSE 0
        END)
        --If EndDate is a Saturday, Subtract 1
        -(CASE WHEN DATENAME(dw, @EndDate) = 'Saturday'
            THEN 1
            ELSE 0
        END)
        --Subtract all holidays
        -(Select Count(*) from tblHolidays
          where HolDate between @StartDate and @EndDate )
        )
    END  
GO

/*
    -- Test Script
declare @EndDate datetime= dateadd(m,2,getdate())
print @EndDate
select  [Master].[dbo].[fn_WorkDays] (getdate(), @EndDate)
*/
于 2013-11-22T16:13:17.117 回答