9

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

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

4

3 回答 3

20

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

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
于 2012-06-18T15:27:19.120 回答
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 表,但我在此示例中省略了它。

于 2013-07-18T16:53:36.367 回答
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
于 2021-05-15T00:14:05.387 回答