【问题标题】:SQL DateDiff advanced usage?SQL DateDiff 高级用法?
【发布时间】:2010-10-10 06:48:08
【问题描述】:

我需要计算两个日期之间的 DateDiff(小时),但仅限于工作时间(8:30 - 16:00,无周末)。该结果将按照以下示例放入 Reaction_Time 列。

ID 日期 Reaction_Time 过期 1 2003 年 4 月 29 日 15:00:00 1 2003 年 4 月 30 日 11:00:00 3:30 2 2003 年 4 月 30 日 14:00:00 2 01.05.2003 14:00:00 7:30 是

*注意:我没有检查示例中的日期是否为假期。

我使用的是 SQL Server 2005

这将与更大的查询相结合,但现在我只需要这个就可以开始了,我会尝试自己弄清楚如何将它们组合在一起。感谢您的帮助!

编辑:嘿,谢谢大家的回复。但由于 SQL 端解决方案的明显复杂性,因此决定我们将在 Excel 中执行此操作,因为无论如何报表都将在此处移动。很抱歉给您带来麻烦,但我真的认为它会比这更简单。事实上,我们只是没有时间。

【问题讨论】:

  • 我不太明白这个问题
  • 我也没有。现在读了几遍,无济于事。您至少能给我们一些原始表格中的数据吗?理想情况下,使您的示例结果合理的数据。
  • 我猜他想对具有相同 ID 的行执行 DateDiff,但只计算营业时间
  • @Zan:您没有时间将此处发布的工作解决方案之一移动到您的数据库中,但您确实有时间在Excel?我不明白...
  • @Tomalak:Excel 不是我的工作,家里有专家可以做所有这些。我在实习,我需要做最基本的任务的时间太多了,所以有时我需要偷工减料来满足 DL 的要求。抱歉,如果您冒犯了,我非常感谢您的帮助。

标签: sql datediff


【解决方案1】:

此功能将为您提供两个给定时间之间的营业时间差。这将根据日期部分参数返回以分钟或小时为单位的差异。

CREATE FUNCTION [dbo].[fnBusinessHoursDateDiff] (@StartTime SmallDatetime, @EndTime SmallDateTime, @DatePart varchar(2)) RETURNS DECIMAL (10,2)
AS 
BEGIN

DECLARE @Minutes        bigint
    ,   @FinalNumber    Decimal(10,2)

-- // Create Minute By minute table for CTE
-- ===========================================================
;WITH  cteInputHours (StartTime, EndTime, NextTime) AS (
    SELECT  @StartTime  
        ,   @EndTime    
        ,   dateadd(mi, 1, @StartTime)
 ),
 cteBusinessMinutes (TimeOfDay, [isBusHour], NextTime) AS(
    SELECT  StartTime [TimeOfDay]
        ,   case when datepart(dw, StartTime) between 2 and 6 and convert(time,StartTime) between '08:30' and '15:59' then 1 else 0 end [isBusHour]
        ,   dateadd(mi, 1, @StartTime)  [NextTime]
    FROM    cteInputHours
    UNION ALL
    SELECT  dateadd(mi, 1, (a.TimeOfDay)) [TimeOfDay]
        ,   case when datepart(dw, a.TimeOfDay) between 2 and 6 and  convert(time,dateadd(mi, 1, (a.TimeOfDay)) ) between '08:30' and '15:59' then 1 else 0 end [isBusHour]
        ,   dateadd(mi, 2, (a.TimeOfDay)) NextTime
    FROM    cteBusinessMinutes a
    WHERE   dateadd(mi, 1, (a.TimeOfDay)) < @EndTime
) 
SELECT  @Minutes = count(*)
FROM    cteBusinessMinutes
WHERE   isBusHour = 1
OPTION (MAXRECURSION 0);

-- // Final Select
-- ===========================================================
SELECT  @FinalNumber = @Minutes / (case when @DatePart = 'hh' then 60.00 else 1 end)

RETURN @FinalNumber 

END

【讨论】:

    【解决方案2】:

    使用此代码:找出日期之间的周末

     (
        DATEDIFF(dd, open_date, zassignment_date) + 1 
        - ( (DATEDIFF(dd, open_date, zassignment_date) + 1) 
        -(DATEDIFF(wk, open_date, zassignment_date) * 2) 
        -(CASE WHEN DATENAME(dw,  open_date) = 'Sunday' THEN 1 ELSE 0 END) 
        -(CASE WHEN DATENAME(dw, zassignment_date) = 'Saturday' THEN 1 ELSE 0 END) )) wk_end 
    

    【讨论】:

      【解决方案3】:

      我建议构建一个用户定义的函数,根据您的规则计算营业时间的日期差异。

      SELECT
        Id,
        MIN(Date) DateStarted,
        MAX(Date) DateCompleted,
        dbo.udfDateDiffBusinessHours(MIN(Date), MAX(Date)) ReactionTime
      FROM
        Incident
      GROUP BY
        Id
      

      我不确定您的 Overdue 值来自哪里,所以我在示例中省略了它。

      在函数中,您可以编写比在查询中更具表现力的 SQL,而且您不会用业务规则阻塞查询,从而使其难以维护。

      一个函数也可以很容易地被重用。将其扩展为包括对假期的支持(我在这里考虑Holidays 表)不会太难。无需更改难以阅读的嵌套 SELECT/CASE WHEN 构造,也可以进行进一步的改进。

      如果我今天有时间,我会考虑编写一个示例函数。


      编辑:这里有一些花里胡哨的东西,在周末透明地计算:

      ALTER FUNCTION dbo.udfDateDiffBusinessHours (
        @date1 DATETIME,
        @date2 DATETIME
      ) RETURNS DATETIME AS
      BEGIN
        DECLARE @sat INT
        DECLARE @sun INT
        DECLARE @workday_s INT
        DECLARE @workday_e INT
        DECLARE @basedate1 DATETIME
        DECLARE @basedate2 DATETIME
        DECLARE @calcdate1 DATETIME
        DECLARE @calcdate2 DATETIME
        DECLARE @cworkdays INT
        DECLARE @cweekends INT
        DECLARE @returnval INT
      
        SET @workday_s = 510 -- work day start:  8.5 hours
        SET @workday_e = 960 -- work day end:   16.0 hours
      
          -- calculate Saturday and Sunday dependent on SET DATEFIRST option
        SET @sat = CASE @@DATEFIRST WHEN 7 THEN 7 ELSE 7 - @@DATEFIRST END 
        SET @sun = CASE @@DATEFIRST WHEN 7 THEN 1 ELSE @sat + 1 END 
      
        SET @calcdate1 = @date1
        SET @calcdate2 = @date2
      
        -- @date1: assume next day if start was after end of workday
        SET @basedate1 = DATEADD(dd, 0, DATEDIFF(dd, 0, @calcdate1))
        SET @calcdate1 = CASE WHEN DATEDIFF(mi, @basedate1, @calcdate1) > @workday_e
                         THEN @basedate1 + 1
                         ELSE @calcdate1
                         END
      
        -- @date1: if Saturday or Sunday, make it next Monday
        SET @basedate1 = DATEADD(dd, 0, DATEDIFF(dd, 0, @calcdate1))
        SET @calcdate1 = CASE DATEPART(dw, @basedate1)
                         WHEN @sat THEN @basedate1 + 2
                         WHEN @sun THEN @basedate1 + 1
                         ELSE @calcdate1
                         END
      
        -- @date1: assume @workday_s as the minimum start time
        SET @basedate1 = DATEADD(dd, 0, DATEDIFF(dd, 0, @calcdate1))
        SET @calcdate1 = CASE WHEN DATEDIFF(mi, @basedate1, @calcdate1) < @workday_s 
                         THEN DATEADD(mi, @workday_s, @basedate1)
                         ELSE @calcdate1
                         END
      
        -- @date2: assume previous day if end was before start of workday
        SET @basedate2 = DATEADD(dd, 0, DATEDIFF(dd, 0, @calcdate2))
        SET @calcdate2 = CASE WHEN DATEDIFF(mi, @basedate2, @calcdate2) < @workday_s
                         THEN @basedate2 - 1
                         ELSE @calcdate2
                         END
      
        -- @date2: if Saturday or Sunday, make it previous Friday
        SET @basedate2 = DATEADD(dd, 0, DATEDIFF(dd, 0, @calcdate2))
        SET @calcdate2 = CASE DATEPART(dw, @calcdate2)
                         WHEN @sat THEN @basedate2 - 0.00001
                         WHEN @sun THEN @basedate2 - 1.00001
                         ELSE @date2
                         END
      
        -- @date2: assume @workday_e as the maximum end time
        SET @basedate2 = DATEADD(dd, 0, DATEDIFF(dd, 0, @calcdate2))
        SET @calcdate2 = CASE WHEN DATEDIFF(mi, @basedate2, @calcdate2) > @workday_e
                         THEN DATEADD(mi, @workday_e, @basedate2)
                         ELSE @calcdate2
                         END
      
        -- count full work days (subtract Saturdays and Sundays)
        SET @cworkdays = DATEDIFF(dd, @basedate1, @basedate2)
        SET @cweekends = @cworkdays / 7
        SET @cworkdays = @cworkdays - @cweekends * 2
      
        -- calculate effective duration in minutes
        SET @returnval = @cworkdays * (@workday_e - @workday_s)
                         + @workday_e - DATEDIFF(mi, @basedate1, @calcdate1) 
                         + DATEDIFF(mi, @basedate2, @calcdate2) - @workday_e
      
        -- return duration as an offset in minutes from date 0
        RETURN DATEADD(mi, @returnval, 0)
      END
      

      该函数返回一个DATETIME 值,表示从日期0(即"1900-01-01 00:00:00")开始的偏移量。例如,8:00 小时的时间跨度为"1900-01-01 08:00:00",25 小时为"1900-01-02 01:00:00"。函数结果是两个日期之间的时间营业时间差。加班无特殊处理/支持。

      SELECT dbo.udfDateDiffBusinessHours('2003-04-29 15:00:00', '2003-04-30 11:00:00')
      --> 1900-01-01 03:30:00.000
      
      SELECT dbo.udfDateDiffBusinessHours('2003-04-30 14:00:00', '2003-05-01 14:00:00')
      --> 1900-01-01 07:30:00.000
      

      @date1 是下班时间时,该函数假定下一个可用工作日(08:30)的开始,当@date2 是下一个可用工作日(16:00)的结束时下班时间。

      “下一个/上一个可用”是指:

      • 如果@date1'2009-02-06 07:00:00'(星期五),它将变为'2009-02-06 08:30:00'(星期五)
      • 如果@date1'2009-02-06 19:00:00'(星期五),它将变为'2009-02-09 08:30:00'(星期一)
      • 如果@date2'2009-02-09 07:00:00'(星期一),它将变为'2009-02-06 16:00:00'(星期五)
      • 如果@date2'2009-02-09 19:00:00'(星期一),它将变为'2009-02-09 16:00:00'(星期一)

      【讨论】:

      • 我们这里没有任何营业时间参考日历,这就是为什么我支持您自己创建规则的想法。我没有向数据库添加表的权限,因此必须在同一个查询中创建函数等。但是谢谢,我会调查一下。 :)
      • 布局和 cmets 在这个解决方案中非常棒。但它对我不起作用,因为它假设至少已经过去一周(SET @cweekends = @cworkdays / 7)。如果间隔是星期四到星期二,则不考虑周末。不过,我可能会尝试更改此查询。如果我让它适合我的情况,我会发帖并给你 Tomalak 适当的信任。尽管没有任何承诺,但我对 SQL 的熟练程度并不高! :)
      • 发布了我的解决方案:stackoverflow.com/questions/3503749/…
      • 我对此进行了修改以制作“在公司的 SLA 范围内发货”报告。干得好!
      • @Jason 感谢您的反馈! :)
      【解决方案4】:
      DECLARE @BusHourStart DATETIME, @BusHourEnd DATETIME
      SELECT @BusHourStart = '08:30:00', @BusHourEnd = '16:00:00'
      DECLARE @BusMinutesStart INT, @BusMinutesEnd INT
      SELECT @BusMinutesStart = DATEPART(minute,@BusHourStart)+DATEPART(hour,@BusHourStart)*60, 
      @BusMinutesEnd = DATEPART(minute,@BusHourEnd)+DATEPART(hour,@BusHourEnd)*60 
      DECLARE @Dates2 TABLE (ID INT, DateStart DATETIME, DateEnd DATETIME)
      INSERT INTO @Dates2
      SELECT 1, '15:00:00 04/29/2003', '11:00:00 04/30/2003' UNION
      SELECT 2, '14:00:00 04/30/2003', '14:00:00 05/01/2003' UNION
      SELECT 3, '14:00:00 05/02/2003', '14:00:00 05/06/2003' UNION
      SELECT 4, '14:00:00 05/02/2003', '14:00:00 05/04/2003' UNION
      SELECT 5, '07:00:00 05/02/2003', '14:00:00 05/02/2003' UNION
      SELECT 6, '14:00:00 05/02/2003', '23:00:00 05/02/2003' UNION
      SELECT 7, '07:00:00 05/02/2003', '08:00:00 05/02/2003' UNION
      SELECT 8, '22:00:00 05/02/2003', '23:00:00 05/03/2003' UNION
      SELECT 9, '08:00:00 05/03/2003', '23:00:00 05/04/2003' UNION
      SELECT 10, '07:00:00 05/02/2003', '23:00:00 05/02/2003' 
      
      -- SET DATEFIRST to U.S. English default value of 7.
      SET DATEFIRST 7
      
      SELECT ID, DateStart, DateEnd, CONVERT(VARCHAR, Minutes/60) +':'+ CONVERT(VARCHAR, Minutes % 60) AS ReactionTime
      FROM ( 
          SELECT ID, DateStart, DateEnd, Overtime,
              CASE 
                  WHEN DayDiff = 0 THEN 
                      CASE 
                          WHEN (MinutesEnd - MinutesStart - Overtime) > 0 THEN (MinutesEnd - MinutesStart - Overtime) 
                          ELSE 0 
                          END
                  WHEN DayDiff > 0  THEN 
                      CASE 
                          WHEN (StartPart + EndPart - Overtime) > 0 THEN (StartPart + EndPart - Overtime) 
                          ELSE 0 
                          END + DayPart
                  ELSE 0
              END AS Minutes 
          FROM(
              SELECT ID, DateStart, DateEnd, DayDiff, MinutesStart, MinutesEnd,
                      CASE WHEN(@BusMinutesStart - MinutesStart) > 0 THEN (@BusMinutesStart - MinutesStart) ELSE 0 END +
                      CASE WHEN(MinutesEnd - @BusMinutesEnd) > 0 THEN (MinutesEnd - @BusMinutesEnd) ELSE 0 END AS Overtime, 
                      CASE WHEN(@BusMinutesEnd - MinutesStart) > 0 THEN (@BusMinutesEnd - MinutesStart) ELSE 0 END AS StartPart,
                      CASE WHEN(MinutesEnd - @BusMinutesStart) > 0 THEN (MinutesEnd - @BusMinutesStart) ELSE 0 END AS EndPart,
                      CASE WHEN DayDiff > 1 THEN (@BusMinutesEnd - @BusMinutesStart)*(DayDiff - 1) ELSE 0 END AS DayPart
              FROM (
                      SELECT DATEDIFF(d,DateStart, DateEnd) AS DayDiff, ID, DateStart, DateEnd,  
                      DATEPART(minute,DateStart)+DATEPART(hour,DateStart)*60 AS MinutesStart,
                      DATEPART(minute,DateEnd)+DATEPART(hour,DateEnd)*60 AS MinutesEnd 
                      FROM (
                              SELECT ID,
                                      CASE 
                                              WHEN DATEPART(dw, DateStart) = 7 
                                              THEN DATEADD(SECOND, 1, DATEADD(DAY, DATEDIFF(DAY, 0, DateStart), 2))
                                              WHEN DATEPART(dw, DateStart) = 1 
                                              THEN DATEADD(SECOND, 1, DATEADD(DAY, DATEDIFF(DAY, 0, DateStart), 1))
                                      ELSE DateStart END AS DateStart,
                                      CASE 
                                              WHEN DATEPART(dw, DateEnd) = 7 
                                              THEN DATEADD(SECOND, -1, DATEADD(DAY, DATEDIFF(DAY, 0, DateEnd), 0))
                                              WHEN DATEPART(dw, DateEnd) = 1 
                                              THEN DATEADD(SECOND, -1, DATEADD(DAY, DATEDIFF(DAY, 0, DateEnd), -1))
                                      ELSE DateEnd END AS DateEnd FROM @Dates2
                      )Weekends
              )InMinutes
          )Overtime
      )Calculation
      

      【讨论】:

      • 有没有一种方法可以在不丢失回车和其他空白的情况下复制此代码?
      • 好吧,让我在这里强调一点 :) 我问如何“在不丢失回车和其他空白的情况下复制这段代码”,这意味着我的猴子般的能力扩展到了足够多的程度来尝试复制并粘贴。但我一直在丢失回车等:)
      • 普通的复制/粘贴对我来说总是有效的 - 空白和所有。
      • IE 7.0 - 我的雇主“积极劝阻”安装任何替代方案
      • 是的,这是浏览器问题。用 IE 试过 - 确实所有的代码块格式都丢失了。
      【解决方案5】:

      假设您有工作日(及其小时数)的参考表,那么我将使用 3 阶段方法(伪 sql)

      (首先排除“一天之内”的琐碎示例,因为这简化了逻辑)

       -- days that are neither the start nor end (full days)
       SELECT @FullDayHours = SUM(day start to day end)
       FROM   reference-calendar
       WHERE  Start >= midnight-after-start and End <= midnight-before-end
      
       -- time after the [query start] to the end of the first working day
       SELECT @FirstDayHours = [query start] to day end
       FROM   reference-calandar
       WHERE  start day
      
       -- time from the start of the last working day to the [query end]
       SELECT @LastDayHours = day start to [query end]
       FROM   reference-calendar
       WHERE  end-day
      
       IF @FirstDayHours < 0 SET @FirstDayHours = 0 -- starts outside working time
       IF @LastDayHours < 0 SET @LastDayHours  = 0 -- ends outside working time
      
       PRINT @FirstDayHours  + @FullDayHours + @LastDayHours
      

      显然,如果没有更多上下文,要正确地做有点困难......

      【讨论】:

      • 如果你只想要一个答案也没关系,如果你想处理整个表呢?
      【解决方案6】:
      select datediff(hh,@date1,@date2) - 16.5*(datediff(dd,@date1,@date2))
      

      唯一的问题是它会给你 3:30 作为 3.5 小时,但你可以很容易地解决这个问题。

      【讨论】:

      • 如果第二个事件发生在 17:00 怎么办?它没有在问题中说明这是否可能。如果不可能,您的答案似乎很好:)
      • 试过了,但它不能识别实际的开始和结束时间,或者什么。例如,如果 @date1 = '2009/01/2 16:00' 和 @date2 = '2009/1/3 09:00',则 datediff 返回 0.5 小时。但是如果我将日期一个时钟更改为 15:30,它会返回 1.5 小时,而它应该只是 1。怎么会?
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-10-19
      • 1970-01-01
      • 2010-11-16
      相关资源
      最近更新 更多