【问题标题】:SQL calculates total down time minutesSQL 计算总停机时间分钟
【发布时间】:2019-04-03 21:20:30
【问题描述】:

我正在开发一个停机时间管理系统,该系统能够为数据库中的问题保存支持票,我的数据库具有以下列:

-ID
-DateOpen
-DateClosed
-Total

我想获取一天中的分钟总和,考虑到票可以同时进行,例​​如:

ID    |     DateOpen                    |        DateClosed       | Total 
1          2019-04-01 08:00:00 AM            2019-04-01 08:45:00    45
2          2019-04-01 08:10:00 AM            2019-04-01 08:20:00    10
3          2019-04-01 09:06:00 AM            2019-04-01 09:07:00    1
4          2019-04-01 09:06:00 AM            2019-04-01 09:41:00    33

有人可以帮我解决这个问题!! :c

如果我使用查询“SUM”,它将返回 89,但如果您查看日期,您会明白实际结果必须是 78,因为票 2 和票 3 是在另一个票正在工作时启动的......

DECLARE @DateOpen date = '2019-04-01'

SELECT AlarmID, DateOpen, DateClosed, TDT FROM AlarmHistory 
WHERE CONVERT(date,DateOpen) = @DateOpen

【问题讨论】:

    标签: sql sql-server datetime summary gaps-and-islands


    【解决方案1】:

    您需要做的是生成一个整数序列并使用它来生成一天中的时间。在您的开放日期和结束日期之间加入该时间序列,然后计算不同时间的数量。

    这是一个适用于 MySQL 的示例:

     SET @row_num = 0;
    
     SELECT COUNT(DISTINCT time_stamp)
             -- this simulates your dateopen and dateclosed table
       FROM (SELECT '2019-04-01 08:00:00' open_time, '2019-04-01 08:45:00' close_time
             UNION SELECT '2019-04-01 08:10:00', '2019-04-01 08:20:00'
             UNION SELECT '2019-04-01 09:06:00', '2019-04-01 09:07:00'
             UNION SELECT '2019-04-01 09:06:00', '2019-04-01 09:41:00') times_used
       JOIN (
             -- generate sequence of minutes in day
             SELECT TIME(sequence*100) time_stamp
               FROM (
                 -- create sequence 1 - 10000
                     SELECT (@row_num:=@row_num + 1) AS sequence
                       FROM {table_with_10k+_records}
                      LIMIT 10000
                    ) minutes
             HAVING time_stamp IS NOT NULL
              LIMIT 1440
           ) times ON (time_stamp >= TIME(open_time) AND time_stamp < TIME(close_time));         
    

    由于您只选择在结果中找到的不同时间,因此不会计算重叠的分钟数。

    注意:根据您的数据库,可能有更好的方法来生成序列。 MySQL 没有生成序列功能我这样做是为了展示可以轻松转换为与您正在使用的任何数据库一起使用的基本思想。

    【讨论】:

      【解决方案2】:

      @drakin8564's answer 适用于我相信您正在使用的 SQL Server:

      ;WITH Gen AS
      (
          SELECT TOP 1440 
                 CONVERT(TIME, DATEADD(minute, ROW_NUMBER() OVER (ORDER BY (SELECT NULL)), '00:00:00')) AS t
            FROM sys.all_objects a1
           CROSS
            JOIN sys.all_objects a2
      )
      SELECT COUNT(DISTINCT t)
        FROM incidents inci
        JOIN Gen 
          ON Gen.t >= CONVERT(TIME, inci.DateOpen) 
         AND Gen.t < CONVERT(TIME, inci.DateClosed)
      

      你最后一条记录的总数是错误的,说是 33 而它是 35,所以查询结果是 80,而不是 78。

      【讨论】:

      • 如果这解决了您的问题,请接受@drakin8564 的回答,而不是我的。所有功劳归他们所有。
      【解决方案3】:

      顺便说一句,正如 MarcinJ 告诉你的,41 - 6 是 35,而不是 33。所以答案是 80,而不是 78。

      即使日期参数不是只有一天(1,440 分钟),以下解决方案也可以工作。假设日期参数是一个月,甚至一年,这个解决方案仍然有效。

      现场演示:http://sqlfiddle.com/#!18/462ac/5

      -- arranged the opening and closing downtime
      with a as 
      (
          select 
              DateOpen d, 1 status
          from dt
          union all
          select
              DateClosed, 2
          from dt
      )
      -- don't compute the downtime from previous date
      -- if the current date's status is opened
      -- yet the previous status is closed
      , downtime_minutes AS
      (
          select 
              *, 
              lag(status) over(order by d, status desc) as prev_status,
              case when status = 1 and lag(status) over(order by d, status desc) = 2 then
                  null
              else
                  datediff(minute, lag(d) over(order by d, status desc), d)
              end as downtime
          from a
      )
      select sum(downtime) as all_downtime from downtime_minutes;
      

      输出:

      | all_downtime |
      |--------------|
      |           80 |
      

      看看它是如何工作的:

      它通过计算先前停机时间的停机时间来工作。如果当前日期的状态为打开而前一个日期的状态为关闭,则不计算停机时间,这意味着当前停机时间是不重叠的。非重叠停机时间用 null 表示。

      对于新打开的停机时间,其停机时间最初为空,停机时间将在随后的日期计算,直至其关闭。

      可以通过反转条件使代码更短:

      -- arranged the opening and closing downtime
      with a as 
      (
          select 
              DateOpen d, 1 status
          from dt
          union all
          select
              DateClosed, 2
          from dt
          -- order by d. postgres can do this?
      )
      -- don't compute the downtime from previous date
      -- if the current date's status is opened
      -- yet the previous status is closed
      , downtime_minutes AS
      (
          select 
              *, 
              lag(status) over(order by d, status desc) as prev_status,
              case when not ( status = 1 and lag(status) over(order by d, status desc) = 2 ) then
                  datediff(minute, lag(d) over(order by d, status desc), d)
              end as downtime
          from a
      )
      select sum(downtime) from downtime_minutes;
      

      对我最初的解决方案并不特别自豪:http://sqlfiddle.com/#!18/462ac/1


      至于order by d, status desc上的status desc,如果一个DateClosed与其他宕机的DateOpen类似,status desc会先排序DateClosed。

      对于 DateOpened 和 DateClosed 都存在 8:00 的数据:

      INSERT INTO dt
          ([ID], [DateOpen], [DateClosed], [Total])
      VALUES
          (1, '2019-04-01 07:00:00', '2019-04-01 07:50:00', 50),
          (2, '2019-04-01 07:45:00', '2019-04-01 08:00:00', 15),   
          (3, '2019-04-01 08:00:00', '2019-04-01 08:45:00', 45);
      ;
      

      对于类似的时间(例如,8:00),如果我们不在开市之前先对收盘进行排序,那么 7:00 将仅计算到 7:50,而不是直到 8:00,如 8 :00-open 的停机时间最初为零。如果类似日期(例如 8:00)没有 status desc 时,以下是打开和关闭停机时间的安排和计算方式。总停机时间仅为 95 分钟,这是错误的。应该是 105 分钟。

      如果我们在 DateClosed 之前对 DateClosed 进行排序(使用status desc),当它们具有相似的日期(例如 8:00)时,这将是如何安排和计算的。总停机时间为 105 分钟,这是正确的。

      【讨论】:

        【解决方案4】:

        另一种方法,使用gaps and islands 方法。答案基于SQL Time Packing of Islands

        现场测试:http://sqlfiddle.com/#!18/462ac/11

        with gap_detector as
        (
            select 
                DateOpen, DateClosed,                 
                case when 
                    lag(DateClosed) over (order by DateOpen) is null 
                    or lag(DateClosed) over (order by DateOpen) < DateOpen 
                then 
                    1
                else 
                    0 
                end as gap
            from dt              
        )
        , downtime_grouper as
        (
            select 
                DateOpen, DateClosed, 
                sum(gap) over (order by DateOpen) as downtime_group
            from gap_detector
        )
         -- group's open and closed detector. then computes the group's downtime
        select 
            downtime_group,
            min(DateOpen) as group_date_open, 
            max(DateClosed) as group_date_closed,
            datediff(minute, min(DateOpen), max(DateClosed)) as group_downtime,
        
            sum(datediff(minute, min(DateOpen), max(DateClosed))) 
                over(order by downtime_group) as downtime_running_total
        from downtime_grouper
        group by downtime_group
        

        输出:

        工作原理

        如果 DateOpen 之前没有停机时间,则它是一系列停机时间的开始(由 null lag(DateClosed) 指示)。如果 DateOpen 与上一个停机时间的 DateClosed 有间隔,它也是一系列停机时间的开始。

        with gap_detector as
        (
            select 
                lag(DateClosed) over (order by DateOpen) as previous_downtime_date_closed,
                DateOpen, DateClosed,                 
                case when 
                    lag(DateClosed) over (order by DateOpen) is null 
                    or lag(DateClosed) over (order by DateOpen) < DateOpen 
                then 
                    1
                else 
                    0 
                end as gap
            from dt              
        )
        select *
        from gap_detector
        order by DateOpen;
        

        输出:

        在检测到间隔开始后,我们会计算间隔的总和,这样我们就可以对彼此相邻的停机时间进行分组。

        with gap_detector as
        (
            select 
                DateOpen, DateClosed,                 
                case when 
                    lag(DateClosed) over (order by DateOpen) is null 
                    or lag(DateClosed) over (order by DateOpen) < DateOpen 
                then 
                    1
                else 
                    0 
                end as gap
            from dt              
        )
        select 
           DateOpen, DateClosed, gap, 
           sum(gap) over (order by DateOpen) as downtime_group
        from gap_detector
        order by DateOpen;
        

        从上面的输出中可以看出,我们现在可以通过在 downtime_group 上分组应用 MIN(DateOpen)MAX(DateClosed),轻松检测停机组的最早 DateOpen 和最新 DateClosed。在 downtime_group 1 上,我们有 08:00 的最早 DateOpen 和 08:45 的最新 DateClosed。在 downtime_group 2 上,我们有 09:06 的最早 DateOpen 和 9:41 的最新 DateClosed。即使同时出现停机时间,我们也可以据此重新计算正确的停机时间。


        我们可以通过反转逻辑来消除对 null 先前停机时间的检测(我们正在评估的当前行是表中的第一行),从而使代码更短。我们不是检测间隙,而是检测孤岛(连续停机时间)。如果上一个停机时间的 DateClosed 与当前停机时间的 DateOpen 重叠,则某些东西是连续的,用 0 表示。如果不重叠,则它是一个间隙,用 1 表示。

        这是查询:

        现场测试:http://sqlfiddle.com/#!18/462ac/12

        with gap_detector as
        (
            select 
                DateOpen, DateClosed,                 
                case when lag(DateClosed) over (order by DateOpen) >= DateOpen 
                then 
                    0
                else 
                    1 
                end as gap
            from dt              
        )
        , downtime_grouper as
        (
            select 
                DateOpen, DateClosed, 
                sum(gap) over (order by DateOpen) as downtime_group
            from gap_detector
        )
         -- group's open and closed detector. then computes the group's downtime
        select 
            downtime_group,
            min(DateOpen) as group_date_open, 
            max(DateClosed) as group_date_closed,
            datediff(minute, min(DateOpen), max(DateClosed)) as group_downtime,
        
            sum(datediff(minute, min(DateOpen), max(DateClosed))) 
                over(order by downtime_group) as downtime_running_total
        from downtime_grouper
        group by downtime_group
        

        如果您使用的是 SQL Server 2012 或更高版本:

        iif(lag(DateClosed) over (order by DateOpen) >= DateOpen, 0, 1) as gap
        

        【讨论】:

          猜你喜欢
          • 2020-05-18
          • 1970-01-01
          • 2023-03-10
          • 1970-01-01
          • 1970-01-01
          • 2014-02-08
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多