SQL Server 2008 - 考虑到自定义假日和周末,将两个日期之间的业务分钟相加

时间:2013-08-06 11:00:11

标签: sql sql-server-2008 tsql

使用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的新手,所以请清楚地解释一下代码/变量 - 如果你能找到一个简洁的解决方案!

提前致谢!

2 个答案:

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