计算实际停机时间,忽略日期/时间的重叠

Posted

技术标签:

【中文标题】计算实际停机时间,忽略日期/时间的重叠【英文标题】:Calculate Actual Downtime ignoring overlap in dates/times 【发布时间】:2014-12-22 22:56:26 【问题描述】:

我正在尝试根据存储在表中的数据计算各种应用程序的实际停机时间。

目前我只是在计算 DowntimeStart 和 DowntimeEnd 之间的差异,它显示在 DowntimeMinutes 中。

问题在于,如果在个别组件出现故障时出现交叉,则应计算总数而忽略重叠。

我的预期显示在预期列中。

关于如何将查询组合在一起以实现此目的的任何想法?

Application         DowntimeStart           DowntimeEnd              DowntimeMinutes    Expected
Application Demo    2014-11-20 17:31:01.467 2014-11-20 18:01:01.243  30                 30
Application Demo    2014-11-28 17:59:00.987 2014-11-28 18:09:02.167  10                 26
Application Demo    2014-11-28 18:00:01.403 2014-11-28 18:25:01.443  25                 0
Application Demo    2014-11-29 19:13:08.580 2014-11-30 05:30:01.763  617                617
Application Demo    2014-11-30 01:55:01.953 2014-11-30 03:54:01.730  119                0

我已经查看并调查了这些选项,但它们没有实现上述目标:

Find Total Minutes Ignoring Overlap (Convert Cursor based Answer to CTE)

SQL to find time elapsed from multiple overlapping intervals

http://www.experts-exchange.com/Database/MS-SQL-Server/SQL_Server_2008/Q_28169653.html

http://thehobt.blogspot.com.au/2009/04/calculating-elapsed-time-based-upon.html

https://forums.teradata.com/forum/database/duration-calculation-between-dates-which-has-overlap-with-other-date-ranges-for-same

【问题讨论】:

【参考方案1】:

更新了新的测试用例

这是一种计算唯一中断然后将它们与导致中断的初始停机时间对齐的技术,以便实际值和预期值匹配。

DECLARE @Downtime TABLE (
    ID INT PRIMARY KEY NOT NULL IDENTITY(1,1),
    Application VARCHAR(25), 
    DowntimeStart DATETIME,
    DowntimeEnd DATETIME,
    Expected INT
)

INSERT @Downtime (Application, DowntimeStart, DowntimeEnd, Expected) VALUES -- Act/Exp
    ('Application Demo', '2014-11-20 17:31:01.467', '2014-11-20 18:01:01.243', 30) -- 30/30
    ,('Application Demo', '2014-11-28 17:59:00.987', '2014-11-28 18:09:02.167', 26) -- 10/26
    ,('Application Demo', '2014-11-28 18:00:01.403', '2014-11-28 18:25:01.443', 0) -- 25/0
    ,('Application Demo', '2014-11-29 19:13:08.580', '2014-11-30 05:30:01.763', 617) -- 617/617
    ,('Application Demo', '2014-11-30 01:55:01.953', '2014-11-30 03:54:01.730', 0)
    ,('Application Demo 2', '2014-12-19 23:09:01.303', '2014-12-22 09:43:01.397', 3514)
    ,('Application Demo 2', '2014-12-19 23:09:01.303', '2014-12-22 09:43:01.397', 0)
    ,('Application Demo 2', '2014-12-19 23:09:01.303', '2014-12-22 09:43:01.397', 0) 
    ,('Application Demo 2', '2014-12-19 23:09:01.303', '2014-12-22 09:43:01.397', 0)
    ,('Application Demo 2', '2014-12-19 23:09:01.303', '2014-12-22 09:43:01.397', 0) 

SELECT
    Downtimes.Application,
    Downtimes.DowntimeStart,
    Downtimes.DowntimeEnd,
    Downtimes.Expected,
    COALESCE(Actual, 0) AS Actual
FROM @Downtime Downtimes
    LEFT OUTER JOIN (
        SELECT DISTINCT
            D1.Application,
            MIN(CASE WHEN D1.DowntimeStart < D2.DowntimeStart THEN D1.ID ELSE D2.ID END) AS [ID],
            MIN(CASE WHEN D1.DowntimeStart < D2.DowntimeStart THEN D1.DowntimeStart ELSE D2.DowntimeStart END) AS [DowntimeStart],
            MAX(CASE WHEN D1.DowntimeEnd > D2.DowntimeEnd THEN D1.DowntimeEnd ELSE D2.DowntimeEnd END) AS [DowntimeEnd],
            DATEDIFF(MINUTE,
                MIN(CASE WHEN D1.DowntimeStart < D2.DowntimeStart THEN D1.DowntimeStart ELSE D2.DowntimeStart END),
                MAX(CASE WHEN D1.DowntimeEnd > D2.DowntimeEnd THEN D1.DowntimeEnd ELSE D2.DowntimeEnd END)) AS Actual
        FROM @Downtime D1
            INNER JOIN @Downtime D2
                ON D1.Application = D2.Application
                    AND (D1.DowntimeStart BETWEEN D2.DowntimeStart AND D2.DowntimeEnd
                        OR D2.DowntimeStart BETWEEN D1.DowntimeStart AND D1.DowntimeEnd)
        GROUP BY
            D1.Application,
            D1.DowntimeStart
    ) Outages
        ON Outages.ID = Downtimes.ID

这会产生所需的输出:

Application               DowntimeStart           DowntimeEnd             Expected    Actual
------------------------- ----------------------- ----------------------- ----------- -----------
Application Demo          2014-11-20 17:31:01.467 2014-11-20 18:01:01.243 30          30
Application Demo          2014-11-28 17:59:00.987 2014-11-28 18:09:02.167 26          26
Application Demo          2014-11-28 18:00:01.403 2014-11-28 18:25:01.443 0           0
Application Demo          2014-11-29 19:13:08.580 2014-11-30 05:30:01.763 617         617
Application Demo          2014-11-30 01:55:01.953 2014-11-30 03:54:01.730 0           0
Application Demo 2        2014-12-19 23:09:01.303 2014-12-22 09:43:01.397 3514        3514
Application Demo 2        2014-12-19 23:09:01.303 2014-12-22 09:43:01.397 0           0
Application Demo 2        2014-12-19 23:09:01.303 2014-12-22 09:43:01.397 0           0
Application Demo 2        2014-12-19 23:09:01.303 2014-12-22 09:43:01.397 0           0
Application Demo 2        2014-12-19 23:09:01.303 2014-12-22 09:43:01.397 0           0

【讨论】:

这里的小数据验证问题:最后一行的开始时间介于上一行的开始和结束时间之间,因此应该影响两个持续时间。 非常感谢 Jason,有一个问题在我测试所有数据之前我没有考虑过。如果您有以下内容,请参阅下一条消息,它会为所有人返回相同的值。无论如何修改查询以考虑到这一点? INSERT #TABLENAME (Application, DowntimeStart, DowntimeEnd, Expected) 值 - ('Application Demo 2', '2014-12-19 23:09:01.303', '2014-12-22 09: 43:01.397', 3514) ,('应用演示 2', '2014-12-19 23:09:01.303', '2014-12-22 09:43:01.397', 0) ,('应用演示 2' , '2014-12-19 23:09:01.303', '2014-12-22 09:43:01.397', 0) ,('应用演示 2', '2014-12-19 23:09:01.303', '2014-12-22 09:43:01.397', 0) - ,('应用演示 2', '2014-12-19 23:09:01.303', '2014-12-22 09:43:01.397', 0) 您的表是否有可用于更改的主键? 是的。直接命名为“ID”并且是 INT 类型。【参考方案2】:

我遇到了类似的问题,并在我的问题How to Consolidate Blocks of Time?中得到了回答

在您的情况下,这是通过 top 1 self external apply 来获得重叠,然后使用重叠的开始时间,或者如果为空,则使用正常记录的结束时间作为结束时间。

CREATE TABLE Downtime (
    Application VARCHAR(25), 
    DowntimeStart DATETIME,
    DowntimeEnd DATETIME,
    Expected INT
)

INSERT Downtime (Application, DowntimeStart, DowntimeEnd, Expected) VALUES -- Act/Exp
    ('Application Demo', '2014-11-20 17:31:01.467', '2014-11-20 18:01:01.243', 30) -- 30/30
    ,('Application Demo', '2014-11-28 17:59:00.987', '2014-11-28 18:09:02.167', 26) -- 10/26
    ,('Application Demo', '2014-11-28 18:00:01.403', '2014-11-28 18:25:01.443', 0) -- 25/0
    ,('Application Demo', '2014-11-29 19:13:08.580', '2014-11-30 05:30:01.763', 617) -- 617/617
    ,('Application Demo', '2014-11-30 01:55:01.953', '2014-11-30 03:54:01.730', 0)


SELECT
    Records.Application, Records.DowntimeStart, Records.DowntimeEnd, Records.Expected
  , DATEDIFF(minute, Records.DowntimeStart, COALESCE(Overlap.DowntimeStart, Records.DowntimeEnd)) AS Actual
--  , Overlap.Application, Overlap.DowntimeStart, Overlap.DowntimeEnd -- For Verification Purposes
FROM Downtime Records
  OUTER APPLY (
    SELECT TOP 1 Overlap.Application, Overlap.DowntimeStart, Overlap.DowntimeEnd
    FROM Downtime Overlap
    WHERE Records.Application = Overlap.Application
      AND Overlap.DowntimeStart > Records.DowntimeStart
      AND Overlap.DowntimeStart BETWEEN Records.DowntimeStart AND Records.DowntimeEnd
    ORDER BY Overlap.DowntimeStart
    ) Overlap

这是SQLFiddle 的解决方案。

【讨论】:

非常感谢 Jaaz,有一个问题在我测试所有数据之前我没有考虑过。如果您有以下内容,请参阅下一条消息,它会为所有人返回相同的值。无论如何修改查询以考虑到这一点? INSERT #TABLENAME (Application, DowntimeStart, DowntimeEnd, Expected) 值 - ('Application Demo 2', '2014-12-19 23:09:01.303', '2014-12-22 09: 43:01.397', 3514) ,('应用演示 2', '2014-12-19 23:09:01.303', '2014-12-22 09:43:01.397', 0) ,('应用演示 2' , '2014-12-19 23:09:01.303', '2014-12-22 09:43:01.397', 0) ,('应用演示 2', '2014-12-19 23:09:01.303', '2014-12-22 09:43:01.397', 0) - ,('应用演示 2', '2014-12-19 23:09:01.303', '2014-12-22 09:43:01.397', 0)

以上是关于计算实际停机时间,忽略日期/时间的重叠的主要内容,如果未能解决你的问题,请参考以下文章

在 SQL 中检测和合并日期范围的连续重叠

如何计算 BigQuery 中日期间隔联合的重叠

Java日期时间API系列37-----时间段是否有重叠(交集)的计算方法

计算多个日期范围内有多少个重叠日期

BigQuery 计算两个日期范围重叠

设计不重叠的日期时间事件