Как найти пропущенные строки данных с помощью SQL? - PullRequest
4 голосов
/ 05 декабря 2009

Моя проблема :

Я получил базу данных MySQL, которая хранит большое количество метеорологических данных в хронологическом порядке (новые данные вставляются каждые 10 минут). К сожалению, было несколько отключений и, следовательно, некоторые строки отсутствуют. Недавно мне удалось получить некоторые резервные файлы с метеостанции, и теперь я хочу использовать их для заполнения отсутствующих данных.

Подобные структуры БД (пример):

date*            the data    
2/10/2009 10:00  ...
2/10/2009 10:10  ...
( Missing data!)
2/10/2009 10:40  ...
2/10/2009 10:50  ...
2/10/2009 11:00  ...
...

* = datatime -типа, первичный ключ

Моя идея :

Поскольку резервная копия и база данных расположены на разных компьютерах, а трафик довольно медленный, я подумал о создании MySQL-запроса, который при запуске вернет список всех пропущенных дат в указанном диапазоне время. Затем я могу извлечь эти даты из резервной копии и вставить их в базу данных.

Вопрос :

Как написать такой запрос? У меня нет разрешения на создание каких-либо вспомогательных таблиц. Можно ли сформулировать «виртуальную таблицу» всех требуемых дат в указанном интервале, а затем использовать ее в JOIN? Или есть совершенно разные предложения для решения моей проблемы?

Редактировать : Да, временные метки всегда имеют форму, показанную выше (всегда 10 минут), за исключением того, что некоторые просто отсутствуют.

Хорошо, а как насчет временных таблиц? Есть ли элегантный способ автоматического заполнения их временным диапазоном? Что, если два сценария пытаются работать одновременно, это вызывает проблемы с таблицей?

Ответы [ 7 ]

5 голосов
/ 05 декабря 2009
select t1.ts as hival, t2.ts as loval
from metdata t1, metdata t2
where t2.ts = (select max(ts) from metdata t3
where t3.ts < t1.ts)
and not timediff(t1.ts, t2.ts) = '00:10:00'

Этот запрос вернет куплеты, которые вы можете использовать для выбора отсутствующих данных. У пропущенных данных будет временная метка между hival и loval для каждого куплета, возвращаемого запросом.

EDIT - спасибо за проверку, Крейг

РЕДАКТИРОВАТЬ2:

получение пропущенных временных меток - этот SQL становится немного сложнее для чтения, поэтому я его немного разбью. Во-первых, нам нужен способ для вычисления серии значений меток времени между заданным низким значением и высоким значением с 10-минутными интервалами. Способ сделать это, когда вы не можете создавать таблицы, основан на следующем sql, который создает в качестве результирующего набора все цифры от 0 до 9.

select d1.* from 
(select 1 as digit
union select 2 
union select 3 
union select 4 
union select 5 
union select 6 
union select 7 
union select 8 
union select 9 
union select 0 
) as d1

... теперь, если пару раз объединить эту таблицу с ее собственной копией, это означает, что мы можем динамически генерировать список указанной длины

select curdate() + 
INTERVAL  (d1.digit * 100 + d2.digit * 10 + d3.digit) * 10 MINUTE 
as date 
from (select 1 as digit
union select 2 
union select 3 
union select 4 
union select 5 
union select 6 
union select 7 
union select 8 
union select 9 
union select 0 
) as d1
join
(select 1 as digit
union select 2 
union select 3 
union select 4 
union select 5 
union select 6 
union select 7 
union select 8 
union select 9 
union select 0 
) as d2
join
(select 1 as digit
union select 2 
union select 3 
union select 4 
union select 5 
union select 6 
union select 7 
union select 8 
union select 9 
union select 0 
) as d3
where (d1.digit * 100 + d2.digit * 10 + d3.digit) between 1 and 42
order by 1

... теперь этот кусок sql приближается к тому, что нам нужно. Имеет 2 входные переменные:

  1. начальная отметка времени (я использовал curdate () в примере); и
  2. количество итераций - где предложение определяет 42 итерации в Например, максимум с 3-значными таблицами составляет 1000 интервалов

... что означает, что мы можем использовать исходный sql, чтобы привести пример сверху, чтобы сгенерировать серию временных меток для каждой пары низших значений hival. Потерпи меня, этот sql немного длиннее ...

select daterange.loval + INTERVAL  (d1.digit * 100 + d2.digit * 10 + d3.digit) * 10 MINUTE as date 
from 
(select t1.ts as hival, t2.ts as loval
from metdata t1, metdata t2
where t2.ts = (select max(ts) from metdata t3
where t3.ts < t1.ts)
and not timediff(t1.ts, t2.ts) = '00:10:00'
) as daterange
join
(select 1 as digit
union select 2 
union select 3 
union select 4 
union select 5 
union select 6 
union select 7 
union select 8 
union select 9 
union select 0 
) as d1
join
(select 1 as digit
union select 2 
union select 3 
union select 4 
union select 5 
union select 6 
union select 7 
union select 8 
union select 9 
union select 0 
) as d2
join
(select 1 as digit
union select 2 
union select 3 
union select 4 
union select 5 
union select 6 
union select 7 
union select 8 
union select 9 
union select 0 
) as d3
where (d1.digit * 100 + d2.digit * 10 + d3.digit) between 1 and
 round((time_to_sec(timediff(hival, loval))-600) /600)
order by 1

... теперь есть немного эпического sql
ПРИМЕЧАНИЕ: использование таблицы цифр 3 раза дает максимальный разрыв, который она будет покрывать чуть более 6 дней

1 голос
/ 05 декабря 2009

Если вы можете создать временную таблицу, вы можете решить проблему с помощью JOIN

CREATE TEMPORARY TABLE DateRange
(theDate DATE);

Заполните таблицу всеми 10-минутными интервалами между датами, затем используйте следующее

SELECT theDate
FROM DateRange dr
LEFT JOIN Meteorological mm on mm.date = dr.theDate
WHERE mm.date IS NULL

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

Если вам нужно быстро найти дни с отсутствующими данными, вы можете использовать

select Date(mm.Date),144-count(*) as TotMissing 
from Meteorological mm 
group by Date(mm.Date) 
having count(*) < 144 

Предполагается, что 24 часа в сутки, 6 записей в час (следовательно, 144 строки). - Sparky 0 секунд назад

0 голосов
/ 06 декабря 2009

Это решение использует подзапросы, и нет необходимости в каких-либо явных временных таблицах. Я предположил, что ваши резервные данные находятся в другой базе данных на другом компьютере; в противном случае вам нужно будет выполнить только шаг 2 для нужного набора результатов и написать свою программу для соответствующего обновления основной базы данных.

Идея состоит в том, чтобы начать с создания «компактного» набора результатов, обобщающего список пробелов. То есть следующие данные:

MeasureDate
2009-12-06 13:00:00
2009-12-06 13:10:00
--missing data
2009-12-06 13:30:00
--missing data
2009-12-06 14:10:00
2009-12-06 14:20:00
2009-12-06 14:30:00
--missing data
2009-12-06 15:00:00

Будет преобразовано в следующее, где фактические промежутки строго между (т.е. исключают) конечными точками:

GapStart            GapEnd
2009-12-06 13:10:00 2009-12-06 13:30:00
2009-12-06 13:30:00 2009-12-06 14:10:00
2009-12-06 14:30:00 2009-12-06 15:00:00
2009-12-06 15:00:00 NULL

Запрос на решение построен следующим образом:

  1. Получить все MeasureDates, у которых нет записи, через 10 минут, так как это будет началом пропуска. ПРИМЕЧАНИЕ: последняя запись будет включена, хотя и не является пробелом; но это не будет иметь побочных эффектов.
  2. Увеличьте вышеупомянутое, добавив конец промежутка, используя первую MeasureDate после начала промежутка.
  3. ПРИМЕЧАНИЕ. Список промежутков компактен, и если у вас нет исключительно высокой распространенности фрагментированных пробелов , он не должен потреблять много пропускной способности при передаче этого набора результатов на машину резервного копирования.
  4. Используйте ВНУТРЕННЕЕ СОЕДИНЕНИЕ с неравенствами, чтобы определить любые недостающие данные, которые могут быть доступны в резервной копии. (Запустите тесты и проверки для проверки целостности данных резервного копирования.)
  5. Предполагая, что ваши резервные данные надежны и не приведут к аномальным необоснованным скачкам в ваших измерениях, ВСТАВЬТЕ данные в основную базу данных.

Следующий запрос должен быть протестирован (желательно, чтобы он работал на сервере резервного копирования из соображений производительности).

/* TiC Copyright
This query is writtend (sic) by me, and cannot be used without 
expressed (sic) written permission. (lol) */

/*Step 3*/
SELECT  gap.GapStart, gap.GapEnd,
        rem.MeasureDate, rem.Col1, ...
FROM    (
        /*Step 2*/
        SELECT  gs.GapStart, (
                SELECT  MIN(wd.MeasureDate)
                FROM    WeatherData wd
                WHERE   wd.MeasureDate > gs.GapStart
                ) AS GapEnd
        FROM    (
                /*Step 1*/
                SELECT  wd.MeasureDate AS GapStart
                FROM    WeatherData wd
                WHERE   NOT EXISTS (
                        SELECT  *
                        FROM    WeatherData nxt
                        WHERE   nxt.MeasureDate = DATEADD(mi, 10, wd.MeasureDate)
                        )
                ) gs
        ) gap
        INNER JOIN RemoteWeatherData rem ON
            rem.MeasureDate > gap.GapStart
        AND rem.MeasureDate < gap.GapEnd

Вставка ...

INSERT INTO WeatherData (MeasureDate, Col1, ...)
SELECT  /*gap.GapStart, gap.GapEnd,*/
        rem.MeasureDate, rem.Col1, ...
...
0 голосов
/ 05 декабря 2009

Примечание: используется синтаксис MSSQL. Я думаю, что MySQL использует DATE_ADD (T1.date, INTERVAL 10 MINUTE) вместо DATEADD, но я не проверял это.

Вы можете получить недостающие метки времени с помощью двух самостоятельных соединений:

SELECT T1.[date] AS DateFrom, MIN(T3.[date]) AS DateTo
    FROM [test].[dbo].[WeatherData] T1
    LEFT JOIN [test].[dbo].[WeatherData] T2 ON DATEADD(MINUTE, 10, T1.date) = T2.date
    LEFT JOIN [test].[dbo].[WeatherData] T3 ON T3.date > T1.Date
    WHERE T2.[value] IS NULL
    GROUP BY T1.[date]

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

Результаты будут примерно такими:

DateFrom                    DateTo
2009-10-02 10:10:00.000 2009-10-02 10:40:00.000
2009-10-02 11:00:00.000 NULL

Последняя строка представляет все данные с последней отметки времени в будущем.

Затем вы можете использовать другое объединение, чтобы получить строки из другой базы данных, которые имеют временную метку между этими интервалами.

0 голосов
/ 05 декабря 2009

В качестве быстрого решения с использованием Sql Server проверьте даты, у которых нет последователя дата + интервал. Я думаю, что MySql имеет какую-то функцию dateadd, но вы можете попробовать что-то вроде этого. Это покажет вам диапазоны, в которых у вас отсутствуют данные.

DECLARE @TABLE TABLE(
        DateValue DATETIME
)

INSERT INTO @TABLE SELECT '10 Feb 2009 10:00:00'
INSERT INTO @TABLE SELECT '10 Feb 2009 10:10:00'
INSERT INTO @TABLE SELECT '10 Feb 2009 10:40:00'
INSERT INTO @TABLE SELECT '10 Feb 2009 10:50:00'
INSERT INTO @TABLE SELECT '10 Feb 2009 11:00:00'

SELECT  *
FROM    @TABLE currentVal
WHERE   ((SELECT * FROM @TABLE nextVal WHERE DATEADD(mi,10,currentVal.DateValue) = nextVal.DateValue) IS NULL AND currentVal.DateValue != (SELECT MAX(DateValue) FROM @TABLE))
OR      ((SELECT * FROM @TABLE prevVal WHERE DATEADD(mi,-10,currentVal.DateValue) = prevVal.DateValue) IS NULL  AND currentVal.DateValue != (SELECT MIN(DateValue) FROM @TABLE))
0 голосов
/ 05 декабря 2009

Выполните самостоятельное объединение, а затем вычислите максимальные значения, которые меньше и имеют разницу больше, чем ваш интервал.

В Oracle я бы сделал это следующим образом (с ts, являющимся столбцом метки времени):

Select t1.ts, max(t2.ts)
FROM atable t1 join atable t2 on t1.ts > t2.ts
GROUP BY t1.ts
HAVING (t1.ts - max(t2.ts))*24*60 > 10

В mySql будут более эффективные способы вычисления различий, но я надеюсь, что идея найдется.

Этот запрос даст вам метки времени сразу после и до сбоя, и вы можете строить оттуда.

0 голосов
/ 05 декабря 2009

Создать временную таблицу (JOIN). Или возьмите все даты и запросите их локально, где у вас должно быть свободное правление (цикл / хэш).

Для СОЕДИНЕНИЯ ваша сгенерированная ссылка на все даты является вашей базовой таблицей, а ваши данные - вашей объединенной таблицей. Ищите пары, в которых объединенные данные не существуют, и выберите сгенерированную дату.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...