Как эффективно определять изменения между строками с помощью SQL - PullRequest
5 голосов
/ 24 мая 2011

У меня очень большая таблица 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?

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

Ответы [ 2 ]

3 голосов
/ 24 мая 2011

Вы можете попробовать это - я не собираюсь гарантировать, что он будет работать лучше, но это мой обычный способ соотнести строку с «предыдущей» строкой:

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), то этот кандидат действительно был предыдущей строкой.

3 голосов
/ 24 мая 2011

Полагаю, вы не можете переключать движок БД.В таком случае оконные функции позволят вам написать что-то вроде этого:

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
...