在 MySQL 中查找时间序列数据间隙的方法?

Posted

技术标签:

【中文标题】在 MySQL 中查找时间序列数据间隙的方法?【英文标题】:Method of finding gaps in time series data in MySQL? 【发布时间】:2012-06-18 15:03:17 【问题描述】:

假设我们有一个包含两列的数据库表,entry_time 和 value。 entry_time 是时间戳,而 value 可以是任何其他数据类型。记录是相对一致的,以大约 x 分钟的间隔输入。然而,在许多 x 的时间内,可能不会输入条目,从而在数据中产生“间隙”。

就效率而言,用查询找到至少时间为 Y(新旧)的这些差距的最佳方法是什么?

【问题讨论】:

您如何定义差距?您对输入之间可能经过的时间有硬性限制吗? 一个变量 Y。忘记指定了。 【参考方案1】:

首先,让我们按小时汇总表中的条目数。

SELECT CAST(DATE_FORMAT(entry_time,'%Y-%m-%d %k:00:00') AS DATETIME) hour,
       COUNT(*) samplecount
  FROM table
 GROUP BY CAST(DATE_FORMAT(entry_time,'%Y-%m-%d %k:00:00') AS DATETIME)

现在,如果您每 6 分钟记录一次(每小时 10 次),那么所有 samplecount 值都应该是 10。这个表达式:CAST(DATE_FORMAT(entry_time,'%Y-%m-%d %k:00:00') AS DATETIME) 看起来很麻烦,但它只是通过将分钟和秒归零来将您的时间戳截断到它们发生的小时。

这相当有效,可以帮助您入门。如果您可以在 entry_time 列上放置一个索引并将您的查询限制为(例如,这里显示的昨天的示例),那将非常有效。

SELECT CAST(DATE_FORMAT(entry_time,'%Y-%m-%d %k:00:00') AS DATETIME) hour,
       COUNT(*) samplecount
  FROM table
 WHERE entry_time >= CURRENT_DATE - INTERVAL 1 DAY
   AND entry_time < CURRENT_DATE
 GROUP BY CAST(DATE_FORMAT(entry_time,'%Y-%m-%d %k:00:00') AS DATETIME)

但它不太擅长检测丢失样本的整个小时数。它对采样中的抖动也有点敏感。也就是说,如果您的最高时间样本有时会提前半秒 (10:59:30) 有时会延迟半秒 (11:00:30),那么您的每小时汇总计数将会关闭。所以,这个小时总结的东西(或一天总结,或分钟总结等)不是万无一失的。

您需要一个自联接查询才能得到完全正确的信息;它有点像毛球,效率不高。

让我们首先创建一个像这样带有编号样本的虚拟表(子查询)。 (这对 mysql 来说是个痛点;其他一些昂贵的 DBMS 让它变得更容易。没关系。)

  SELECT @sample:=@sample+1 AS entry_num, c.entry_time, c.value
    FROM (
        SELECT entry_time, value
      FROM table
         ORDER BY entry_time
    ) C,
    (SELECT @sample:=0) s

这个小虚拟表给出了entry_num、entry_time、value。

下一步,我们将其加入自身。

SELECT one.entry_num, one.entry_time, one.value, 
       TIMEDIFF(two.value, one.value) interval
  FROM (
     /* virtual table */
  ) ONE
  JOIN (
     /* same virtual table */
  ) TWO ON (TWO.entry_num - 1 = ONE.entry_num)

这会将接下来的两个表对齐,彼此偏移一个条目,由 JOIN 的 ON 子句控制。

最后,我们从该表中选择interval 大于您的阈值的值,并且样本的时间正好在缺失的时间之前。

整个自连接查询是这样的。我告诉过你这是一个毛球。

SELECT one.entry_num, one.entry_time, one.value, 
       TIMEDIFF(two.value, one.value) interval
  FROM (
    SELECT @sample:=@sample+1 AS entry_num, c.entry_time, c.value
      FROM (
          SELECT entry_time, value
            FROM table
           ORDER BY entry_time
      ) C,
      (SELECT @sample:=0) s
  ) ONE
  JOIN (
    SELECT @sample2:=@sample2+1 AS entry_num, c.entry_time, c.value
      FROM (
          SELECT entry_time, value
            FROM table
           ORDER BY entry_time
      ) C,
      (SELECT @sample2:=0) s
  ) TWO ON (TWO.entry_num - 1 = ONE.entry_num)

如果您必须在生产环境中对大型表执行此操作,您可能希望针对数据子集执行此操作。例如,您可以每天对前两天的样本执行此操作。这将非常有效,并且还可以确保您在午夜时不会忽略任何丢失的样本。为此,您的小行编号虚拟表应如下所示。

  SELECT @sample:=@sample+1 AS entry_num, c.entry_time, c.value
    FROM (
        SELECT entry_time, value
      FROM table
         ORDER BY entry_time
         WHERE entry_time >= CURRENT_DATE - INTERVAL 2 DAY
           AND entry_time < CURRENT_DATE /*yesterday but not today*/
    ) C,
    (SELECT @sample:=0) s

【讨论】:

非常感谢您提供此解决方案,尽管我对 @sample:=@sample+1 究竟做了什么感到困惑 这个@sample 变量跟踪行号。请注意,它在(SELECT @sample:=0) 中初始化,并为表的每一行递增。如果你有数万美元要为 Oracle 支付,你可以说 ROWNUM,但这是 MySQL 黑客做同样的事情。奥术,嗯?【参考方案2】:

一个非常有效的方法是使用游标的存储过程。我认为这比其他答案更简单、更有效。

此过程创建一个游标并遍历您正在检查的日期时间记录。如果有超过您指定的间隙,它会将间隙的开始和结束写入表格。

    CREATE PROCEDURE findgaps()
    BEGIN    
    DECLARE done INT DEFAULT FALSE;
    DECLARE a,b DATETIME;
    DECLARE cur CURSOR FOR SELECT dateTimeCol FROM targetTable
                           ORDER BY dateTimeCol ASC;
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;     
    OPEN cur;       
    FETCH cur INTO a;       
    read_loop: LOOP
        SET b = a;
        FETCH cur INTO a;   
        IF done THEN
            LEAVE read_loop;
        END IF;     
        IF DATEDIFF(a,b) > [range you specify] THEN
            INSERT INTO tmp_table (gap_begin, gap_end)
            VALUES (a,b);
        END IF;
    END LOOP;           
    CLOSE cur;      
    END;

在这种情况下,假定存在“tmp_table”。您可以在过程中轻松地将其定义为 TEMPORARY 表,但我在此示例中省略了它。

【讨论】:

【参考方案3】:

我正在 MariaDB 10.3.27 上尝试此操作,因此此过程可能无法正常工作,但我在创建过程时遇到错误,我不知道为什么!我有一个名为electric_use 的表,其中包含一个字段Intervaldatetime DATETIME,我想在其中查找空白。我创建了一个目标表electric_use_gaps,字段为gap_begin datetimegap_end datetime

数据每小时采集一次,我想知道我是否在 5 年内遗漏了一个小时的数据。

 DELIMITER $$  
  CREATE PROCEDURE findgaps()
    BEGIN    
    DECLARE done INT DEFAULT FALSE;
    DECLARE a,b DATETIME;
    DECLARE cur CURSOR FOR SELECT Intervaldatetime FROM electric_use
                           ORDER BY Intervaldatetime ASC;
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;     
    OPEN cur;       
    FETCH cur INTO a;       
    read_loop: LOOP
        SET b = a;
        FETCH cur INTO a;   
        IF done THEN
            LEAVE read_loop;
        END IF;     
        IF TIMESTAMPDIFF(MINUTE,a,b) > [60] THEN
            INSERT INTO electric_use_gaps(gap_begin, gap_end)
            VALUES (a,b);
        END IF;
    END LOOP;           
    CLOSE cur;      
    END&&
    
    DELIMITER ;

这是错误:

Query: CREATE PROCEDURE findgaps() BEGIN DECLARE done INT DEFAULT FALSE; DECLARE a,b DATETIME; DECLARE cur CURSOR FOR SELECT Intervalda...

Error Code: 1064
You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near '[60] THEN
            INSERT INTO electric_use_gaps(gap_begin, gap_end)
   ...' at line 16

【讨论】:

以上是关于在 MySQL 中查找时间序列数据间隙的方法?的主要内容,如果未能解决你的问题,请参考以下文章

Pandas 时间序列:查找会话中的间隙,并使用单独的 ID 命名每个会话/间隙

使用 Java 持久性查询语言按顺序查找间隙

检测数字序列中的间隙是随机的还是连续的

了解下Mysql的间隙锁及产生的原因

了解下Mysql的间隙锁及产生的原因

查找导致事件的行并将它们视为一个序列