3

使用 SQL Server 2008,我需要将两个日期时间字段之间的营业时间相加,同时考虑到非工作时间和周末/公司假期。如果可能的话,我想加入一个日历,以便我需要编辑任何可以轻松完成的假期。

例如

OpenCall              CloseCall 
05/08/2013 14:00:00   06/08/2013 09:30:00             

以上结果,需要返回:240——(4小时)工作时间为:08:30-17:00。

如果电话在周五打开,周二关闭,那么它应该只计算周五、周一和周二工作时间之间的分钟数(即不是周末)。

我是 SQL/T-SQL 的新手,所以请清楚地解释任何代码/变量 - 如果你能找到一个简洁的解决方案!

提前致谢!

4

2 回答 2

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

SQL Fiddle 示例

于 2013-08-06T11:54:40.287 回答
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
于 2013-08-06T12:38:12.870 回答