计算两个date之间的工作日

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

星期一到星期五,它必须是T-SQL。

对于星期一到星期五的工作日,您可以使用一个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) 

如果你想包括假期,你必须解决一些问题。

在“ 计算工作日”中,您可以find关于此主题的好文章,但正如您所看到的那样,并不是那么先进。

 --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 

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

所有信贷Bogdan马克西姆&彼得Mortensen。 这是他们的职位,我只是增加了假期的function(这假定你有一个表“tblHolidays”与date时间字段“HolDate”。

 --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) */ 

我接受的答案作为一个函数使用DATEPART ,所以我不必做一个string比较的行

 DATENAME(dw, @StartDate) = 'Sunday' 

无论如何,这是我的业务过时function

 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 

(我有点评论特权害羞)

如果您决定放弃CMS优雅解决scheme中的+1天,请注意,如果您的开始date和结束date在同一个周末,您会得到一个否定答案。 即,2008/10/26至2008/10/26返回-1。

我相当简单的解决scheme:

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

..也将所有错误的post与结束date之后的开始 date设置为零。 你可能会或可能不会在寻找的东西。

计算工作日的另一种方法是使用WHILE循环,该循环基本上在整个date范围内迭代,并且每当星期一至星期五发现天时,就将其递增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 

尽pipeWHILE循环选项比较干净,使用的代码行less,但它可能会成为环境中的性能瓶颈,特别是当date范围跨越数年时。

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

  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 

对于date之间的差异,包括假期我去这样的:

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 

希望我能帮上忙。

干杯

这是一个很好的版本(我认为)。 假期表包含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 

使用date表:

  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创build一个数字表。

 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 
 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 

我在这里采取了各种示例,但在我的特殊情况下,我们有@PromisedDate交付和一个@ReceivedDate实际收到的项目。 当在“PromisedDate”之前接收到一个项目时,除非我按照日历顺序传递给函数,否则计算的总计不正确。 每次不想检查date,我改变了function来处理这个对我来说。

 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 

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

 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 

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

 datediff(day, <start>, <end>) - 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, ...)总是使用星期六到星期天的边界数周,所以expression式是确定性的,不需要修改(只要我们对周一至周五的定义一致)。date编号根据@@datefirst设置的不同而不同,修改后的计算用一些模块化算术的小复杂度来处理这种修正。

处理星期六/星期天事情的一个更清洁的方法是在提取星期值之前翻译date。 在移动之后,数值将回到固定的(也许更为熟悉的)编号,从星期天的1开始到星期六的7结束。

 datediff(day, <start>, <end>) - 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 

至less到2002年,我已经追踪了这个解决scheme的forms,还有一篇Itzik Ben-Gan的文章。 ( https://technet.microsoft.com/en-us/library/aa175781(v=sql.80).aspx )虽然它需要一个小的调整,因为较新的datetypes不允许date算术,否则它是相同的。

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

对我来说,@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 

创buildfunction如下:

 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 
 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 

结束

我发现下面的TSQL一个相当优雅的解决scheme(我没有权限运行function)。 我发现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*/