Способ поиска пробелов в данных временных рядов в MySQL?

допустим, у нас есть таблица базы данных с двумя столбцами, entry_time и value. entry_time-метка времени, а value может быть любым другим типом данных. Записи относительно последовательны, вводятся примерно через X минутные интервалы. Однако в течение многих x времен запись может не быть сделана, что создает "пробел" в данных.

с точки зрения эффективности, каков наилучший способ найти эти пробелы по крайней мере времени Y (как новые, так и старые) с помощью запроса?

2 ответов


для начала давайте суммируем количество записей по часам в вашей таблице.

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), ваши почасовые сводные подсчеты будут отключены. Итак, эта часовая сводка (или дневная сводка, или минутная сводка и т. д.) не пуленепробиваемый.

вам нужен запрос self-join, чтобы получить материал совершенно правильно; это немного больше комок волос и не так эффективно.

давайте начнем с создания виртуальной таблицы (подзапроса), как это с пронумерованными образцами. (Это боль в MySQL; некоторые другие дорогие СУБД облегчают это. Неважно.)

  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, значение.

следующий шаг, мы присоединяемся к его себя.

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)

это выравнивает таблицы рядом друг с другом, смещенные одной записью, регулируемой предложением ON соединения.

наконец, мы выбираем значения из этой таблицы с помощью interval больше, чем ваш порог, и есть время образцов прямо перед отсутствующими.

над всем self join запрос это. Я же говорил, что это волосяной ком.

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

очень эффективный способ сделать это с помощью хранимой процедуры с помощью курсоров. Я думаю, что это проще и эффективнее, чем другие ответы.

эта процедура создает курсор и повторяет его через записи datetime, которые вы проверяете. Если когда-либо будет разрыв больше, чем вы укажете, он запишет начало и конец разрыва в таблицу.

    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 существует. Вы можете легко определить это как временное таблица в процедуре, но я оставил ее из этого примера.