【问题标题】:SQL: Efficiently get the number of hours between 2 Datetimes while excluding weekendsSQL:有效地获取 2 个日期时间之间的小时数,同时不包括周末
【发布时间】:2014-07-12 16:59:50
【问题描述】:

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

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。这可能是我最糟糕的情况,但我正在寻找一种比这更有效的解决方案。欢迎任何建议或解决方案。

【问题讨论】:

  • 您使用的是哪个 SQL Server 版本?

标签: sql sql-server date datetime


【解决方案1】:

没有详尽测试,只有一个版本;

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 代表星期日等。

第二个公用表表达式将开始和结束时间调整为从不在周末(开始时间向前移动到星期一,结束时间向后移动到星期五(或者更确切地说是从午夜到星期六)。

然后查询本身只计算时间之间的差异,并根据周末进行调整。

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

  • 如果 start 和 end 都在同一个周末,则返回的时间将为负数,因此您可能需要添加逻辑来处理它。

  • 如果某些语言环境认为一周的开始是在周中的某个地方,它可能会错误地计算周末,因此在这种情况下您可能需要对此进行调整。

An SQLfiddle with some test cases.

【讨论】:

  • 这似乎工作得很好。我对其进行了一些调整,以便输入日期作为参数而不是来自特定表。在我确认这是解决方案之前,我会进行更多测试。
  • @ThisLanham 测试进行得如何?我想知道任何问题:)
【解决方案2】:

考虑为此和其他日期/时间查询创建一个永久日历表和实用程序编号表。这将允许您使用有效的基于集合的查询,并考虑周末以外的非工作日。有关创建和加载这些表的示例脚本,请参见 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;

【讨论】:

  • 我正在使用客户的系统,因此添加额外的表可能不是一个选项。我同意它可以帮助提高效率,但如果我能在合理的时间内使用 SQL 日期函数得到相同的结果,那就更好了。
猜你喜欢
  • 2020-11-26
  • 1970-01-01
  • 2019-12-10
  • 2011-04-06
  • 1970-01-01
  • 1970-01-01
  • 2017-01-05
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多