SQL:有效地获得2个日期时间之间的小时数,而不包括周末

时间:2014-07-12 16:59:51

标签: sql sql-server date datetime

这与此处发现的问题类似......

Count work days between two dates

最大的区别在于我需要以小时为单位计算我的范围,我需要考虑部分天数,其中一些可能是周末。例如,我看到的大多数代码示例都是周六和周日整体,就像这样可以返回该范围内的非周末天数...

SELECT @WeekDays = 
   (DATEDIFF(dd, @StartDate, @EndDate) + 1)
  -(DATEDIFF(wk, @StartDate, @EndDate) * 2)
  -(CASE WHEN DATENAME(dw, @StartDate) = 'Sunday' THEN 1 ELSE 0 END)
  -(CASE WHEN DATENAME(dw, @EndDate) = 'Saturday' THEN 1 ELSE 0 END)

我需要这样的东西,但就小时而言。大多数论坛/解决方案会让我将周末日数增加24倍,如果我输入“星期六中午”到“星期一下午6点”之类的东西,那就不行了。我将从总小时数减去48小时而不是36小时。

为了记录,这是我现在正在做的,以便在范围内找到所有小时...

SELECT @HourCount = DATEDIFF(hh, @StartDate, @EndDate)

我愿意在一次移动中正确计算它,或者使用上面的@HourCount并减去范围内的“周末小时数”。只是不确定如何准确地做任何一个。

最后请注意,所有重要的效率事情。从技术上讲,我可能会使用while循环并从@StartDate迭代到@EndDate,同时增加小时(甚至分钟),检查当前日期,如果不是周末,则将总数加1。这可能是我最糟糕的情况,但我正在寻找一种比这更有效的解决方案。欢迎任何建议或解决方案。

2 个答案:

答案 0 :(得分:0)

未经详尽测试,但有一个版本;

WITH cte AS (
  SELECT start_time, end_time,
    (@@datefirst  + datepart(weekday, start_time)) % 7 start_day,
    (@@datefirst  + datepart(weekday, end_time  )) % 7 end_day
  FROM test
), cte2 AS (
  SELECT CASE WHEN start_day < 2 THEN CAST(start_time + 2 - start_day AS DATE)
                                 ELSE start_time END start_time,
         CASE WHEN end_day   < 2 THEN CAST(end_time - end_day AS DATE)
                                 ELSE end_time END end_time
  FROM cte
)
SELECT DATEDIFF(hh, start_time, end_time) - 
       DATEDIFF(wk, start_time, end_time) * 48 diff
FROM cte2;

第一个公用表表达式获取星期几,0表示星期六,1表示星期日等开始和结束时间。

第二个公用表格表达式将开始和结束时间调整为永远不会在周末(开始时间向前移动到星期一,结束时间回到星期五(或者说午夜到星期六)。

查询本身只是计算时间之间的差异,调整周末。

我可以在查询中看到的唯一明显问题是:

  • 如果开始和结束都在同一个周末,则返回的时间将为负数,因此您可能需要添加逻辑来处理该问题。

  • 如果某个区域设置考虑周中某周的开始,则可能会计算周末错误,因此在这种情况下您可能需要对此进行调整。

An SQLfiddle with some test cases

答案 1 :(得分:0)

考虑为此查询和其他日期/时间查询创建永久日历表和实用程序编号表。这将允许您使用有效的基于集合的查询,并在除周末之外的非工作日帐户。有关创建和加载这些表的示例脚本,请参阅http://www.dbdelta.com/calendar-table-and-datetime-functions/

下面是一个示例查询,说明了这些表的一种方法,您可以根据需要进行调整。

DECLARE 
     @StartDate datetime = '2014-07-12 12:00:00'
    ,@EndDate datetime = '2024-07-14 18:00:00';
WITH 
    business_days AS (
        SELECT CalendarDate
        FROM dbo.Calendar
        WHERE
            CalendarDate BETWEEN CAST(@StartDate AS date) AND DATEADD(day, 1, @EndDate)
            AND BusinessDay = 1
    )
    ,business_hours AS (
        SELECT DATEADD(hour, Number, CAST(CalendarDate AS datetime)) AS BusinessHour
        FROM business_days
        CROSS JOIN dbo.Numbers
        WHERE Number BETWEEN 0 AND 23
    )
SELECT COUNT(*) AS total_business_hours
FROM business_hours
WHERE
    BusinessHour >= @StartDate
    AND BusinessHour < @EndDate;