[英]SQL Server 2008 - Sum business minutes between two dates taking into account custom holidays and weekends
Using SQL Server 2008, I need to sum the business minutes between two datetime fields, while taking into consideration non working hours and weekends/company holidays. 使用SQL Server 2008,我需要将两个日期时间字段之间的业务分钟相加,同时考虑非工作时间和周末/公司假期。 I would like to incorporate a calendar if possible so that should I need to edit any holiday it could be easily done.
如果可能的话,我想合并一个日历,这样我就可以编辑任何可以轻松完成的假期。
Eg 例如
OpenCall CloseCall
05/08/2013 14:00:00 06/08/2013 09:30:00
The result for the above, needs to return: 240 -- (4 hours) working hours are: 08:30-17:00. 结果如上,需要返回:240 - (4小时)工作时间为:08:30-17:00。
If the call was open on Friday and was closed on Tuesday, then it should only calculate the minutes between the working hours on Friday, Monday and Tuesday (ie not the weekend). 如果电话在星期五开放并在星期二关闭,那么它应该只计算星期五,星期一和星期二(即不是周末)的工作时间之间的分钟数。
I'm new to SQL/T-SQL so please explain any code/variables clearly - IF you can find a neat solution! 我是SQL / T-SQL的新手,所以请清楚地解释任何代码/变量 - 如果你能找到一个简洁的解决方案!
Thanks in advance! 提前致谢!
First, This is the structure I have used, I don't think it will take much adapting to fit it into your structure. 首先,这是我使用的结构,我认为它不会适应你的结构。
(Note I would recommend a lot more fields in your calendar table, but IsWorkingDay is the only one required for this example) (注意我会在你的日历表中推荐更多字段,但IsWorkingDay是这个例子中唯一需要的字段)
SET DATEFIRST 1;
CREATE TABLE dbo.Calendar
( [Date] DATE NOT NULL,
IsWorkingDay BIT NOT NULL
CONSTRAINT PK_Calendar_Date PRIMARY KEY ([Date])
);
-- INSERT DATES IN 2013 (NOT DOING A FULL TABLE AS IT'S JUST AN EXAMPLE)
INSERT dbo.Calendar ([Date], IsWorkingDay)
SELECT [Date] = DATEADD(DAY, Number, '20130101'), 1
FROM Master..spt_values
WHERE Type = 'P'
AND Number < 365;
-- UPDATE NON WORKING DAYS
UPDATE dbo.Calendar
SET IsWorkingDay = 0
WHERE DATEPART(WEEKDAY, [Date]) IN (6, 7)
OR [Date] IN ('20130101', '20130329', '20130401', '20130506', '20130527', '20130826', '20131225', '20131226');
-- CREATE SAMPLE DATA
CREATE TABLE T (OpenCall DATETIME NOT NULL, CloseCall DATETIME NOT NULL);
INSERT T (OpenCall, CloseCall)
VALUES
('20130805 14:00:00', '20130806 09:30:00'),
('20130823 16:00:00', '20130828 10:30:00'); -- CROSS BANK HOLIDAY AND WEEKEND
The first step is to get all days between your two dates. 第一步是在两个日期之间获取所有日期。 You can do this by joining to the calendar table where the date in the calender table is between the opening and closing datetimes:
您可以通过加入日历表来执行此操作,其中日历表中的日期位于开始和结束日期时间之间:
SELECT T.OpenCall,
T.CloseCall,
Calendar.[Date],
StartTime = CASE WHEN CAST(T.OpenCall AS DATE) = Calendar.[Date] THEN CAST(T.OpenCall AS TIME) ELSE CAST('08:30' AS TIME) END,
EndTime = CASE WHEN CAST(T.CloseCall AS DATE) = Calendar.[Date] THEN CAST(T.CloseCall AS TIME) ELSE CAST('17:00' AS TIME) END
FROM T
INNER JOIN Calendar
ON Calendar.Date >= CAST(T.OpenCall AS DATE)
AND Calendar.Date <= CAST(T.CloseCall AS DATE)
AND Calendar.IsWorkingDay = 1;
For the example data, this would give 对于示例数据,这将给出
+---------------------+---------------------+------------+----------+----------+
| OpenCall | CloseCall | Date |StartTime | EndTime |
|---------------------+---------------------+------------+----------+----------|
| 2013-08-05 14:00:00 | 2013-08-06 09:30:00 | 2013-08-05 | 14:00:00 | 17:00:00 |
| 2013-08-05 14:00:00 | 2013-08-06 09:30:00 | 2013-08-06 | 08:30:00 | 09:30:00 |
|---------------------+---------------------+------------+----------+----------|
| 2013-08-23 16:00:00 | 2013-08-28 10:30:00 | 2013-08-23 | 16:00:00 | 17:00:00 |
| 2013-08-23 16:00:00 | 2013-08-28 10:30:00 | 2013-08-27 | 08:30:00 | 17:00:00 |
| 2013-08-23 16:00:00 | 2013-08-28 10:30:00 | 2013-08-28 | 08:30:00 | 09:30:00 |
+---------------------+---------------------+------------+----------+----------+
As you can see, on the first day it uses the open time from the source data , and on the last day of each range it uses the close time from source data, for all other start/end times it uses hard coded business hours (in this case 9am-5.30pm). 如您所见,第一天它使用源数据的开放时间,并且在每个范围的最后一天使用源数据的关闭时间,对于所有其他开始/结束时间,它使用硬编码的营业时间(在这种情况下,上午9点至下午5点30分)。
The last step would just be to sum up the difference between the starttime and the endtime for each range: 最后一步只是总结每个范围的开始时间和结束时间之间的差异:
WITH Data AS
( SELECT T.OpenCall,
T.CloseCall,
StartTime = CASE WHEN CAST(T.OpenCall AS DATE) = Calendar.[Date] THEN CAST(T.OpenCall AS TIME) ELSE CAST('08:30' AS TIME) END,
EndTime = CASE WHEN CAST(T.CloseCall AS DATE) = Calendar.[Date] THEN CAST(T.CloseCall AS TIME) ELSE CAST('17:00' AS TIME) END
FROM T
INNER JOIN Calendar
ON Calendar.Date >= CAST(T.OpenCall AS DATE)
AND Calendar.Date <= CAST(T.CloseCall AS DATE)
AND Calendar.IsWorkingDay = 1
)
SELECT OpenCall,
CloseCall,
BusinessMinutes = SUM(DATEDIFF(MINUTE, StartTime, EndTime))
FROM Data
GROUP BY OpenCall, CloseCall;
Giving an end result of: 给出最终结果:
+---------------------+---------------------+--------------------+
| OpenCall | CloseCall | BusinessMinutes |
|---------------------+---------------------+--------------------+
| 2013-08-05 14:00:00 | 2013-08-06 09:30:00 | 240 |
| 2013-08-23 16:00:00 | 2013-08-28 10:30:00 | 690 |
+---------------------+---------------------+--------------------+
Here's my attempt. 这是我的尝试。 The goal was to get this query without table of dates for each date in period.
目标是获取此查询而不包含期间中每个日期的日期表。 I think this could work faster for long periods, but have not tested it.
我认为这可以长时间更快地工作,但还没有测试过。
declare @Start_Time time = '08:30', @End_Time time = '17:00'
declare @Whole_Date_Minutes int = datediff(mi, @Start_Time, @End_Time)
;with cte as (
select
C.OpenCall, C.CloseCall,
cast(C.OpenCall as date) as OpenCallDate,
case when cast(C.OpenCall as time) < @Start_Time then @Start_Time else cast(C.OpenCall as time) end as OpenCallTime,
cast(C.CloseCall as date) as CloseCallDate,
case when cast(C.CloseCall as time) > @End_Time then @End_Time else cast(C.CloseCall as time) end as CloseCallTime
from @Calls as C
), cte2 as (
select
OpenCall, CloseCall, OpenCallDate, OpenCallTime,
case when CloseCallDate > OpenCallDate then OpenCallDate else CloseCallDate end as CloseCallDate,
case when CloseCallDate > OpenCallDate then @End_Time else CloseCallTime end as CloseCallTime
from cte
union all
select
OpenCall, CloseCall, dateadd(dd, 1, OpenCallDate) as OpenCallDate, @Start_Time as OpenCallTime,
CloseCallDate, CloseCallTime
from cte
where CloseCallDate > OpenCallDate
)
select
c.OpenCall, c.CloseCall,
sum(
@Whole_Date_Minutes +
datediff(dd, c.OpenCallDate, CloseCallDate) * @Whole_Date_Minutes -
datediff(mi, @Start_Time, c.OpenCallTime) -
datediff(mi, c.CloseCallTime, @End_Time) -
H.[Days] * @Whole_Date_Minutes
) as BusinessMinutes
from cte2 as c
outer apply (select count(*) as [Days] from @Holidays as H where H.[Date] >= c.OpenCallDate and H.[Date] <= c.CloseCallDate) as H
group by c.OpenCall, c.CloseCall
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.