Like any other enterprise RDBMS system, SQL Server ships with several built-in functions that make developers’ T-SQL code clean, convenient and reusable. To demonstrate the efficiency of functions, say we needed to retrieve a server name for one of our SQL Server instances. Well, one of doing this would be to write a SELECT statement that would query the system view [sys].[servers] from the master database as shown in Script 1.
与任何其他企业RDBMS系统一样,SQL Server附带了一些内置功能,这些功能使开发人员的T-SQL代码干净,方便和可重用。 为了演示功能的效率,假设我们需要检索其中一个SQL Server实例的服务器名称。 嗯,这样做之一就是编写SELECT语句,该语句将从master数据库中查询系统视图[sys]。[servers] ,如脚本1所示。
USE master;
GO
SELECT
[name] AS [Server Name]
FROM [sys].[servers];
GO
However, another simpler and cleaner approach is for me to simply call on the @@SERVERNAME built-in function shown in Script 2
但是,另一种更简单,更简洁的方法是让我只需调用脚本2中所示的@@ SERVERNAME内置函数
SELECT @@SERVERNAME AS [Server Name];
GO
Unfortunately, it is impossible for SQL Server to provide built-in functions for everything that developers would need. That is why – as developers – we are given an ability to create our own user-defined functions. One such common user-defined function involves the ability to calculate the total number of working days and working hours within a given date range. I have personally noticed a need for such a function in cases whereby some KPIs relates to determining a total number of days/time it takes to resolve a customer complaint. In this article, we take a look at some of the tricks and T-SQL methods that can be used to easily create a user-defined function that calculates working days and hours.
不幸的是,SQL Server无法为开发人员所需的一切提供内置功能。 因此,作为开发人员,我们拥有创建自己的用户定义函数的能力。 一种此类用户定义的常用功能涉及计算给定日期范围内的工作日总数和工作时间总数的功能。 我个人注意到在某些KPI与确定解决客户投诉所需的总天数/时间有关的情况下,需要使用此功能。 在本文中,我们介绍了一些技巧和T-SQL方法,这些技巧和方法可用于轻松创建用户定义的函数来计算工作日和小时数。
In this approach, we employ several steps that make use of DATEDIFF and DATEPART functions to successfully determine working days.
在这种方法中,我们采用了几个步骤,这些步骤利用DATEDIFF和DATEPART函数成功确定工作日。
In this step, we use the DAY interval within the DATEDIFF function to determine the number of days between two dates. The output of this calculation is stored in the @TotDays variable, as shown in Script 3.
在此步骤中,我们使用DATEDIFF函数中的DAY间隔来确定两个日期之间的天数。 该计算的输出存储在@TotDays变量中,如脚本3所示。
DECLARE @DateFrom DATETIME;
DECLARE @DateTo DATETIME;
SET @DateFrom = '2017-06-03 11:19:11.287';
SET @DateTo = '2017-06-11 13:53:14.750';
DECLARE @TotDays INT= DATEDIFF(DAY, @DateFrom, @DateTo)
Following the execution of Script 3, the value of the @TotDays variable is 8 days as shown in Figure 1.
执行脚本3之后 , @ TotDays变量的值为8天, 如图1所示。
The total of 8 days is actually incorrect as it is excluding the start date. Say for instance that for some reason you ended up working on the 25th of December 2016 – a Christmas day. If you were to perform a working day calculation in SQL Server using the DATEDIFF function as shown in Script 4, you would get an incorrect result of 0 total days as shown in Figure 2.
实际上,总共8天是错误的,因为它不包括开始日期。 举例来说,由于某种原因,您最终于2016年12月25 日 (圣诞节)工作。 如果要使用DATEDIFF函数在SQL Server中执行工作日计算(如脚本4所示),则会得到不正确的结果,即0天总数, 如图2所示。
SET @DateFrom = '2016-12-25';
SET @DateTo = '2016-12-25';
DECLARE @TotDays INT= DATEDIFF(DAY, @DateFrom, @DateTo) AS [TotalDays];
One way to get around this issue is to always increment the output of a DATEDIFF function by 1, as shown in Script 5.
解决此问题的一种方法是始终将DATEDIFF函数的输出增加1,如脚本5所示。
DECLARE @TotDays INT= DATEDIFF(DAY, @DateFrom, @DateTo) + 1 AS [TotalDays];
Following the increment by 1, the total number of days shown in Figure 1 changes from 8 to 9 as shown in Figure 3.
随着增加1, 图1中显示的总天数从8变为9, 如图3所示。
Once we have obtained the total number of days, we now need to:
一旦获得了总天数,我们现在需要:
In order to do this calculation, we again use the DATEDIFF function but this time we change the interval to week (represented as WEEK or WK). The output of the week calculation is stored in the @TotWeeks variable, as shown in Script 6.
为了进行此计算,我们再次使用DATEDIFF函数,但是这次我们将间隔更改为周(表示为WEEK或WK)。 星期计算的输出存储在@TotWeeks变量中,如脚本6所示。
DECLARE @TotWeeks INT= DATEDIFF(WEEK, @DateFrom, @DateTo);
Given the date range specified in Script 3, our week calculation returns 2 as shown in Figure 4.
给定脚本3中指定的日期范围,我们的星期计算将返回2, 如图4所示。
Again, just as in the calculation of days, the output of the week calculation – 2 weeks – is incorrect. This time the issue is as a result of the way that WEEK interval works within the DATEDIFF function. The WEEK interval in DATEDIFF does not actually calculate the number of weeks, instead it calculates the number of instances that a complete weekend appears (combination of Saturday and Sunday) within the specified date range. Consequently, for a more accurate week calculation, we should always multiply the output by 2 – the number of days in a weekend. The revised script for calculating the number of weeks is shown below in Script 7.
同样,与天数的计算一样,周数的计算结果(2周)也不正确。 这次问题是由于DATEDIFF函数中WEEK间隔的工作方式导致的。 DATEDIFF中的WEEK间隔实际上并没有计算周数,而是计算了在指定日期范围内出现完整周末(周六和周日的组合)的实例数。 因此,为了更准确地计算周,我们应该始终将输出乘以2 –周末的天数。 下面的脚本7中显示了用于计算周数的修改后的脚本。
DECLARE @TotWeeks INT= (DATEDIFF(WEEK, @DateFrom, @DateTo) * 2) AS [TotalWeeks];
The output of Script 7 basically doubles what was returned in Figure 4 from 2 to 4 weeks as shown in Figure 5.
脚本7的输出从2周到4周基本上是图4中返回结果的两倍, 如图5所示。
The final steps involve the exclusion of incomplete weekend days from being counted as part of working days. Incomplete weekend days refer to instances whereby the Date From parameter value falls on a Sunday or the Date To parameter value is on a Saturday. The exclusion of incomplete weekends can be done by either using DATENAME or DATEPART functions. Whenever you can, refrain from using the DATEPART in calculating working days as it is affected by your language settings of your SQL Server instance. For instance, Script 8 returns Sunday as day name for both US and British language settings.
最后的步骤涉及将不完整的周末排除在工作日之外。 周末不完整是指“ 日期自”参数值位于星期日或“ 日期至”参数值位于星期六的实例。 使用DATENAME或DATEPART函数可以排除不完整的周末。 尽量避免在计算工作日时使用DATEPART ,因为它受SQL Server实例的语言设置的影响。 例如,对于美国和英国语言设置, 脚本8返回星期日作为日期名称。
SET LANGUAGE us_english;
SELECT DATENAME(weekday, '20170611') [US];
SET LANGUAGE British;
SELECT DATENAME(weekday, '20170611') [British];
However, when DATEPART function is used as shown in Script 9, we get different values for US and British settings.
但是,如脚本9所示使用DATEPART函数时,美国和英国设置将获得不同的值。
SET LANGUAGE us_english;
SELECT DATEPART(weekday, '20170611') [US];
SET LANGUAGE British;
SELECT DATEPART(weekday, '20170611') [British];
Script 10 shows the complete definition for a user defined function that calculates working days by mostly using the DATEDIFF function.
脚本10显示了用户定义函数的完整定义,该函数主要使用DATEDIFF函数来计算工作日。
CREATE FUNCTION [dbo].[fn_GetTotalWorkingDays]
(
@DateFrom Date,
@DateTo Date
)
RETURNS INT
AS
BEGIN
DECLARE @TotDays INT= DATEDIFF(DAY, @DateFrom, @DateTo) + 1;
DECLARE @TotWeeks INT= DATEDIFF(WEEK, @DateFrom, @DateTo) * 2;
DECLARE @IsSunday INT= CASE
WHEN DATENAME(WEEKDAY, @DateFrom) = 'Sunday'
THEN 1
ELSE 0
END;
DECLARE @IsSaturday INT= CASE
WHEN DATENAME(WEEKDAY, @DateTo) = 'Saturday'
THEN 1
ELSE 0
END;
DECLARE @TotWorkingDays INT= @TotDays - @TotWeeks - @IsSunday + @IsSaturday;
RETURN @TotWorkingDays;
END
Now if we call this function as shown in Script 11, 5 is returned in Figure 8 – which is actually the correct number of working days between the 3rd and 11th of June 2017.
这实际上是第三届 ,六月2017 第 11个工作日正确的数字-现在,如果我们把这个功能,如图11的脚本 ,5 如图8回复。
SELECT [dbo].[fn_GetTotalWorkingDays] ('2017-06-03','2017-06-11')
Another approach to calculating working days is to use a WHILE loop which basically iterates through a date range and increment it by 1 whenever days are found to be within Monday – Friday. The complete script for calculating working days using the WHILE loop is shown in Script 12.
另一种计算工作日的方法是使用WHILE循环,该循环基本上会遍历日期范围,并在发现星期一至星期五的某几天内将其递增1。 脚本12中显示了使用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
Although the WHILE loop option is cleaner and uses less lines of code, it has the potential of being a performance bottleneck in your environment particularly when your date range spans across several years.
尽管WHILE循环选项更干净并且使用的代码行更少,但它可能会成为您环境中的性能瓶颈,尤其是当您的日期范围跨越数年时。
The final section of this article involves the calculation of working hours based on a given date range.
本文的最后一部分涉及基于给定日期范围的工作时间计算。
In this step, we use a similar approach to the previous sections whereby we calculate the total working days. The only difference is that we are not incrementing the output by 1 as shown in Script 13.
在此步骤中,我们使用与前面各节类似的方法来计算总工作日。 唯一的区别是,我们没有按脚本13所示将输出增加1。
DECLARE @TotalWorkDays INT, @TotalTimeDiff DECIMAL(18, 2), @DateFrom DATETIME, @DateTo DATETIME;
SET @DateFrom = '2017-06-05 11:19:11.287';
SET @DateTo = '2017-06-07 09:53:14.750';
SET @TotalWorkDays = DATEDIFF(DAY, @DateFrom, @DateTo)
-(DATEDIFF(WEEK, @DateFrom, @DateTo) * 2)
-CASE
WHEN DATENAME(WEEKDAY, @DateFrom) = 'Sunday'
THEN 1
ELSE 0
END+CASE
WHEN DATENAME(WEEKDAY, @DateTo) = 'Saturday'
THEN 1
ELSE 0
END;
The next part involves getting a difference in seconds between the two dates and converting that difference into hours by dividing by 3600.0 as shown in Script 14.
下一部分涉及两个日期之间以秒为单位的差异,并将该差异转换为小时,方法是将其除以3600.0 ,如脚本14所示。
SET @TotalTimeDiff =
(
SELECT DATEDIFF(SECOND,
(
SELECT CONVERT(TIME, @DateFrom)
),
(
SELECT CONVERT(TIME, @DateTo)
)) / 3600.0
);
The last part involves multiplying the output of Step 1 by 24 (total number of hours in a day) and then later adding that to the output of Step 2 as shown in Script 15.
最后一部分涉及将步骤1的输出乘以24(一天的总小时数),然后再将其添加到步骤2的输出中,如脚本15所示。
SELECT(@TotalWorkDays * 24.00) + @TotalTimeDiff;
Finally, the complete script that can be used to create a user defined function for calculating working hours is shown in Script 16 and its application is shown in Figure 9.
最后,可用于创建用于计算的工作时间在用户定义函数的完整脚本中示出了脚本16及其应用在图9中示出。
CREATE FUNCTION [dbo].[fn_GetTotalWorkingHours]
(
@DateFrom Datetime,
@DateTo Datetime
)
RETURNS DECIMAL(18,2)
AS
BEGIN
DECLARE @TotalWorkDays INT, @TotalTimeDiff DECIMAL(18, 2)
SET @TotalWorkDays = DATEDIFF(DAY, @DateFrom, @DateTo)
-(DATEDIFF(WEEK, @DateFrom, @DateTo) * 2)
-CASE
WHEN DATENAME(WEEKDAY, @DateFrom) = 'Sunday'
THEN 1
ELSE 0
END+CASE
WHEN DATENAME(WEEKDAY, @DateTo) = 'Saturday'
THEN 1
ELSE 0
END;
SET @TotalTimeDiff =
(
SELECT DATEDIFF(SECOND,
(
SELECT CONVERT(TIME, @DateFrom)
),
(
SELECT CONVERT(TIME, @DateTo)
)) / 3600.0
);
RETURN (SELECT(@TotalWorkDays * 24.00) + @TotalTimeDiff)
END
GO
翻译自: https://www.sqlshack.com/how-to-calculate-work-days-and-hours-in-sql-server/