【问题标题】:Summing dates across multiple rows in SQL?在 SQL 中跨多行求和日期?
【发布时间】:2016-04-12 19:29:56
【问题描述】:

我们有一个表格,用于存储我们系统中某些设定点的警报。我正在尝试编写一个查询,该查询首先获取两个日期之间的差异(分布在两行中),然后将所有日期差异相加,以获得设定点处于警报状态的时间量的总和。

我们有一个数据库,我已经完成了类似的操作,但在这种情况下,startTime 和 endTime 都在同一行中。在这种情况下,这是不够的

一些示例数据

|   Row   |   TagID   |   SetPointID   |         EventLogTime         |   InAlarm   |
-------------------------------------------------------------------------------------
|    1    |     1     |        2       |    2016-01-01 01:49:18.070   |      1      |
|    2    |     1     |        1       |    2016-01-01 03:23:39.970   |      1      |
|    3    |     1     |        2       |    2016-01-01 03:23:40.070   |      0      |
|    4    |     1     |        1       |    2016-01-01 08:04:01.260   |      0      |
|    5    |     1     |        2       |    2016-01-01 08:04:01.370   |      1      |
|    6    |     1     |        1       |    2016-01-01 11:40:36.367   |      1      |
|    7    |     1     |        2       |    2016-01-01 11:40:36.503   |      0      |
|    8    |     1     |        1       |    2016-01-01 13:00:30.263   |      0      |

结果

|   TagID    |   SetPointID   |   TotalTimeInAlarm   |
------------------------------------------------------
|     1      |        1       |    6.004443  (hours) |
|     1      |        2       |    5.182499  (hours) |

本质上,我需要做的是获取每个标签和每个设定点的开始时间和结束时间,然后我需要获取警报的总时间。我觉得 CTE 可能会有所帮助,但我不确定。

我相信伪查询逻辑会类似于

Define @startTime DATETIME, @endTime DATETIME

SELECT TagID,
       SetPointID,
       ABS(First Occurrence of InAlarm = True (since last occurrence WHERE InAlarm = False) 
           - First Occurrence of InAlarm = False (since last occurrence WHERE InAlarm = True)) 
       -- IF no InAlarm = False use @endTime.
GROUP BY TagID, SetPointID

【问题讨论】:

  • 输出应该是什么样的?
  • 基本上,最后,它只是:TagID | SetPointID | TotalTimeInAlarm
  • 您能否详细说明第 1 点是如何得到 0.2501851 的?请写下公式,比如这个时间加上那个时间减去那个时间等等。
  • 下一个问题。你用的是什么版本的sql server?如果您在 2012+ 年,答案将非常重要
  • @Jdsfighter:这就是精神!现在,如果您以inAlarm = 0 开头会发生什么?如果inAlarm = 1 有两个或多个连续记录会发生什么?如果系列以inAlarm = 1 结尾会发生什么?

标签: sql sql-server tsql common-table-expression


【解决方案1】:

您可以使用LEAD 窗口函数(或LAG)很容易地做到这一点。这假定行总是与“InAlarm”的 1-0-1-0 成对出现。如果这没有发生,那么它将把事情扔掉。无论如何,您都需要针对这些情况制定业务规则。

;WITH CTE_Timespans AS
(
    SELECT
        TagID,
        SetPointID,
        InAlarm,
        EventLogTime,
        LEAD(EventLogTime, 1) OVER (PARTITION BY TagID, SetPointID ORDER BY EventLogTime) AS EndingEventLogTime
    FROM
        My_Table
)
SELECT
    TagID,
    SetPointID,
    SUM(DATEDIFF(SS, EventLogTime, EndingEventLogTime))/3600.0 AS TotalTime
FROM
    CTE_Timespans
WHERE
    InAlarm = 1
GROUP BY
    TagID,
    SetPointID

【讨论】:

  • 通过添加一个 set @endDate 变量并将 EndingEventLogTime 如果 null 合并到 @endDate 的值,这似乎可以正常工作。将InAlarm 放在 where 子句中的目的是什么?是否只是过滤任何仍不令人担忧的行?
  • 您只想总结那些以 1 开头的时间跨度。CTE 实际上会返回所有行(包括那些 InAlarm 值为 0 的行)以及下一行的时间。您要确保没有添加以 0 开头的时间跨度。
  • 顺便说一句,您还可以为InAlarm 值添加LEAD,并仅过滤以InAlarm 为开头和结尾值为0 的行。
【解决方案2】:

一种简单的方法是使用 OUTER APPLY 来获取不是 InAlarm 的下一个日期

SELECT  mt.TagID,
        mt.SetPointID,
        SUM(DATEDIFF(ss,mt.EventLogTime,oa.EventLogTime)) / 3600.0 AS [TotalTimeInAlarm]
FROM    MyTable mt
        OUTER APPLY (SELECT MIN([EventLogTime]) EventLogTime
                     FROM   MyTable mt2
                     WHERE  mt.TagID = mt2.TagID
                            AND mt.SetPointID = mt2.SetPointID
                            AND mt2.EventLogTime > mt.EventLogTime
                            AND InAlarm = 0
                    ) oa
WHERE   mt.InAlarm = 1
GROUP BY mt.TagID,
        mt.SetPointID

如果使用 MSSQL 2012+,LEAD() 的性能可能会更好

【讨论】:

    【解决方案3】:

    在 SQL Server 2014+ 中:

    SELECT  tagId, setPointId, SUM(DATEDIFF(second, pt, eventLogTime)) / 3600. AS diff
    FROM    (
            SELECT  *,
                    LAG(inAlarm) OVER (PARTITION BY tagId, setPointId ORDER BY eventLogTime, row) ppa,
                    LAG(eventLogTime) OVER (PARTITION BY tagId, setPointId ORDER BY eventLogTime, row) pt
            FROM    (
                    SELECT  LAG(inAlarm) OVER (PARTITION BY tagId, setPointId ORDER BY eventLogTime, row) pa,
                            *
                    FROM    mytable
                    ) q
            WHERE   EXISTS
                    (
                    SELECT  pa
                    EXCEPT
                    SELECT  inAlarm
                    )
            ) q
    WHERE   ppa = 0
            AND inAlarm = 1
    GROUP BY
            tagId, setPointId
    

    这将过滤掉具有相同警报状态的连续事件

    【讨论】:

      猜你喜欢
      • 2021-01-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-02-15
      • 2022-01-05
      • 2021-04-18
      • 1970-01-01
      • 2023-04-01
      相关资源
      最近更新 更多