【问题标题】:Calculation of time a record spent in particular status per day计算记录每天在特定状态中花费的时间
【发布时间】:2016-02-10 23:41:33
【问题描述】:

有人可以帮我完成下一个任务吗?

这里有一个问题:我们有一个历史记录表(进程的状态变化),我们需要计算一个进程处于特定状态的时间(以每天小时为单位)。这是历史表的结构:

ID| ProcessId| CreatedDate         | Status 
------------------------------------------- 
1 | Process1 | 2016-01-09 06:30:00 | UP
2 | Process1 | 2016-01-09 12:30:00 | UP
3 | Process1 | 2016-01-09 18:30:00 | DOWN
4 | Process1 | 2016-01-10 00:30:00 | UP 
5 | Process2 | 2016-01-08 18:30:00 | UP
6 | Process2 | 2016-01-09 00:30:00 | DOWN
7 | Process2 | 2016-01-09 06:30:00 | DOWN
8 | Process2 | 2016-01-09 12:30:00 | DOWN
9 | Process2 | 2016-01-09 18:30:00 | DOWN
10| Process2 | 2016-01-10 00:30:00 | UP
11| Process2 | 2016-01-10 06:30:00 | UP
12| Process2 | 2016-01-10 12:30:00 | DOWN
13| Process2 | 2016-01-10 18:30:00 | DOWN
14| Process2 | 2016-01-11 00:30:00 | DOWN
15| Process2 | 2016-01-11 06:30:00 | DOWN

因此,我们需要创建一个视图/表,例如:

ProcessId | Status | Date        | TimeSpentInStatusInDays
----------------------------------------------------------
Process1  | UP     | 2016-01-09  | 12h 00m
Process1  | DOWN   | 2016-01-09  | 05h 30m
Process1  | UP     | 2016-01-10  | 00h 00m
Process1  | DOWN   | 2016-01-10  | 00h 30m
Process2  | UP     | 2016-01-08  | 05h 30m
Process2  | DOWN   | 2016-01-08  | 00h 00m
Process2  | UP     | 2016-01-09  | 24h 00m
Process2  | DOWN   | 2016-01-09  | 00h 00m
Process2  | UP     | 2016-01-10  | 12h 00m
Process2  | DOWN   | 2016-01-10  | 12h 00m
Process2  | UP     | 2016-01-11  | 00h 00m
Process2  | DOWN   | 2016-01-11  | 06h 30m

值是例如(它们不连接到实际数据集)。

代码需要在 mySQL 中。任何帮助将不胜感激。谢谢。

【问题讨论】:

  • 所以一个进程在同一天“down”之后就再也不能“up”了?
  • 又可以起来了。在当前示例中,每 6 小时检查一次进程的状态,此时状态可以反映为已更改。我们基本上需要计算当天进程的上下时间。
  • 我认为样本数据集不能充分代表问题的这一方面。另外,虽然我可以看到如何将一个事物的周期与另一事物相加,但我无法理解对不相加的周期求和的逻辑!?!
  • 这里的要求是每天显示哪些流程处于上升和下降状态以及持续时间。虽然我能够获得状态变化之间的持续时间,但我每天都面临着打破它的挑战。理想情况下,某一时刻的数据将开始提供更真实的输入,例如每 15-30 分钟并提供流程的持续状态,逻辑将开始变得更有意义。问题是状态不会跨日期更改或在更长的时间内保持不变。假设一个流程永远不会停止,那么每天找到持续时间就成了挑战。
  • 到目前为止,这是我的代码:

标签: mysql sql


【解决方案1】:

我喜欢你的问题,因为它给了我一个使用 SQL 的理由,而我有一段时间没有机会这样做了。

这是我对你的问题的看法。

首先,我们准备一个临时表TempStatusLog,在其中我们每天在 00:00:01 添加一条状态等于当天最早读数的记录,并在 23:59:59 添加一条记录当天的最新读物。我们还使用变量@rownumvar 对所有行进行编号。假设原始表名为StatusLog,则使用此SELECT 语句创建临时表:

SELECT @rownumvar := @rownumvar + 1 AS `rowNo`,
       `t`.`ProcessId`, `t`.`CreatedDate`, `t`.`Status`
FROM (SELECT `ProcessId`, `CreatedDate`, `Status`
      FROM   `StatusLog`

      UNION

      SELECT `ProcessId`,
             STR_TO_DATE(CONCAT(`OnDate`, ' 23:59:59'),
                         '%Y-%m-%d %H:%i:%s') AS `CreatedDate`,
            (SELECT `Status`
             FROM   `StatusLog` AS `l`
             WHERE  `l`.`ProcessId` = `t1`.`ProcessId` AND
                    `l`.`CreatedDate`
                      = STR_TO_DATE(CONCAT(`t1`.`OnDate`, ' ', `t1`.`LastStatus`),
                                    '%Y-%m-%d %H:%i:%s')) AS `Status`
      FROM (SELECT `ProcessId`,
                   DATE_FORMAT(`CreatedDate`, '%Y-%m-%d') AS `OnDate`,
                   DATE_FORMAT(MAX(TIME(`CreatedDate`)), '%H:%i:%s') AS `LastStatus`
            FROM   `StatusLog`
            GROUP BY DATE(`OnDate`), `ProcessId`
            ORDER BY `ProcessId`, DATE(`OnDate`)) AS `t1`

      UNION

      SELECT `ProcessId`,
             STR_TO_DATE(CONCAT(`OnDate`, ' 00:00:01'),
                         '%Y-%m-%d %H:%i:%s') AS `CreatedDate`,
            (SELECT `Status`
             FROM   `StatusLog` AS `l`
             WHERE  `l`.`ProcessId` = `t2`.`ProcessId` AND
                    `l`.`CreatedDate`
                      = STR_TO_DATE(CONCAT(`t2`.`OnDate`, ' ', `t2`.`FirstStatus`),
                                    '%Y-%m-%d %H:%i:%s')) AS `Status`
      FROM (SELECT `ProcessId`,
                   DATE_FORMAT(`CreatedDate`, '%Y-%m-%d') AS `OnDate`,
                   DATE_FORMAT(MIN(TIME(`CreatedDate`)), '%H:%i:%s') AS `FirstStatus`
            FROM   `StatusLog`
            GROUP BY DATE(`OnDate`), `ProcessId`
            ORDER BY `ProcessId`, DATE(`OnDate`)) AS `t2`) AS `t`,
     (SELECT @rownumvar := 0) AS `r`
ORDER BY `t`.`ProcessId`, `t`.`CreatedDate` ASC

现在计算每个进程每天处于每个状态的时间相对容易。我们选择一个两行的运行窗口(这是编号行起作用的地方)并计算每两个读数之间的时间差,然后将其相加:

SELECT `p`.`ProcessId`,
       DATE_FORMAT(`q`.`CreatedDate`, '%Y-%m-%d') AS `Day`,
       DATE_FORMAT(
         SEC_TO_TIME(
           SUM(
             TIME_TO_SEC(
               TIMEDIFF(TIME(`q`.`CreatedDate`),
                        TIME(`p`.`CreatedDate`))
             )
           )
         ),
         '%H:%i:%s'
       ) AS `Elapsed`,
       `p`.`Status`
FROM   `TempStatusLog` AS `p`,
       `TempStatusLog` AS `q`
WHERE  `q`.`rowNo` = `p`.`rowNo` + 1 AND
       DATE(`q`.`CreatedDate`) = DATE(`p`.`CreatedDate`)
GROUP BY `Day`, `Status`, `ProcessId`
ORDER BY `Day` ASC, `ProcessId` ASC, `Status` ASC

这个解决方案有两个小问题:

  1. 它每天损失 2 秒。即,如果一个进程一整天都在运行,它会说它在 23:59:58 结束。
  2. 如果该进程一整天都在运行,则不会有记录表明它在 00:00:00 内停止(反之亦然)

对我来说,这两个问题似乎都微不足道。

您可以在这里观看现场演示:http://www.sqlfiddle.com/#!9/0a79cc/1

请注意,SQLFiddle 不允许创建临时表,因此我为此创建了一个普通表。

PS:在 MySQL 中解决这个问题比在几乎任何其他 RDBMS 中要困难得多,因为 MySQL 不支持 SQL 的许多特性。一方面,它不支持 CTE,这是 ANSI SQL 规范的一部分。这迫使用户创建临时表或寻找其他类似的解决方法。许多 RDBMS(Oracle、SQL Server)也支持 ROW_NUMBER() 函数的一些变体,我不得不使用变量来解决这些问题。

【讨论】:

  • 感谢您的意见。让我试试这个,如果有效,我会回来。
  • 谢谢 - 这是一个很好的方法,真的很有帮助。我试图弄清楚是否有办法找回我们失去的那 2 秒。如果我有解决方案,会通知您。
  • @hselin 我认为,可以通过在第一个 SELECT 中将 '00:00:01' 更改为 '00:00:00' 来获得一秒。此外,您可能可以查看第二个SELECT 的结果并将00:00:01 添加到以:59 结尾的每条记录中。
  • @hselin 还有,如果我的回答对你有帮助,你可以选择它作为问题的答案。
【解决方案2】:

我不保证这是在 MySQL 中执行此操作的好方法,或者速度很快。

我会获取您的历史记录表,并在每天结束时在必要时附加行(每个进程的最后一天除外。)添加的行包含每个进程每天最后一行的状态。如果这样的行已经存在,这确实可能导致午夜的瞬时状态变化。 (我稍后尝试处理这种情况。)

由于 MySQL 没有超前/滞后功能,我将匹配上述两个相同副本的每一行,以按顺序查找下一个时间(这可能是为一天结束添加的逻辑状态行。)之后这只是分组的问题。

由于我对 MySQL 日期函数不太熟悉,所以我只使用了 time_to_sec,因为跨度永远不会超过一天。唯一的麻烦是必须对午夜进行特殊处理。我会让你处理将秒值转换为适当的输出格式。

http://sqlfiddle.com/#!9/b0f3279/44

select
    ProcessId,
    date(CreatedDate) as `Date`,
    Status,
    sum(
        case
            when time_to_sec(NextDate) = 0 then 86400
            else time_to_sec(NextDate)
        end - time_to_sec(CreatedDate)
    ) as TimeSpentSeconds
from
    (
    select
        h1.ProcessId, h1.CreatedDate, h1.Status,
        min(
            h2.CreatedDate
            --case
            --    when date(h2.CreatedDate) > date(h1.CreatedDate)
            --    then date_add(date(h1.CreatedDate), interval 1 day)
            --    else h2.CreatedDate
            --end 
        ) as NextDate
    from
        (
        select ProcessId, CreatedDate, Status from history
        union
        select
            ProcessId,
            date_add(date(CreatedDate), interval 1 day),
            substring(
                max(
                    concat(
                        date_format(CreatedDate, get_format(datetime, 'ISO')),
                        Status
                    )
                ), 20, 10) as LastStatus
        from history h0
        where date(CreatedDate) <
            (
                select max(date(CreatedDate)) from history hm
                where hm.ProcessId = h0.ProcessId
            )
        group by ProcessId, date(CreatedDate)
        ) h1
            inner join
        (
        select ProcessId, CreatedDate, Status from history
        union
        select
            ProcessId,
            date_add(date(CreatedDate), interval 1 day),
            substring(
                max(
                    concat(
                        date_format(CreatedDate, get_format(datetime, 'ISO')),
                        Status
                    )
                ), 20, 10) as LastStatus
        from history h0
        where date(CreatedDate) <
            (
                select max(date(CreatedDate)) from history hm
                where hm.ProcessId = h0.ProcessId
            )
        group by ProcessId, date(CreatedDate)
        ) h2
            on      h2.ProcessId   = h1.ProcessId
                and h1.CreatedDate < h2.CreatedDate
                and h2.CreatedDate <= date_add(date(h1.CreatedDate), interval 1 day)
    group by h1.ProcessId, h1.CreatedDate, h1.Status
    ) hx
group by ProcessId, date(CreatedDate), Status
order by ProcessId, `Date`, Status desc, TimeSpentSeconds

我相信第二个选项可以处理我上面提到的瞬时/重复状态场景。它已经有点复杂了,但这感觉更加混乱。我添加了一种序列号以促进抢七,并调整了时差表达式。最后,我包含了一个having 子句,以消除累积为零的行被吐出。参考小提琴示例数据中的ProcessX:

select
    ProcessId,
    date(CreatedDate) as `Date`,
    Status,
    sum(
        case
            when NextDate > CreatedDate and time_to_sec(NextDate) = 0 then 86400
            else time_to_sec(NextDate)
        end - time_to_sec(CreatedDate)
    ) as TimeSpentSeconds
from
    (
    select
        h1.ProcessId, h1.CreatedDate, h1.Status,
        min(
            h2.CreatedDate,
            --case
            --    when date(h2.CreatedDate) > date(h1.CreatedDate)
            --    then date_add(date(h1.CreatedDate), interval 1 day)
            --    else h2.CreatedDate
            --end 
        ) as NextDate
    from
        (
        select 1 as Sequence, ProcessId, CreatedDate, Status from history
        union all
        select
            0,
            ProcessId,
            date_add(date(CreatedDate), interval 1 day),
            substring(
                max(
                    concat(
                        date_format(CreatedDate, get_format(datetime, 'ISO')),
                        Status
                    )
                ), 20, 10) as LastStatus
        from history h0
        where date(CreatedDate) <
            (
                select max(date(CreatedDate)) from history hm
                where hm.ProcessId = h0.ProcessId
            )
        group by ProcessId, date(CreatedDate)
        ) h1
            inner join
        (
        select 1 as Sequence, ProcessId, CreatedDate, Status from history
        union all
        select
            0,
            ProcessId,
            date_add(date(CreatedDate), interval 1 day),
            substring(
                max(
                    concat(
                        date_format(CreatedDate, get_format(datetime, 'ISO')),
                        Status
                    )
                ), 20, 10) as LastStatus
        from history h0
        where date(CreatedDate) <
            (
                select max(date(CreatedDate)) from history hm
                where hm.ProcessId = h0.ProcessId
            )
        group by ProcessId, date(CreatedDate)
        ) h2
            on      h2.ProcessId   = h1.ProcessId
                and (
                        h1.CreatedDate <  h2.CreatedDate
                    and h2.CreatedDate <= date_add(date(h1.CreatedDate), interval 1 day)
                    or
                        h1.CreatedDate =  h2.CreatedDate
                    and h1.Sequence    <  h2.Sequence
                )   
    group by h1.ProcessId, h1.CreatedDate, h1.Status
    ) hx
group by ProcessId, date(CreatedDate), Status
having TimeSpentSeconds > 0 /* MySQL shortcut reference */
order by ProcessId, `Date`, Status desc, TimeSpentSeconds

http://sqlfiddle.com/#!9/b582b2/10

我刚刚意识到我对NextDate 的表达不需要检查午夜超限,所以我将其注释掉。虽然我没有改变小提琴。而且我还忘了提到,我假设每个流程每天至少有一份状态报告。也许这是使用其他 MySQL 选项的起点,例如临时表(用于速度)或变量(用于领先/滞后)。

【讨论】:

  • 感谢您的意见。让我试试这个,如果有效,我会回来。
【解决方案3】:

只是为了好玩。去 Postgres :)

select 
  ProcessId, CreatedDate, Status, 
  to_char( CreatedDate - lag( CreatedDate ) over ( order by CreatedDate, ProcessId ), 'HH24:MI' ) as diff
from history
order by ProcessId, ID;

http://sqlfiddle.com/#!15/83cb0/9

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多