【问题标题】:Select latest record of consecutive rows containing same value SQL选择包含相同值SQL的连续行的最新记录
【发布时间】:2020-07-19 20:38:14
【问题描述】:

我有一个程序每小时对一组机器运行一次 ping,并将该 ping 的结果(无论该计算机是否连接)存储在一个名为 machine_ping_data 的表中。

This is the structure of the table

我正在尝试获取status 成功“SUCC”的每台机器的最新连续记录。连续我的意思是他们可以被另一个 machine_id 的记录打断,但不能被同一 machine_id 的记录和不同的 status (UNK,FAIL)打断。 另外,当我说最新时,我的意思是对于machine_id,除了“SUCC”之外,没有更多最近的记录,status。我希望这很清楚。

This is an example of the data I have in the table

我目前的代码如下:

    SELECT machine_id, status, TIMESTAMPDIFF(hour, MIN(insert_date), MAX(insert_date)) AS durationFROM (
        SELECT
        *
        , @groupNumber := IF(@prev_machine_id != machine_id OR @prev_status != status, @groupNumber + 1, @groupNumber) AS gn
        , @prev_machine_id  := machine_id
        , @prev_status := status
        FROM machine_ping_data
        , (SELECT @groupNumber := 0, @prev_machine_id  := NULL, @prev_status := NULL) var_init_subquery
        ORDER BY machine_id, insert_date DESC
    ) sq
    WHERE status = "SUCC"
    GROUP BY gn, machine_id, status

它确实对连续的结果进行分组,但不仅仅是最新的结果,它会获取特定machine_id 的最新记录,而不管status 是否为“SUCC”。 我也尝试过使用联合,但没有得到任何结果。

任何帮助或建议将不胜感激!

【问题讨论】:

    标签: mysql group-by duplicates


    【解决方案1】:

    首先将您的问题归结为核心:选择最近连续成功的 ping。

    这可以通过仅使用包含三列的数据表来完成:(machine_id INT, insert_date INT, status ENUM("SUCC", "FAIL"))

    针对这个问题,我找到了以下解决方案:

    SELECT C.machine_id, C.insert_date
      FROM data AS C
      WHERE C.status = "SUCC"
      AND (
        C.insert_date > ANY ( -- C.insert_date is later than the latest failure which occurred before the latest success.
          SELECT max(A.insert_date) AS LATEST_FAILURE_BEFORE_LATEST_SUCCESS
              FROM data AS A JOIN data AS B
              ON (A.machine_id = B.machine_id AND A.status != "SUCC" AND B.status = "SUCC" AND A.insert_date < B.insert_date)
              GROUP BY A.machine_id HAVING A.machine_id = C.machine_id
          )
        OR NOT EXISTS ( -- corner case NO_PRECEDING_FAILURES
          SELECT 1 FROM data AS D 
            WHERE D.machine_id = C.machine_id
            AND D.status != "SUCC"
        )
      );
    
    

    我想复杂的部分是内部的SELECT,我在其中加入数据并选择LATEST_FAILURE_BEFORE_LATEST_SUCCESS。在这里,我利用成功 ping 的最新连续记录(通常)在它们之前有失败的事实。我选择最新的fa

    带有测试用例的完整脚本:

    DROP DATABASE example;
    CREATE DATABASE example;
    USE example;
    
    CREATE TABLE data (
        machine_id INT,
        insert_date INT,
        status ENUM("SUCC", "FAIL")
    );
    
    -- Test cases with different machines
    INSERT INTO data (machine_id, insert_date, status) VALUES
    -- 0. a machine which has sequences of FAIL - SUCC
    (0, 4, "FAIL"),
    (0, 3, "SUCC"), -- TARGET
    (0, 2, "SUCC"), -- TARGET
    (0, 1, "FAIL"),
    (0, 0, "FAIL"),
    -- 1. a machine which has sequences of FAIL - SUCC - FAIL
    (1, 4, "FAIL"),
    (1, 3, "SUCC"), -- TARGET
    (1, 2, "SUCC"), -- TARGET
    (1, 1, "FAIL"),
    (1, 0, "FAIL"),
    -- 2. a machine which has sequences of FAIL - SUCC - FAIL - SUCC
    (2, 7, "SUCC"), -- TARGET
    (2, 6, "SUCC"), -- TARGET
    (2, 5, "SUCC"), -- TARGET
    (2, 4, "FAIL"),
    (2, 3, "SUCC"),
    (2, 2, "SUCC"),
    (2, 1, "FAIL"),
    (2, 0, "FAIL"),
    -- 3. a machine which has sequences of FAIL - SUCC - FAIL - SUCC - FAIL
    (3, 9, "FAIL"),
    (3, 8, "FAIL"),
    (3, 7, "SUCC"), -- TARGET
    (3, 6, "SUCC"), -- TARGET
    (3, 5, "SUCC"), -- TARGET
    (3, 4, "FAIL"),
    (3, 3, "SUCC"),
    (3, 2, "SUCC"),
    (3, 1, "FAIL"),
    (3, 0, "FAIL"),
    -- 4. a machine which has sequences of SUCC - FAIL
    (4, 2, "FAIL"),
    (4, 1, "SUCC"), -- TARGET
    (4, 0, "SUCC"), -- TARGET
    -- 5. a machine which has sequences of SUCC - FAIL - SUCC
    (5, 5, "SUCC"), -- TARGET
    (5, 4, "SUCC"), -- TARGET
    (5, 3, "SUCC"), -- TARGET
    (5, 2, "FAIL"),
    (5, 1, "SUCC"),
    (5, 0, "SUCC"),
    -- 6. a machine which has sequences of SUCC - FAIL - SUCC - FAIL
    (6, 7, "FAIL"),
    (6, 6, "FAIL"),
    (6, 5, "SUCC"), -- TARGET
    (6, 4, "SUCC"), -- TARGET
    (6, 3, "SUCC"), -- TARGET
    (6, 2, "FAIL"),
    (6, 1, "SUCC"),
    (6, 0, "SUCC"),
    -- 7. a machine which has only FAIL
    (7, 2, "FAIL"),
    (7, 1, "FAIL"),
    (7, 0, "FAIL"),
    -- 8. a machine which has only SUCC
    (8, 2, "SUCC"), -- TARGET
    (8, 1, "SUCC"), -- TARGET
    (8, 0, "SUCC"); -- TARGET
    
    SELECT C.machine_id, C.insert_date
      FROM data AS C
      WHERE C.status = "SUCC"
      AND (
        C.insert_date > ANY ( -- C.insert_date is later than the latest failure which occurred before the latest success.
          SELECT max(A.insert_date) AS LATEST_FAILURE_BEFORE_LATEST_SUCCESS
              FROM data AS A JOIN data AS B
              ON (A.machine_id = B.machine_id AND A.status != "SUCC" AND B.status = "SUCC" AND A.insert_date < B.insert_date)
              GROUP BY A.machine_id HAVING A.machine_id = C.machine_id
          )
        OR NOT EXISTS ( -- corner case NO_PRECEDING_FAILURES
          SELECT 1 FROM data AS D 
            WHERE D.machine_id = C.machine_id
            AND D.status != "SUCC"
        )
      );
    
    -- Should return:
    -- machine_id   insert_date
    -- 0    3
    -- 0    2
    -- 1    3
    -- 1    2
    -- 2    7
    -- 2    6
    -- 2    5
    -- 3    7
    -- 3    6
    -- 3    5
    -- 5    5
    -- 5    4
    -- 5    3
    -- 6    5
    -- 6    4
    -- 6    3
    -- 8    2
    -- 8    1
    -- 8    0
    
    DROP DATABASE example;
    

    【讨论】:

    • 它不起作用,最终不得不重新启动系统,因为此查询使服务器崩溃:/
    • @Chewaka10 这个问题对你还有意义吗?如果是这样,我可以帮助您调试我提出的解决方案的问题。
    【解决方案2】:

    我认为鉴于您要实现的目标的复杂性,最好保持 sql 语句简单:

    选择insert_date, machine_id, status FROM machine_ping_data ORDER BY insert_date DESC

    然后使用 PHP/无论您调用什么 sql 查询来处理数据

    因此,假设 PHP 下面的这段代码可以让您在每台机器上获得每个成功 ping 的日期,直到最近一次不成功。

    $sql = "SELECT `insert_date`, `machine_id`, `status` FROM `machine_ping_data` ORDER BY `insert_date` DESC";
    $db = (however you establish connection to your sql database...)
    $res = $db->query($sql);
    $machines = array();
    while($r = $res->fetch_object())
    {
      if(!isset($machines[$r->machine_id]))
      {
        $x = new stdclass;
        $x->history = array();
        $machines[$r->machine_id] = $x; 
      }
    
      $x->history[] = $r;
      if($r->status!="SUCC")
      {
        continue;
      }
    }
    

    那么每台机器的“历史”数组中的第一个条目是你最近的成功,最后一个条目是你最近失败的那台机器。

    根据我的经验,最好让 sql 查询尽可能简单,然后再处理数据;这也减少了服务器的负载。

    【讨论】:

      猜你喜欢
      • 2019-01-28
      • 2021-07-20
      • 2017-06-11
      • 2021-10-30
      • 2015-02-03
      • 1970-01-01
      • 2014-09-10
      • 2013-05-29
      • 2016-07-15
      相关资源
      最近更新 更多