SQL: выбор строк, где значение столбца изменилось с предыдущей строки
Скажем, у меня есть эта (MySQL) база данных, отсортированная по возрастающей метке времени:
Timestamp System StatusA StatusB
2011-01-01 A Ok Ok
2011-01-02 B Ok Ok
2011-01-03 A Fail Fail
2011-01-04 B Ok Fail
2011-01-05 A Fail Ok
2011-01-06 A Ok Ok
2011-01-07 B Fail Fail
Как выбрать строки, в которых StatusA изменен из предыдущей строки для этой системы? StatusB не имеет значения (я показываю это только в этом вопросе, чтобы проиллюстрировать, что может быть много последовательных строк для каждой системы, где StatusA не изменяется). В приведенном выше примере запрос должен возвращать строки 2011-01-03 (StatusA изменен между 2011-01-01 и 2011-01-03 для SystemA), 2011-01-06, 2011-01-07.
Запрос должен выполняться быстро, когда таблица содержит десятки тысяч записей.
Спасибо
Ответы
Ответ 1
SELECT a.*
FROM tableX AS a
WHERE a.StatusA <>
( SELECT b.StatusA
FROM tableX AS b
WHERE a.System = b.System
AND a.Timestamp > b.Timestamp
ORDER BY b.Timestamp DESC
LIMIT 1
)
Но вы можете попробовать это также (с индексом на (System,Timestamp)
:
SELECT System, Timestamp, StatusA, StatusB
FROM
( SELECT (@statusPre <> statusA AND @systemPre=System) AS statusChanged
, System, Timestamp, StatusA, StatusB
, @statusPre := StatusA
, @systemPre := System
FROM tableX
, (SELECT @statusPre:=NULL, @systemPre:=NULL) AS d
ORDER BY System
, Timestamp
) AS good
WHERE statusChanged ;
Ответ 2
select a.Timestamp, a.System, a.StatusA, a.StatusB
from tableX as a
cross join tableX as b
where a.System = b.System
and a.Timestamp > b.Timestamp
and not exists (select *
from tableX as c
where a.System = c.System
and a.Timestamp > c.Timestamp
and c.Timestamp > b.Timestamp
)
and a.StatusA <> b.StatusA;
Обновление адресации комментария:
Почему бы не использовать внутреннее соединение вместо перекрестного соединения?
Вопрос задает решение MySQL. Согласно документации :
В MySQL CROSS JOIN является синтаксическим эквивалентно INNER JOIN (они могут заменить друг друга). В стандартном SQL, они не эквивалентны. INNER JOIN - это используется с предложением ON, CROSS JOIN используется иначе.
Это означает, что любое из этих соединений будет работать.
Условие condition_expr, используемое с ON, равно любое условное выражение вида которые могут использоваться в предложении WHERE. Как правило, вы должны использовать ON для условий, которые определяют, как для объединения таблиц и предложения WHERE ограничить, какие строки вы хотите в результат.
Условие a.System = b.System
, вероятно, относится к категории "как присоединиться к таблицам", поэтому использование INNER JOIN было бы лучше в этом случае.
Поскольку оба продукта дают одинаковые результаты, разница может быть в производительности. Чтобы сказать, что будет быстрее, мне нужно знать, как объединения выполняются внутри страны - используют ли они индексы или хеширование для соединения.
Ответ 3
Использовать rownum
У меня есть 0,05 секунды на 20000 строк
select a1.*
from (select rownum R_NUM, TIMESTAMP, System, StatusA from TableX) a1
join (select rownum R_NUM, TIMESTAMP, SYSTEM, STATUSA from TABLEX) a2
on a1.R_NUM = a2.R_NUM+1
where a1.system = a2.system
and a1.StatusA != a2.StatusA
Ответ 4
Здесь немного более короткая версия с аналогичной логикой. Я проверял это так часто, я уверен, что он эффективен; прежде всего потому, что он устраняет коррелированный подзапрос (WHERE NOT EXISIS).
"c" находится там, чтобы убедиться, что b находится непосредственно под a - он говорит, что c (между ними) не может быть найден (через тест NULL).
SELECT a.Timestamp, a.System, a.StatusA, a.StatusB
FROM tableX AS a
JOIN tableX AS b
ON a.System = b.System
AND a.Timestamp > b.Timestamp
LEFT JOIN tableX AS c
ON a.System = b.System
AND a.Timestamp > c.Timestamp
AND b.Timestamp < c.Timestamp
WHERE c.System IS NULL
AND a.StatusA <> b.StatusA;
Ответ 5
Ответ Egor работал у меня в MSSQL с небольшим изменением. Если бы заменить операторы ROWNUM на:
select row_number () over (order by TIMESTAMP) as R_NUM, ...
Ответ 6
ВЫБЕРИТЕ a. * FROM (выберите row_number() over (разделить по Порядку системы по Timestamp asc) в качестве aRow, Timestamp, System, StatusA, StatusB из tableX) в качестве левого соединения (выберите row_number() over (разделить по Порядку системы по Timestamp) asc) как bRow, Timestamp, System, StatusA, StatusB из таблицы X) как b в a.aRow = b.bRow + 1 и a.System = b.System, где (a.StatusA! = b.StatusA или b.StatusA является ноль)
Он вернет первую строку и строки, где значение отличается.