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