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

时间:2012-06-18 15:03:17

标签: mysql database time

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

在效率方面,通过查询找到至少时间Y(新旧)的最佳方法是什么?

3 个答案:

答案 0 :(得分:18)

首先,让我们在表格中按小时汇总参赛作品数量。

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)

现在,如果您每隔六分钟(每小时十次)记录一次,那么所有samplecount值都应为十。这个表达式: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)

如果您必须在大型表格的生产中执行此操作,则可能需要为数据的子集执行此操作。例如,您可以每天对前两天的样本进行此操作。这将是相当有效的,并且还将确保您在午夜时不会忽略任何遗失的样本。要做到这一点,你的小小的rownumbered虚拟表将是这样的。

  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

答案 1 :(得分:1)

执行此操作的一种非常有效的方法是使用游标的存储过程。我认为这比其他答案更简单,更有效。

此过程创建一个游标并通过您正在检查的日期时间记录对其进行迭代。如果存在超过您指定的间隙,则会将间隙的开始和结束写入表格。

    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表,但我将其排除在此示例之外。

答案 2 :(得分:0)

我正在 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