Как эффективно определять изменения между строками с использованием SQL
У меня очень большая таблица MySQL, содержащая данные, считываемые с нескольких датчиков. По существу, есть метка времени и столбец значений. Я опускаю идентификатор датчика, индексирует другие детали здесь:
CREATE TABLE `data` (
`time` datetime NOT NULL,
`value` float NOT NULL
)
Столбец value
редко изменяется, и мне нужно найти моменты времени, когда происходят эти изменения. Предположим, что есть значение каждую минуту, следующий запрос возвращает именно то, что мне нужно:
SELECT d.*,
(SELECT value FROM data WHERE time<d.time ORDER by time DESC limit 1)
AS previous_value
FROM data d
HAVING d.value<>previous_value OR previous_value IS NULL;
+---------------------+-------+----------------+
| time | value | previous_value |
+---------------------+-------+----------------+
| 2011-05-23 16:05:00 | 1 | NULL |
| 2011-05-23 16:09:00 | 2 | 1 |
| 2011-05-23 16:11:00 | 2.5 | 2 |
+---------------------+-------+----------------+
Единственная проблема заключается в том, что это очень неэффективно, в основном из-за зависимого подзапроса. Какой был бы лучший способ оптимизировать это с помощью инструментов, которые MySQL 5.1 может предложить?
Последнее ограничение состоит в том, что значения не упорядочены до того, как они будут вставлены в таблицу данных, и что они могут быть обновлены позднее. Это может повлиять на любые возможные стратегии де-нормализации.
Ответы
Ответ 1
Вы можете попробовать это - я не буду гарантировать, что он будет работать лучше, но это мой обычный способ сопоставить строку со "предыдущей" строкой:
SELECT
* --TODO, list columns
FROM
data d
left join
data d_prev
on
d_prev.time < d.time --TODO - Other key columns?
left join
data d_inter
on
d_inter.time < d.time and
d_prev.time < d_inter.time --TODO - Other key columns?
WHERE
d_inter.time is null AND
(d_prev.value is null OR d_prev.value <> d.value)
(Я думаю, что это правильно - можно было бы с некоторыми образцами данных проверить его).
В принципе, идея состоит в том, чтобы присоединить таблицу к себе, и для каждой строки (в d
) найдите строки-кандидаты (в d_prev
) для "предыдущей" строки. Затем выполните дальнейшее соединение, чтобы попытаться найти строку (в d_inter
), которая существует между текущей строкой (в d
) и рядом с кандидатом (в d_prev
). Если мы не сможем найти такую строку (d_inter.time is null
), то этот кандидат действительно был предыдущей строкой.
Ответ 2
Я полагаю, что это не вариант для переключения механизма БД. В случае, если это возможно, то функции окна позволяют писать такие вещи:
SELECT d.*
FROM (
SELECT d.*, lag(d.value) OVER (ORDER BY d.time) as previous_value
FROM data d
) as d
WHERE d.value IS DISTINCT FROM d.previous_value;
Если нет, вы можете попробовать переписать запрос так:
select data.*
from data
left join (
select data.measure_id,
data.time,
max(prev_data) as prev_time
from data
left join data as prev_data
on prev_data.time < data.time
group by data.measure_id, data.time, data.value
) as prev_data_time
on prev_data_time.measure_id = data.measure_id
and prev_data_time.time = data.time
left join prev_data_value
on prev_data_value.measure_id = data.measure_id
and prev_data_value.time = prev_data_time.prev_time
where data.value <> prev_data_value.value or prev_data_value.value is null