SQL计算总停机时间(分钟)

时间:2019-04-03 21:20:30

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

我正在使用一个停机时间管理系统,该系统能够在数据库中保存有关问题的支持单,我的数据库包含以下列:

-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

4 个答案:

答案 0 :(得分: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没有生成序列函数,我这样做是为了显示基本思想,可以很容易地将其转换为与正在使用的任何数据库一起工作。

答案 1 :(得分:0)

@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。

答案 2 :(得分:0)

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

即使date参数不是仅一天(1,440分钟),以下解决方案也将起作用。假设date参数是一个月,甚至是一年,此解决方案仍然可以使用。

实时演示: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 |

查看其工作原理:

enter image description here

它通过计算先前的停机时间来停机。如果当前日期的状态为打开并且前一个日期的状态为关闭,则不要计算停机时间,这意味着当前停机时间是不重叠的。非重叠停机时间用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


对于status desc上的order by d, 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分钟。

enter image description here

如果我们在日期相似的日期(例如8:00)之前首先对DateClosed排序(通过使用status desc,然后在DateOpen之前对其进行排序,则将如何安排和计算日期)。总停机时间为105分钟,这是正确的。

enter image description here

答案 3 :(得分:0)

另一种方法,使用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

输出:

enter image description here

工作方式

如果DateOpen没有以前的停机时间(由空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;

输出:

enter image description here

在检测到间隙启动器之后,我们对间隙进行了总计运行,因此我们可以将彼此相邻的停机时间进行分组。

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;

enter image description here

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


我们可以通过反转逻辑来消除对先前空停机时间(检测到的当前行是表中的第一行)的检测,从而缩短代码长度。我们没有检测间隙,而是检测孤岛(连续的停机时间)。如果先前的停机时间的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