Запрос, чтобы найти два последовательных значения столбца

0

У меня есть таблица с именами 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. Это означает, что каждый раз при выполнении этой задачи мы можем выполнять сотни тысяч операций (что довольно часто, потому что ее результаты должны отображаться на главной странице нашей панели администратора).

Поэтому мне было интересно, знает ли кто-нибудь лучшее решение. Я попытался найти запрос, чтобы помочь мне с этим, но безрезультатно.

  • 0
    «Мне нужно выполнить следующее: найти каждый location_id, для которого по крайней мере две последовательные записи в таблице событий имеют тип = сбой» -> чтобы ожидаемый результат, основанный на данных вашего примера, возвращал ноль записей
  • 0
    @RaymondNijland Да, это правильно. Я обновлю данные, чтобы включить положительный результат через минуту.
Показать ещё 9 комментариев
Теги:

1 ответ

1

Создание данных таблицы/вставки

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')
;

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

  1. последовательный месяц с тем же годом и в тот же день

    Так
    2018-02-04
    2018-03-04
    является последовательным значением

  2. последовательный день с тем же годом и тем же месяцем

    Так
    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

  • 0
    Теперь это красота :) Но я потерял тебя на самом последнем шаге. Я не интерпретировал "последовательный", чтобы быть точно на расстоянии в один месяц; скорее просто как две неудачи подряд, когда они сгруппированы по location_id и отсортированы по дате. Тем не менее, +1 за «смену». Я подумал, что что-то подобное будет необходимо, но у вас был этот превосходный ответ, прежде чем у меня была возможность много думать об этом. Отличный ответ!
  • 0
    @TimMorton У меня в основном один и тот же комментарий, и вы правы относительно того, что подразумевается под «последовательным». Я приму этот ответ, потому что он помог мне решить проблему, но я предлагаю обновить его, чтобы отразить, что на самом деле означает «последовательный».
Показать ещё 1 комментарий

Ещё вопросы

Сообщество Overcoder
Наверх
Меню