使用SQL Server 2008,我需要将两个日期时间字段之间的业务分钟相加,同时考虑非工作时间和周末/公司假期。如果可能的话,我想合并一个日历,以便我可以编辑任何可以轻松完成的假期。
E.g。
OpenCall CloseCall
05/08/2013 14:00:00 06/08/2013 09:30:00
以上结果,需要退货:240 - (4小时)工作时间为:08:30-17:00。
如果电话在星期五开放并在星期二休息,那么它应该只计算星期五,星期一和星期二(即周末)的工作时间之间的分钟数。
我是SQL / T-SQL的新手,所以请清楚地解释一下代码/变量 - 如果你能找到一个简洁的解决方案!
提前致谢!
答案 0 :(得分:1)
首先,这是我使用的结构,我认为它不会适应你的结构。
(注意我会在您的日历表中推荐更多字段,但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
第一步是在两个日期之间获取所有日期。您可以通过加入日历表来完成此操作,其中日历表中的日期位于开始日期和结束日期之间:
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;
对于示例数据,这将给出
+---------------------+---------------------+------------+----------+----------+
| 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 |
+---------------------+---------------------+------------+----------+----------+
如您所见,第一天它使用源数据的开放时间,并且在每个范围的最后一天使用源数据的关闭时间,对于所有其他开始/结束时间,它使用硬编码营业时间(在这种情况下,上午9点至下午5点30分)。
最后一步只是总结每个范围的开始时间和结束时间之间的差异:
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;
给出最终结果:
+---------------------+---------------------+--------------------+
| 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 |
+---------------------+---------------------+--------------------+
<强> Example on SQL Fiddle 强>
答案 1 :(得分:0)
这是我的尝试。目标是获取此查询而不包含期间中每个日期的日期表。我认为这可以在很长一段时间内更快地运行,但还没有测试过。
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