У меня есть таблица с именами events
. Это выглядит так:
id | location_id | type | date
1 | 123 | success | 2018-01-02
2 | 45 | success | 2018-01-13
3 | 123 | failure | 2018-01-23
4 | 66 | failure | 2018-02-04
5 | 123 | success | 2018-02-06
6 | 66 | failure | 2018-03-04
Столбец type
может иметь только два значения - "успех" или "отказ". Я должен выполнить следующее: найдите каждый location_id
для которого по меньшей мере две последовательные записи в таблице events
имеют type=failure
. Последовательность, когда вы заказываете записи по дате, то есть. В приведенном выше примере следует возвращать только location_id
66, поскольку он имеет два последовательных сбоя в столбце type
.
Очевидное решение:
iterate through location_ids
get all entries from events table for each location_id, ordered by date
iterate through the results and return true if we find two consecutive rows with type=failure
Моя проблема с этим подходом: у меня несколько тысяч location_id
s, и каждый из них может иметь сотни записей в таблице events
. Это означает, что каждый раз при выполнении этой задачи мы можем выполнять сотни тысяч операций (что довольно часто, потому что ее результаты должны отображаться на главной странице нашей панели администратора).
Поэтому мне было интересно, знает ли кто-нибудь лучшее решение. Я попытался найти запрос, чтобы помочь мне с этим, но безрезультатно.
Создание данных таблицы/вставки
CREATE TABLE events
('id' int, 'location_id' int, 'type' varchar(7), 'date' date)
;
INSERT INTO events
('id', 'location_id', 'type', 'date')
VALUES
(1, 123, 'success', '2018-01-02'),
(2, 45, 'success', '2018-01-13'),
(3, 123, 'failure', '2018-01-23'),
(4, 66, 'failure', '2018-02-04'),
(5, 123, 'success', '2018-02-06'),
(6, 66, 'failure', '2018-03-04')
;
Для этого решения я предполагал, что, когда вы сказали, что это последовательный, вы имеете в виду..
последовательный месяц с тем же годом и в тот же день
Так
2018-02-04
2018-03-04
является последовательным значением
последовательный день с тем же годом и тем же месяцем
Так
2018-02-04
2018-02-05
является последовательным значением
Нам просто нужно показать location_id в любом случае, а не о дате последнего отказа. Таким образом, 3 отказа или больше не должны иметь значения
Лучшее, что нужно сделать, это спроектировать запрос, который может по меньшей мере соответствовать двум или более отдельным записям даты на основе location_id
и группе type
с фильтром, где type = 'failure'
запрос
SELECT
location_id
, type
FROM
events
WHERE
type = 'failure'
GROUP BY
location_id
, type
HAVING
COUNT(DISTINCT date) >= 2
Результат
| location_id | type |
|-------------|---------|
| 66 | failure |
см. демо http://sqlfiddle.com/#!9/df4679e/56
Теперь мы используем INNER JOIN для получения всех записей.
запрос
SELECT
events.*
FROM (
SELECT
location_id
, type
FROM
events
WHERE
type = 'failure'
GROUP BY
location_id
, type
HAVING
COUNT(DISTINCT date) >= 2
) AS events_grouped
INNER JOIN
events
ON
events_grouped.location_id = events.location_id
AND
events_grouped.type = events.type
Результат
| id | location_id | type | date |
|----|-------------|---------|------------|
| 4 | 66 | failure | 2018-02-04 |
| 6 | 66 | failure | 2018-03-04 |
Теперь нам нужно иметь доступ к следующей записи. Некоторые базы данных поддерживают LEAD для этого.
Но текущие версии готовой версии MySQL не поддерживают это
Таким образом, мы собираемся моделировать LEAD с переключением самоподключения.
запрос
SELECT
events1.*
, events2.*
FROM (
SELECT
location_id
, type
FROM
events
WHERE
type = 'failure'
GROUP BY
location_id
, type
HAVING
COUNT(DISTINCT date) >= 2
) AS events_grouped
INNER JOIN
events events1
ON
events_grouped.location_id = events1.location_id
AND
events_grouped.type = events1.type
INNER JOIN
events events2
ON
# shift to have acces to the next record.
events1.id <> events2.id
AND
events1.date <= events2.date
Результат
| id | location_id | type | date | id | location_id | type | date |
|----|-------------|---------|------------|----|-------------|---------|------------|
| 4 | 66 | failure | 2018-02-04 | 5 | 123 | success | 2018-02-06 |
| 4 | 66 | failure | 2018-02-04 | 6 | 66 | failure | 2018-03-04 |
см. демо http://sqlfiddle.com/#!9/df4679e/62
Вы можете ясно, что записи смещаются в JOIN, поэтому мы теперь можем добавить последовательную проверку значений, о которой я говорил.
Окончательный запрос
SELECT
events1.location_id
FROM (
SELECT
location_id
, type
FROM
events
WHERE
type = 'failure'
GROUP BY
location_id
, type
HAVING
COUNT(DISTINCT date) >= 2
) AS events_grouped
INNER JOIN
events events1
ON
events_grouped.location_id = events1.location_id
AND
events_grouped.type = events1.type
INNER JOIN
events events2
ON
# shift to have acces to the next record.
events1.id <> events2.id
AND
events1.date <= events2.date
AND
(
(
# check consecutive MONTH, YEAR and DAY need to be the same
# consecutive month with the same year and same day
# So <br />
# 2018-02-04 <br />
# 2018-03-04 <br />
# is a consecutive value
ABS(YEAR(events1.date) - YEAR(events2.date)) = 0
AND
ABS(MONTH(events1.date) - MONTH(events2.date)) = 1
AND
ABS(DAY(events1.date) - DAY(events2.date)) = 0
)
OR
(
# check consecutive DAY, YEAR and MONTH need to be the same
# consecutive month with the same year and same day
# So <br />
# 2018-02-04 <br />
# 2018-02-05 <br />
# is a consecutive value
ABS(YEAR(events1.date) - YEAR(events2.date)) = 0
AND
ABS(MONTH(events1.date) - MONTH(events2.date)) = 0
AND
ABS(DAY(events1.date) - DAY(events2.date)) = 1
)
)
Результат
| location_id |
|-------------|
| 66 |
см. демо http://sqlfiddle.com/#!9/df4679e/65