【问题标题】:Count work days between two dates计算两个日期之间的工作日
【发布时间】:2010-09-20 03:10:55
【问题描述】:

如何计算 SQL Server 中两个日期之间的工作日数?

周一到周五,必须是 T-SQL。

【问题讨论】:

  • 你能定义工作日吗?周一到周五有吗?不包括重大节日?什么国家?必须在 SQL 中完成吗?

标签: sql tsql date


【解决方案1】:

以上功能均不适用于同一周或处理假期。我是这样写的:

create FUNCTION [dbo].[ShiftHolidayToWorkday](@date date)
RETURNS date
AS
BEGIN
    IF DATENAME( dw, @Date ) = 'Saturday'
        SET @Date = DATEADD(day, - 1, @Date)

    ELSE IF DATENAME( dw, @Date ) = 'Sunday'
        SET @Date = DATEADD(day, 1, @Date)

    RETURN @date
END
GO

create FUNCTION [dbo].[GetHoliday](@date date)
RETURNS varchar(50)
AS
BEGIN
    declare @s varchar(50)

    SELECT @s = CASE
        WHEN dbo.ShiftHolidayToWorkday(CONVERT(varchar, [Year]  ) + '-01-01') = @date THEN 'New Year'
        WHEN dbo.ShiftHolidayToWorkday(CONVERT(varchar, [Year]+1) + '-01-01') = @date THEN 'New Year'
        WHEN dbo.ShiftHolidayToWorkday(CONVERT(varchar, [Year]  ) + '-07-04') = @date THEN 'Independence Day'
        WHEN dbo.ShiftHolidayToWorkday(CONVERT(varchar, [Year]  ) + '-12-25') = @date THEN 'Christmas Day'
        --WHEN dbo.ShiftHolidayToWorkday(CONVERT(varchar, [Year]) + '-12-31') = @date THEN 'New Years Eve'
        --WHEN dbo.ShiftHolidayToWorkday(CONVERT(varchar, [Year]) + '-11-11') = @date THEN 'Veteran''s Day'

        WHEN [Month] = 1  AND [DayOfMonth] BETWEEN 15 AND 21 AND [DayName] = 'Monday' THEN 'Martin Luther King Day'
        WHEN [Month] = 5  AND [DayOfMonth] >= 25             AND [DayName] = 'Monday' THEN 'Memorial Day'
        WHEN [Month] = 9  AND [DayOfMonth] <= 7              AND [DayName] = 'Monday' THEN 'Labor Day'
        WHEN [Month] = 11 AND [DayOfMonth] BETWEEN 22 AND 28 AND [DayName] = 'Thursday' THEN 'Thanksgiving Day'
        WHEN [Month] = 11 AND [DayOfMonth] BETWEEN 23 AND 29 AND [DayName] = 'Friday' THEN 'Day After Thanksgiving'
        ELSE NULL END
    FROM (
        SELECT
            [Year] = YEAR(@date),
            [Month] = MONTH(@date),
            [DayOfMonth] = DAY(@date),
            [DayName]   = DATENAME(weekday,@date)
    ) c

    RETURN @s
END
GO

create FUNCTION [dbo].GetHolidays(@year int)
RETURNS TABLE 
AS
RETURN (  
    select dt, dbo.GetHoliday(dt) as Holiday
    from (
        select dateadd(day, number, convert(varchar,@year) + '-01-01') dt
        from master..spt_values 
        where type='p' 
        ) d
    where year(dt) = @year and dbo.GetHoliday(dt) is not null
)

create proc UpdateHolidaysTable
as

if not exists(select TABLE_NAME from INFORMATION_SCHEMA.TABLES where TABLE_NAME = 'Holidays')
    create table Holidays(dt date primary key clustered, Holiday varchar(50))

declare @year int
set @year = 1990

while @year < year(GetDate()) + 20
begin
    insert into Holidays(dt, Holiday)
    select a.dt, a.Holiday
    from dbo.GetHolidays(@year) a
        left join Holidays b on b.dt = a.dt
    where b.dt is null

    set @year = @year + 1
end

create FUNCTION [dbo].[GetWorkDays](@StartDate DATE = NULL, @EndDate DATE = NULL)
RETURNS INT 
AS
BEGIN
    IF @StartDate IS NULL OR @EndDate IS NULL
        RETURN  0

    IF @StartDate >= @EndDate 
        RETURN  0

    DECLARE @Days int
    SET @Days = 0

    IF year(@StartDate) * 100 + datepart(week, @StartDate) = year(@EndDate) * 100 + datepart(week, @EndDate) 
        --same week
        select @Days = (DATEDIFF(dd, @StartDate, @EndDate))
      - (CASE WHEN DATENAME(dw, @StartDate) = 'Sunday' THEN 1 ELSE 0 END)
      - (CASE WHEN DATENAME(dw, @EndDate) = 'Saturday' THEN 1 ELSE 0 END)
      - (select count(*) from Holidays where dt between @StartDate and @EndDate)
    ELSE
        --diff weeks
        select @Days = (DATEDIFF(dd, @StartDate, @EndDate) + 1)
      - (DATEDIFF(wk, @StartDate, @EndDate) * 2)
      - (CASE WHEN DATENAME(dw, @StartDate) = 'Sunday' THEN 1 ELSE 0 END)
      - (CASE WHEN DATENAME(dw, @EndDate) = 'Saturday' THEN 1 ELSE 0 END)
      - (select count(*) from Holidays where dt between @StartDate and @EndDate)
 
    RETURN  @Days
END

【讨论】:

【解决方案2】:

我借鉴了其他人的一些想法来创建我的解决方案。我使用内联代码来忽略周末和美国联邦假期。在我的环境中,EndDate 可能为空,但它永远不会在 StartDate 之前。

CREATE FUNCTION dbo.ufn_CalculateBusinessDays(
@StartDate DATE,
@EndDate DATE = NULL)

RETURNS INT
AS

BEGIN
DECLARE @TotalBusinessDays INT = 0;
DECLARE @TestDate DATE = @StartDate;


IF @EndDate IS NULL
    RETURN NULL;

WHILE @TestDate < @EndDate
BEGIN
    DECLARE @Month INT = DATEPART(MM, @TestDate);
    DECLARE @Day INT = DATEPART(DD, @TestDate);
    DECLARE @DayOfWeek INT = DATEPART(WEEKDAY, @TestDate) - 1; --Monday = 1, Tuesday = 2, etc.
    DECLARE @DayOccurrence INT = (@Day - 1) / 7 + 1; --Nth day of month (3rd Monday, for example)

    --Increment business day counter if not a weekend or holiday
    SELECT @TotalBusinessDays += (
        SELECT CASE
            --Saturday OR Sunday
            WHEN @DayOfWeek IN (6,7) THEN 0
            --New Year's Day
            WHEN @Month = 1 AND @Day = 1 THEN 0
            --MLK Jr. Day
            WHEN @Month = 1 AND @DayOfWeek = 1 AND @DayOccurrence = 3 THEN 0
            --G. Washington's Birthday
            WHEN @Month = 2 AND @DayOfWeek = 1 AND @DayOccurrence = 3 THEN 0
            --Memorial Day
            WHEN @Month = 5 AND @DayOfWeek = 1 AND @Day BETWEEN 25 AND 31 THEN 0
            --Independence Day
            WHEN @Month = 7 AND @Day = 4 THEN 0
            --Labor Day
            WHEN @Month = 9 AND @DayOfWeek = 1 AND @DayOccurrence = 1 THEN 0
            --Columbus Day
            WHEN @Month = 10 AND @DayOfWeek = 1 AND @DayOccurrence = 2 THEN 0
            --Veterans Day
            WHEN @Month = 11 AND @Day = 11 THEN 0
            --Thanksgiving
            WHEN @Month = 11 AND @DayOfWeek = 4 AND @DayOccurrence = 4 THEN 0
            --Christmas
            WHEN @Month = 12 AND @Day = 25 THEN 0
            ELSE 1
            END AS Result);

    SET @TestDate = DATEADD(dd, 1, @TestDate);
END

RETURN @TotalBusinessDays;
END

【讨论】:

    【解决方案3】:

    这基本上是 CMS 的答案,不依赖于特定的语言设置。由于我们正在拍摄通用,这意味着它也应该适用于所有 @@datefirst 设置。

    datediff(day, <start>, <end>) + 1 - datediff(week, <start>, <end>) * 2
        /* if start is a Sunday, adjust by -1 */
      + case when datepart(weekday, <start>) = 8 - @@datefirst then -1 else 0 end
        /* if end is a Saturday, adjust by -1 */
      + case when datepart(weekday, <end>) = (13 - @@datefirst) % 7 + 1 then -1 else 0 end
    

    datediff(week, ...) 在几周内始终使用周六到周日的边界,因此该表达式是确定性的,不需要修改(只要我们对工作日的定义始终是周一到周五)。日期编号确实有所不同根据@@datefirst 设置和修改后的计算处理此校正,并带有一些模运算的小复杂性。

    处理周六/周日事情的一种更简洁的方法是在提取星期几值之前翻译日期。移位后,这些值将重新与固定的(可能更熟悉的)编号保持一致,该编号在周日以 1 开始,在周六以 7 结束。

    datediff(day, <start>, <end>) + 1 - datediff(week, <start>, <end>) * 2
      + case when datepart(weekday, dateadd(day, @@datefirst, <start>)) = 1 then -1 else 0 end
      + case when datepart(weekday, dateadd(day, @@datefirst, <end>))   = 7 then -1 else 0 end
    

    至少早在 2002 年和 Itzik Ben-Gan 的一篇文章中,我就已经跟踪过这种形式的解决方案。 (https://technet.microsoft.com/en-us/library/aa175781(v=sql.80).aspx) 虽然由于较新的 date 类型不允许日期算术,但它需要稍作调整,但在其他方面是相同的。

    编辑: 我添加了不知何故被遗漏的+1。还值得注意的是,此方法始终计算开始天数和结束天数。它还假定结束日期在开始日期之前或之后。

    【讨论】:

    • 请注意,这将在周末的许多日期返回错误的结果,因此它们不会加起来(周五->周一应该与周五->周六+周六->周日+周日->周一相同)。 Fri->Sat 应为 0(正确),Sat->Sun 应为 0(错误 -1),Sun->Mon 应为 1(错误 0)。其他错误是 Sat->Sat = -1, Sun->Sun = -1, Sun->Sat = 4
    • @adrianm 我相信我已经纠正了这些问题。实际上问题是它总是被一个关闭,因为我不小心把那个部分掉了。
    • 感谢您的更新。我以为你的公式不包括我需要的开始日期。自己解决并添加为另一个答案。
    【解决方案4】:

    我知道这是一个老问题,但我需要一个不包括开始日期的工作日公式,因为我有几个项目并且需要正确累积天数。

    没有一个非迭代的答案对我有用。

    我使用了类似的定义

    午夜到周一、周二、周三、周四和周五的次数

    (其他人可能会从午夜计算到星期六而不是星期一)

    我最终得到了这个公式

    SELECT DATEDIFF(day, @StartDate, @EndDate) /* all midnights passed */
         - DATEDIFF(week, @StartDate, @EndDate) /* remove sunday midnights */
         - DATEDIFF(week, DATEADD(day, 1, @StartDate), DATEADD(day, 1, @EndDate)) /* remove saturday midnights */
    

    【讨论】:

    • 那个是为我做的,但我必须做一点小改动。它没有考虑@StartDate 是星期六还是星期五。这是我的版本:DATEDIFF(day, @StartDate, @EndDate) - DATEDIFF(week, @StartDate, @EndDate) - DATEDIFF(week, DATEADD(day, 1, @StartDate), DATEADD(day, 1, @EndDate)) - (CASE WHEN DATEPART(WEEKDAY, @StartDate) IN (1, 7) THEN 1 ELSE 0 END) + 1
    • @caiosm1005,周六到周日返回0,周六到周一返回1,周五到周六返回0。都和我的定义一致。您的代码不会正确累积(例如,周五到周五返回 6,但周一到周一返回 5)
    【解决方案5】:

    与 DATEDIFF 一样,我不认为结束日期是间隔的一部分。 @StartDate 和@EndDate 之间的(例如)星期日数是“初始”星期一和@EndDate 之间的星期日数减去这个“初始”星期一和@StartDate 之间的星期日数。知道了这一点,我们可以如下计算工作日数:

    DECLARE @StartDate DATETIME
    DECLARE @EndDate DATETIME
    SET @StartDate = '2018/01/01'
    SET @EndDate = '2019/01/01'
    
    SELECT DATEDIFF(Day, @StartDate, @EndDate) -- Total Days
      - (DATEDIFF(Day, 0, @EndDate)/7 - DATEDIFF(Day, 0, @StartDate)/7) -- Sundays
      - (DATEDIFF(Day, -1, @EndDate)/7 - DATEDIFF(Day, -1, @StartDate)/7) -- Saturdays
    

    最好的问候!

    【讨论】:

    • 完美!这就是我一直在寻找的。特别感谢!
    【解决方案6】:

    一种方法是结合 case 表达式从头到尾“遍历日期”,该表达式检查当天是不是星期六或星期日并标记它(1 表示工作日,0 表示周末)。最后,只需对标志求和(它将等于 1 标志的计数,因为另一个标志为 0)为您提供工作日数。

    您可以使用 GetNums(startNumber,endNumber) 类型的实用程序函数,该函数生成一系列数字,用于从开始日期到结束日期“循环”。参考http://tsql.solidq.com/SourceCodes/GetNums.txt 的实现。逻辑也可以扩展以迎合假期(例如,如果您有假期表)

    declare @date1 as datetime = '19900101'
    declare @date2 as datetime = '19900120'
    
    select  sum(case when DATENAME(DW,currentDate) not in ('Saturday', 'Sunday') then 1 else 0 end) as noOfWorkDays
    from dbo.GetNums(0,DATEDIFF(day,@date1, @date2)-1) as Num
    cross apply (select DATEADD(day,n,@date1)) as Dates(currentDate)
    

    【讨论】:

      【解决方案7】:

      我发现下面的 TSQL 是一个相当优雅的解决方案(我没有运行函数的权限)。我发现DATEDIFF 忽略了DATEFIRST,我希望我一周的第一天是星期一。我还希望将第一个工作日设置为零,如果星期一是周末,则设置为零。这可能会对要求略有不同的人有所帮助:)

      它不处理银行假期

      SET DATEFIRST 1
      SELECT
      ,(DATEDIFF(DD,  [StartDate], [EndDate]))        
      -(DATEDIFF(wk,  [StartDate], [EndDate]))        
      -(DATEDIFF(wk, DATEADD(dd,-@@DATEFIRST,[StartDate]), DATEADD(dd,-@@DATEFIRST,[EndDate]))) AS [WorkingDays] 
      FROM /*Your Table*/ 
      

      【讨论】:

        【解决方案8】:

        另一种计算工作日的方法是使用 WHILE 循环,该循环基本上会遍历一个日期范围,并在发现日期在周一至周五之间时将其加 1。使用 WHILE 循环计算工作日的完整脚本如下所示:

        CREATE FUNCTION [dbo].[fn_GetTotalWorkingDaysUsingLoop]
        (@DateFrom DATE,
        @DateTo   DATE
        )
        RETURNS INT
        AS
             BEGIN
                 DECLARE @TotWorkingDays INT= 0;
                 WHILE @DateFrom <= @DateTo
                     BEGIN
                         IF DATENAME(WEEKDAY, @DateFrom) IN('Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday')
                             BEGIN
                                 SET @TotWorkingDays = @TotWorkingDays + 1;
                         END;
                         SET @DateFrom = DATEADD(DAY, 1, @DateFrom);
                     END;
                 RETURN @TotWorkingDays;
             END;
        GO
        

        虽然 WHILE 循环选项更简洁且使用的代码行数更少,但它有可能成为您环境中的性能瓶颈,尤其是当您的日期范围跨越数年时。

        您可以在本文中看到更多关于如何计算工作日和小时数的方法: https://www.sqlshack.com/how-to-calculate-work-days-and-hours-in-sql-server/

        【讨论】:

          【解决方案9】:
          Create Function dbo.DateDiff_WeekDays 
          (
          @StartDate  DateTime,
          @EndDate    DateTime
          )
          Returns Int
          As
          
          Begin   
          
          Declare @Result Int = 0
          
          While   @StartDate <= @EndDate
          Begin 
              If DateName(DW, @StartDate) not in ('Saturday','Sunday')
                  Begin
                      Set @Result = @Result +1
                  End
                  Set @StartDate = DateAdd(Day, +1, @StartDate)
          End
          
          Return @Result
          

          结束

          【讨论】:

            【解决方案10】:

            创建函数如:

            CREATE FUNCTION dbo.fn_WorkDays(@StartDate DATETIME, @EndDate DATETIME= NULL )
            RETURNS INT 
            AS
            BEGIN
                   DECLARE @Days int
                   SET @Days = 0
            
                   IF @EndDate = NULL
                          SET @EndDate = EOMONTH(@StartDate) --last date of the month
            
                   WHILE DATEDIFF(dd,@StartDate,@EndDate) >= 0
                   BEGIN
                          IF DATENAME(dw, @StartDate) <> 'Saturday' 
                                 and DATENAME(dw, @StartDate) <> 'Sunday' 
                                 and Not ((Day(@StartDate) = 1 And Month(@StartDate) = 1)) --New Year's Day.
                                 and Not ((Day(@StartDate) = 4 And Month(@StartDate) = 7)) --Independence Day.
                          BEGIN
                                 SET @Days = @Days + 1
                          END
            
                          SET @StartDate = DATEADD(dd,1,@StartDate)
                   END
            
                   RETURN  @Days
            END
            

            你可以像这样调用函数:

            select dbo.fn_WorkDays('1/1/2016', '9/25/2016')
            

            或者喜欢:

            select dbo.fn_WorkDays(StartDate, EndDate) 
            from table1
            

            【讨论】:

              【解决方案11】:

              我接受的答案版本是使用DATEPART 的函数,所以我不必在行上进行字符串比较

              DATENAME(dw, @StartDate) = 'Sunday'
              

              无论如何,这是我的业务 datediff 函数

              SET ANSI_NULLS ON
              GO
              SET QUOTED_IDENTIFIER ON
              GO
              
              CREATE FUNCTION BDATEDIFF
              (
                  @startdate as DATETIME,
                  @enddate as DATETIME
              )
              RETURNS INT
              AS
              BEGIN
                  DECLARE @res int
              
              SET @res = (DATEDIFF(dd, @startdate, @enddate) + 1)
                  -(DATEDIFF(wk, @startdate, @enddate) * 2)
                  -(CASE WHEN DATEPART(dw, @startdate) = 1 THEN 1 ELSE 0 END)
                  -(CASE WHEN DATEPART(dw, @enddate) = 7 THEN 1 ELSE 0 END)
              
                  RETURN @res
              END
              GO
              

              【讨论】:

                【解决方案12】:

                这对我有用,在我的国家,周六和周日是非工作日。

                @StartDate 和@EndDate 的时间对我来说很重要。

                CREATE FUNCTION [dbo].[fnGetCountWorkingBusinessDays]
                (
                    @StartDate as DATETIME,
                    @EndDate as DATETIME
                )
                RETURNS INT
                AS
                BEGIN
                    DECLARE @res int
                
                SET @StartDate = CASE 
                    WHEN DATENAME(dw, @StartDate) = 'Saturday' THEN DATEADD(dd, 2, DATEDIFF(dd, 0, @StartDate))
                    WHEN DATENAME(dw, @StartDate) = 'Sunday' THEN DATEADD(dd, 1, DATEDIFF(dd, 0, @StartDate))
                    ELSE @StartDate END
                
                SET @EndDate = CASE 
                    WHEN DATENAME(dw, @EndDate) = 'Saturday' THEN DATEADD(dd, 0, DATEDIFF(dd, 0, @EndDate))
                    WHEN DATENAME(dw, @EndDate) = 'Sunday' THEN DATEADD(dd, -1, DATEDIFF(dd, 0, @EndDate))
                    ELSE @EndDate END
                
                
                SET @res =
                    (DATEDIFF(hour, @StartDate, @EndDate) / 24)
                  - (DATEDIFF(wk, @StartDate, @EndDate) * 2)
                
                SET @res = CASE WHEN @res < 0 THEN 0 ELSE @res END
                
                    RETURN @res
                END
                
                GO
                

                【讨论】:

                  【解决方案13】:

                  使用日期表:

                      DECLARE 
                          @StartDate date = '2014-01-01',
                          @EndDate date = '2014-01-31'; 
                      SELECT 
                          COUNT(*) As NumberOfWeekDays
                      FROM dbo.Calendar
                      WHERE CalendarDate BETWEEN @StartDate AND @EndDate
                        AND IsWorkDay = 1;
                  

                  如果没有,可以使用数字表:

                      DECLARE 
                      @StartDate datetime = '2014-01-01',
                      @EndDate datetime = '2014-01-31'; 
                      SELECT 
                      SUM(CASE WHEN DATEPART(dw, DATEADD(dd, Number-1, @StartDate)) BETWEEN 2 AND 6 THEN 1 ELSE 0 END) As NumberOfWeekDays
                      FROM dbo.Numbers
                      WHERE Number <= DATEDIFF(dd, @StartDate, @EndDate) + 1 -- Number table starts at 1, we want a 0 base
                  

                  它们都应该很快,并且消除了歧义/复杂性。第一个选项是最好的,但如果您没有日历表,您总是可以创建一个带有 CTE 的数字表。

                  【讨论】:

                    【解决方案14】:

                    如果您需要将工作日添加到给定日期,您可以创建一个依赖于日历表的函数,如下所述:

                    CREATE TABLE Calendar
                    (
                      dt SMALLDATETIME PRIMARY KEY, 
                      IsWorkDay BIT
                    );
                    
                    --fill the rows with normal days, weekends and holidays.
                    
                    
                    create function AddWorkingDays (@initialDate smalldatetime, @numberOfDays int)
                        returns smalldatetime as 
                    
                        begin
                            declare @result smalldatetime
                            set @result = 
                            (
                                select t.dt from
                                (
                                    select dt, ROW_NUMBER() over (order by dt) as daysAhead from calendar 
                                    where dt > @initialDate
                                    and IsWorkDay = 1
                                    ) t
                                where t.daysAhead = @numberOfDays
                            )
                    
                            return @result
                        end
                    

                    【讨论】:

                    【解决方案15】:

                    感谢 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 [DB04\DB04].[Gateway].[dbo].[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)
                    */
                    

                    【讨论】:

                    • 嗨 Dan B。只是为了让您知道您的版本假定表 tblHolidays 不包含星期六和星期一,这有时会发生。无论如何,感谢您分享您的版本。干杯
                    • Julio - 是的 - 我的版本确实假设周六和周日(不是周一)是周末,因此不是“非工作日”。但是,如果您在周末工作,那么我猜每天都是“工作日”,您可以注释掉该子句的周六和周日部分,然后将所有假期添加到 tblHolidays 表中。
                    • 谢谢丹。我将它合并到我的函数中,添加一个周末检查,因为我的 DateDimensions 表包括所有日期、假期等。使用你的函数,我刚刚添加:并且 IsWeekend = 0 在 [HolDate] 之间的 StartDate 和 EndDate 之后)
                    • 如果Holiday 表包含周末的假期,您可以像这样修改条件:WHERE HolDate BETWEEN @StartDate AND @EndDate AND DATEPART(dw, HolDate) BETWEEN 2 AND 6 以仅计算周一至周五的假期。
                    【解决方案16】:

                    这是一个运行良好的版本(我认为)。假期表包含 Holiday_date 列,其中包含贵公司遵守的假期。

                    DECLARE @RAWDAYS INT
                    
                       SELECT @RAWDAYS =  DATEDIFF(day, @StartDate, @EndDate )--+1
                                        -( 2 * DATEDIFF( week, @StartDate, @EndDate ) )
                                        + CASE WHEN DATENAME(dw, @StartDate) = 'Saturday' THEN 1 ELSE 0 END
                                        - CASE WHEN DATENAME(dw, @EndDate) = 'Saturday' THEN 1 ELSE 0 END 
                    
                       SELECT  @RAWDAYS - COUNT(*) 
                         FROM HOLIDAY NumberOfBusinessDays
                        WHERE [Holiday_Date] BETWEEN @StartDate+1 AND @EndDate 
                    

                    【讨论】:

                    • 那些假期日期也可能落在周末。对于一些人来说,周日的假期将被下周一取代。
                    【解决方案17】:

                    我在这里举了各种例子,但在我的特殊情况下,我们有一个用于交付的@PromisedDate 和一个用于实际接收物品的@ReceivedDate。当在“PromisedDate”之前收到一个项目时,除非我按日历顺序订购传递给函数的日期,否则计算的总计不正确。不想每次都检查日期,我更改了函数来为我处理这个问题。

                    Create FUNCTION [dbo].[fnGetBusinessDays]
                    (
                     @PromiseDate date,
                     @ReceivedDate date
                    )
                    RETURNS integer
                    AS
                    BEGIN
                     DECLARE @days integer
                    
                     SELECT @days = 
                        Case when @PromiseDate > @ReceivedDate Then
                            DATEDIFF(d,@PromiseDate,@ReceivedDate) + 
                            ABS(DATEDIFF(wk,@PromiseDate,@ReceivedDate)) * 2 +
                            CASE 
                                WHEN DATENAME(dw, @PromiseDate) <> 'Saturday' AND DATENAME(dw, @ReceivedDate) = 'Saturday' THEN 1 
                                WHEN DATENAME(dw, @PromiseDate) = 'Saturday' AND DATENAME(dw, @ReceivedDate) <> 'Saturday' THEN -1 
                                ELSE 0
                            END +
                            (Select COUNT(*) FROM CompanyHolidays 
                                WHERE HolidayDate BETWEEN @ReceivedDate AND @PromiseDate 
                                AND DATENAME(dw, HolidayDate) <> 'Saturday' AND DATENAME(dw, HolidayDate) <> 'Sunday')
                        Else
                            DATEDIFF(d,@PromiseDate,@ReceivedDate)  -
                            ABS(DATEDIFF(wk,@PromiseDate,@ReceivedDate)) * 2  -
                                CASE 
                                    WHEN DATENAME(dw, @PromiseDate) <> 'Saturday' AND DATENAME(dw, @ReceivedDate) = 'Saturday' THEN 1 
                                    WHEN DATENAME(dw, @PromiseDate) = 'Saturday' AND DATENAME(dw, @ReceivedDate) <> 'Saturday' THEN -1 
                                    ELSE 0
                                END -
                            (Select COUNT(*) FROM CompanyHolidays 
                                WHERE HolidayDate BETWEEN @PromiseDate and @ReceivedDate 
                                AND DATENAME(dw, HolidayDate) <> 'Saturday' AND DATENAME(dw, HolidayDate) <> 'Sunday')
                        End
                    
                    
                     RETURN (@days)
                    
                    END
                    

                    【讨论】:

                      【解决方案18】:

                      对于包括假期在内的日期之间的差异,我这样做了:

                      1) 假期表:

                          CREATE TABLE [dbo].[Holiday](
                      [Id] [int] IDENTITY(1,1) NOT NULL,
                      [Name] [nvarchar](50) NULL,
                      [Date] [datetime] NOT NULL)
                      

                      2) 我有这样的计划表,想填充空的 Work_Days 列:

                          CREATE TABLE [dbo].[Plan_Phase](
                      [Id] [int] IDENTITY(1,1) NOT NULL,
                      [Id_Plan] [int] NOT NULL,
                      [Id_Phase] [int] NOT NULL,
                      [Start_Date] [datetime] NULL,
                      [End_Date] [datetime] NULL,
                      [Work_Days] [int] NULL)
                      

                      3) 因此,为了让“Work_Days”稍后填写我的专栏,只需:

                      SELECT Start_Date, End_Date,
                       (DATEDIFF(dd, Start_Date, End_Date) + 1)
                      -(DATEDIFF(wk, Start_Date, End_Date) * 2)
                      -(SELECT COUNT(*) From Holiday Where Date  >= Start_Date AND Date <= End_Date)
                      -(CASE WHEN DATENAME(dw, Start_Date) = 'Sunday' THEN 1 ELSE 0 END)
                      -(CASE WHEN DATENAME(dw, End_Date) = 'Saturday' THEN 1 ELSE 0 END)
                      -(CASE WHEN (SELECT COUNT(*) From Holiday Where Start_Date  = Date) > 0 THEN 1 ELSE 0 END)
                      -(CASE WHEN (SELECT COUNT(*) From Holiday Where End_Date  = Date) > 0 THEN 1 ELSE 0 END) AS Work_Days
                      from Plan_Phase
                      

                      希望我能帮上忙。

                      干杯

                      【讨论】:

                      • 关于你的假期减法。如果开始日期是 1 月 1 日,结束日期是 12 月 31 日怎么办?你只会减去 2 - 这是错误的。我建议对 End_Date 使用 DATEDIFF(day, Start_Date, Date) 而不是整个 'SELECT COUNT(*) FROM Holiday ...'。
                      【解决方案19】:

                      (我比评论权限差了几分)

                      如果您决定放弃 CMS's elegant solution 中的 +1 天,请注意,如果您的开始日期和结束日期在同一个周末,您会得到否定的答案。即,2008/10/26 到 2008/10/26 返回 -1。

                      我比较简单的解决方案:

                      select @Result = (..CMS's answer..)
                      if  (@Result < 0)
                              select @Result = 0
                          RETURN @Result
                      

                      .. 它还将 start dateend date 之后的所有错误帖子设置为零。您可能会或可能不会寻找的东西。

                      【讨论】:

                        【解决方案20】:
                        CREATE FUNCTION x
                        (
                            @StartDate DATETIME,
                            @EndDate DATETIME
                        )
                        RETURNS INT
                        AS
                        BEGIN
                            DECLARE @Teller INT
                        
                            SET @StartDate = DATEADD(dd,1,@StartDate)
                        
                            SET @Teller = 0
                            IF DATEDIFF(dd,@StartDate,@EndDate) <= 0
                            BEGIN
                                SET @Teller = 0 
                            END
                            ELSE
                            BEGIN
                                WHILE
                                    DATEDIFF(dd,@StartDate,@EndDate) >= 0
                                BEGIN
                                    IF DATEPART(dw,@StartDate) < 6
                                    BEGIN
                                        SET @Teller = @Teller + 1
                                    END
                                    SET @StartDate = DATEADD(dd,1,@StartDate)
                                END
                            END
                            RETURN @Teller
                        END
                        

                        【讨论】:

                          【解决方案21】:

                          Calculating Work Days 你可以找到一篇关于这个主题的好文章,但是你可以看到它不是那么先进。

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

                          如果您需要使用自定义日历,您可能需要添加一些检查和一些参数。希望它能提供一个好的起点。

                          【讨论】:

                          • 感谢您提供链接以了解其工作原理。 sqlservercentral 写的很棒!
                          【解决方案22】:
                          DECLARE @StartDate datetime,@EndDate datetime
                          
                          select @StartDate='3/2/2010', @EndDate='3/7/2010'
                          
                          DECLARE @TotalDays INT,@WorkDays INT
                          
                          DECLARE @ReducedDayswithEndDate INT
                          
                          DECLARE @WeekPart INT
                          
                          DECLARE @DatePart INT
                          
                          SET @TotalDays= DATEDIFF(day, @StartDate, @EndDate) +1
                          
                          SELECT @ReducedDayswithEndDate = CASE DATENAME(weekday, @EndDate)
                              WHEN 'Saturday' THEN 1
                              WHEN 'Sunday' THEN 2
                              ELSE 0 END
                          
                          SET @TotalDays=@TotalDays-@ReducedDayswithEndDate
                          
                          SET @WeekPart=@TotalDays/7;
                          
                          SET @DatePart=@TotalDays%7;
                          
                          SET @WorkDays=(@WeekPart*5)+@DatePart
                          
                          SELECT @WorkDays
                          

                          【讨论】:

                          【解决方案23】:
                           DECLARE @TotalDays INT,@WorkDays INT
                           DECLARE @ReducedDayswithEndDate INT
                           DECLARE @WeekPart INT
                           DECLARE @DatePart INT
                          
                           SET @TotalDays= DATEDIFF(day, @StartDate, @EndDate) +1
                           SELECT @ReducedDayswithEndDate = CASE DATENAME(weekday, @EndDate)
                            WHEN 'Saturday' THEN 1
                            WHEN 'Sunday' THEN 2
                            ELSE 0 END 
                           SET @TotalDays=@TotalDays-@ReducedDayswithEndDate
                           SET @WeekPart=@TotalDays/7;
                           SET @DatePart=@TotalDays%7;
                           SET @WorkDays=(@WeekPart*5)+@DatePart
                          
                           RETURN @WorkDays
                          

                          【讨论】:

                          • 如果您发布代码、XML 或数据示例,在文本编辑器中突出显示这些行,然后单击编辑器工具栏上的“代码示例”按钮 ({})很好地格式化和语法高亮它!
                          • 太好了,不需要外围功能或使用它来更新数据库。谢谢。顺便说一句,喜欢saltire :-)
                          • 超级解决方案。我在 webi Universe 中使用变量的公式来计算 2 个表列中的日期之间的工作日 (MF),如下所示 ...((((DATEDIFF(day, table.col1, table.col2) +1)- ((CASE DATENAME(weekday, table.col2) WHEN 'Saturday' THEN 1 WHEN 'Sunday' THEN 2 ELSE 0 END )))/7)*5)+(((DATEDIFF(day, table.col1, table.col2) ) +1)-((CASE DATENAME(weekday, table.col2) WHEN 'Saturday' THEN 1 WHEN 'Sunday' THEN 2 ELSE 0 END )))%7)
                          【解决方案24】:

                          对于周一至周五的工作日,您可以使用单个 SELECT 来完成,如下所示:

                          DECLARE @StartDate DATETIME
                          DECLARE @EndDate DATETIME
                          SET @StartDate = '2008/10/01'
                          SET @EndDate = '2008/10/31'
                          
                          
                          SELECT
                             (DATEDIFF(dd, @StartDate, @EndDate) + 1)
                            -(DATEDIFF(wk, @StartDate, @EndDate) * 2)
                            -(CASE WHEN DATENAME(dw, @StartDate) = 'Sunday' THEN 1 ELSE 0 END)
                            -(CASE WHEN DATENAME(dw, @EndDate) = 'Saturday' THEN 1 ELSE 0 END)
                          

                          如果你想包括假期,你必须稍微考虑一下......

                          【讨论】:

                          • 我刚刚意识到这段代码并不总是有效!我试过这个: SET @StartDate = '28-mar-2011' SET @EndDate = '29-mar-2011' 答案它算作 2 天
                          • @greektreat 它工作正常。只是@StartDate 和@EndDate 都包含在计数中。如果您希望星期一到星期二算作 1 天,只需删除第一个 DATEDIFF 后的“+1”即可。然后你也会得到 Fri->Sat=0, Fri->Sun=0, Fri->Mon=1。
                          • 作为@JoeDaley 的后续行动。当您删除 DATEDIFF 后的 + 1 以从计数中排除 startdate 时,您还需要调整其中的 CASE 部分。我最终使用了这个: +(CASE WHEN DATENAME(dw, @StartDate) = 'Saturday' THEN 1 ELSE 0 END) - (CASE WHEN DATENAME(dw, @EndDate) = 'Saturday' THEN 1 ELSE 0 END)跨度>
                          • datename 函数取决于区域设置。一个更强大但也更模糊的解决方案是将最后两行替换为:-(case datepart(dw, @StartDate)+@@datefirst when 8 then 1 else 0 end) -(case datepart(dw, @EndDate)+@@datefirst when 7 then 1 when 14 then 1 else 0 end)
                          • 为了澄清@Sequenzia 的评论,您将完全删除有关星期日的案例陈述,只留下+(CASE WHEN DATENAME(dw, @StartDate) = 'Saturday' THEN 1 ELSE 0 END) - (CASE WHEN DATENAME(dw, @EndDate) = 'Saturday' THEN 1 ELSE 0 END)
                          猜你喜欢
                          • 2018-08-03
                          • 2014-10-20
                          • 1970-01-01
                          • 1970-01-01
                          • 2012-02-13
                          • 1970-01-01
                          • 1970-01-01
                          相关资源
                          最近更新 更多