как сгруппировать последовательные строки?

Итак, у меня есть таблица с такими строками:

Ev_Message       Ev_Comment             EV_Custom1           Ev_Time_Ms     
-------------------------------------------------------------------------------------
Machine 1 Alarm  5/23/2016 11:02:00 AM  Alarms Scanned       25              
Machine 1 Alarm  5/23/2016 11:00:00 AM  Alarms Scanned       686 
Machine 1 Alarm  5/23/2016 11:00:00 AM  Light curtain        537
Machine 1 Alarm  5/23/2016 11:00:00 AM  Guard door open      346 
Machine 1 Alarm  5/23/2016 11:00:00 AM  No control voltage   135 
Machine 1 Alarm  5/23/2016 10:38:34 AM  Alarms Scanned       269
Machine 1 Alarm  5/23/2016 10:38:29 AM  Alarms Scanned       378
Machine 1 Alarm  5/23/2016 10:38:29 AM  Guard door open      156
Machine 1 Alarm  5/23/2016 10:38:25 AM  Alarms Scanned       654
Not an Alarm     5/23/2016 10:38:25 AM  Not an Alarm         467     
Machine 1 Alarm  5/23/2016 10:38:25 AM  Guard door open      234
Machine 1 Alarm  5/23/2016 10:38:25 AM  No control voltage   67
Machine 1 Alarm  5/23/2016 10:38:23 AM  Alarms Scanned       124
Machine 1 Alarm  5/23/2016 10:38:23 AM  No control voltage   100   

Строка «Сканированные сигналы тревоги» добавляется каждый раз, когда сканируются сигналы тревоги, то есть каждый раз, когда сигнал тревоги срабатывает или сбрасывается. Любые тревоги будут добавлять строку с определенным Ev_Custom1. первый столбец Ev_Message содержит идентификатор машины, что позволяет отделить тревоги от разных машин. (вам не нравятся произвольные имена столбцов?) Существует более девятисот уникальных аварийных сообщений.

То, что я хочу, чтобы мой запрос возвращал, выглядит примерно так:

Alarm Message       Alarm Start Time       Alarm Stop Time  
----------------------------------------------------------------  
No control voltage  5/23/2016 10:38:23 AM  5/23/2016 10:38:29 AM  
Guard door open     5/23/2016 10:38:25 AM  5/23/2016 10:38:34 AM  
No control voltage  5/23/2016 11:00:00 AM  5/23/2016 11:02:00 AM  
Guard door open     5/23/2016 11:00:00 AM  5/23/2016 11:02:00 AM  
Light curtain       5/23/2016 11:00:00 AM  5/23/2016 11:02:00 AM  

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

С некоторой помощью мой текущий запрос таков:

WITH T AS (
    SELECT     s.Ev_Comment AS start_time,
               MIN(COALESCE (e.Ev_Comment, s.Ev_Comment)) AS end_time
    FROM       A AS s
    INNER JOIN A AS e
            ON s.Ev_Comment < e.Ev_Comment
           AND s.Ev_Custom1 = 'Alarms Scanned'
           AND e.Ev_Custom1 = 'Alarms Scanned'
    GROUP BY   s.Ev_Comment)
SELECT     T_1.start_time,
           T_1.end_time,
           A.Ev_Custom1
FROM       A
INNER JOIN T AS T_1
        ON A.Ev_Comment LIKE T_1.start_time
WHERE      (A.Ev_Custom1 <> 'Alarms Scanned')

У меня еще есть одна проблема. если тревога длится более одного периода, например, «Открыта дверь охраны» с 10:38:25 до 10:38:34, тогда она будет отображаться в двух отдельных строках, например:

start_time             end_time               EV_Custom1   
---------------------  ---------------------  -------------
5/23/2016 10:38:25 AM  5/23/2016 10:38:29 AM  Guard door open
5/23/2016 10:38:29 AM  5/23/2016 10:38:34 AM  Guard door open

Когда в идеале я хочу:

start_time             end_time               EV_Custom1   
---------------------  ---------------------  -------------
5/23/2016 10:38:25 AM  5/23/2016 10:38:34 AM  Guard door open

Я думаю, что мне нужно group by ((Ev_custom1) and (when end_time = start_time)) (простите за мой псевдокод), но я недостаточно знаю синтаксис, необходимый для этого.

Вот SQLFiddle


person z_temp_string    schedule 26.05.2016    source источник
comment
как вы получаете значения для времени остановки будильника?   -  person techspider    schedule 26.05.2016
comment
это временная метка сканируемой строки тревоги, в которой нет тревоги с такой же временной меткой. указывая на то, что во время этого сканирования тревога не была обнаружена.   -  person z_temp_string    schedule 26.05.2016
comment
я добавил важность столбца Ev_Message для каждого запроса   -  person z_temp_string    schedule 27.05.2016


Ответы (2)


Если я правильно понимаю опубликованную проблему, то ваш CTE эффективно определяет периоды времени (или интервалы) для всех ваших сигналов тревоги. Ваше последнее предложение выбора объединяет фактическую информацию о тревоге с вашими интервалами тревоги. Часть вашей проблемы заключается в том, что ваша система сигнализации будет продолжать регистрировать записи «Сканирование тревог», если ваша тревога остается активной в течение длительного периода времени (я предполагаю, что дольше, чем ваш цикл сканирования тревог), что эффективно приводит к разделению активных тревог. Если у вас есть SQL Server 2012 или более поздней версии, то относительно легко определить, было ли событие тревоги разделено. Вам просто нужно проверить, совпадает ли время окончания тревоги со временем начала следующей тревоги того же типа. Этого можно добиться с помощью функции оконной функции LAG в версии 2012.
Следующим шагом является создание идентификатора, по которому можно сгруппировать будильник, чтобы можно было объединять разделенные события. Это достигается с помощью предложения SUM OVER. В следующем примере показано, как этого можно достичь:

;WITH AlarmTimeBuckets
AS 
(
    SELECT       EventStart.Ev_Comment AS StartDateTime 
                ,MIN(COALESCE (EventEnd.Ev_Comment, EventStart.Ev_Comment)) AS EndDateTime
                ,EventStart.Ev_Message As Machine
    FROM         A EventStart 
    INNER JOIN   A EventEnd ON EventStart.Ev_Comment < EventEnd.Ev_Comment AND EventStart.Ev_Custom1 = 'Alarms Scanned' AND EventEnd.Ev_Custom1 = 'Alarms Scanned' AND EventStart.Ev_Message = EventEnd.Ev_Message
    GROUP BY     EventStart.Ev_Message, EventStart.Ev_Comment
),
AlarmsByTimeBucket
AS
(
    SELECT      AlarmTimeBuckets.Machine
               ,AlarmTimeBuckets.StartDateTime
               ,AlarmTimeBuckets.EndDateTime 
               ,Alarm.Ev_Custom1 AS Alarm
               ,(
                 CASE
                    WHEN LAG(AlarmTimeBuckets.EndDateTime, 1, NULL) OVER (PARTITION BY Alarm.Ev_Custom1,Alarm.Ev_Message ORDER BY AlarmTimeBuckets.StartDateTime) = AlarmTimeBuckets.StartDateTime THEN 0
                    ELSE 1
                 END
                ) AS IsNewEvent
    FROM       A Alarm 
    INNER JOIN AlarmTimeBuckets  ON Alarm.Ev_Message = AlarmTimeBuckets.Machine AND  Alarm.Ev_Comment = AlarmTimeBuckets.StartDateTime
    WHERE     (Alarm.Ev_Custom1 <> 'Alarms Scanned')
)
,
AlarmsByGroupingID
AS
(
    SELECT   Machine
            ,StartDateTime
            ,EndDateTime
            ,Alarm
            ,SUM(IsNewEvent) OVER (ORDER BY Machine, Alarm, StartDateTime) AS GroupingID
    FROM    AlarmsByTimeBucket
)
SELECT       MAX(Machine) AS Machine
            ,MIN(StartDateTime) AS StartDateTime
            ,MAX(EndDateTime) AS EndDateTime
            ,MAX(Alarm) AS Alarm
FROM        AlarmsByGroupingID
GROUP BY    GroupingID
ORDER BY    StartDateTime
person Edmond Quinton    schedule 26.05.2016
comment
Оказывается, я использую SQL Server 2012. Я запрашиваю через Microsoft Visual Studio 2010, используя службы отчетов SQL Server. Я скопировал ваш ответ дословно, и это дало мне именно то, что я хотел. - person z_temp_string; 26.05.2016
comment
я был неправ. вы не учли первую колонку Ev_Message. этот столбец содержит идентификатор машины, который позволяет мне отделять сигналы тревоги от разных машин. (вам не нравятся произвольные имена столбцов?) ваш запрос не смотрит на этот столбец. плохо видеть, если я могу настроить его сам. - person z_temp_string; 27.05.2016
comment
@TylerLillemo, пожалуйста, обновите свой исходный пост, чтобы отразить важность EV_Message, поскольку вы впервые упомянули об этом. - person Edmond Quinton; 27.05.2016
comment
спасибо, я смог исправить это, я думаю, добавив оператор WHERE между первыми inner join и group by - person z_temp_string; 27.05.2016
comment
@TylerLillemo вам также нужно будет обновить форму оконных вызовов OVER (PARTITION BY Alarm.Ev_Message на OVER (PARTITION BY Alarm.Ev_Custom1, Alarm.Ev_Message. Я обновил свой ответ, чтобы отразить это изменение. - person Edmond Quinton; 27.05.2016
comment
Спасибо за помощь. Я обновил свой запрос, чтобы он соответствовал, и он работает очень хорошо. у меня это сработало без этого шага, потому что те немногие, которые я тестировал, имели немного отличающийся текст сигнала тревоги между двумя машинами. - person z_temp_string; 27.05.2016

Я также обновил вашу ссылку на sqlfiddle, добавив обновления ниже. В вашем окончательном наборе результатов вам нужно установить номер строки и вернуться к нему на EV_CUSTOM1, START_TIME = END_TIME (как вы подозревали), а также номер строки = номер строки +1. Вот как вы можете определить, относятся ли два события к одному и тому же периоду. Было бы немного проще, если бы вы работали на Sql Server 2012+, где у вас есть доступные функции LAG/LEAD, как указал @EdmondQuinton в своем ответе.

WITH T AS (SELECT  s.Ev_Comment AS start_time, MIN(COALESCE (e.Ev_Comment, s.Ev_Comment)) AS end_time           
           FROM A AS s 
           INNER JOIN A AS e 
           ON s.Ev_Comment < e.Ev_Comment 
           AND s.Ev_Custom1 = 'Alarms Scanned' 
           AND e.Ev_Custom1 = 'Alarms Scanned'
           GROUP BY s.Ev_Comment
          ),

T2 AS(SELECT T_1.start_time, T_1.end_time, A.Ev_Custom1,
             ROW_NUMBER() OVER (PARTITION BY EV_CUSTOM1 ORDER BY T_1.START_TIME) RN
      FROM  A 
      INNER JOIN
      T AS T_1 
      ON A.Ev_Comment LIKE T_1.start_time
      WHERE (A.Ev_Custom1 <> 'Alarms Scanned')
      )

select 
  coalesce(b.START_TIME, a.START_TIME) START_TIME, 
  max(a.END_TIME) END_TIME, 
  a.EV_CUSTOM1
from T2 a
left outer join T2 b
on a.EV_CUSTOM1 = b.EV_CUSTOM1
and a.START_TIME = b.END_TIME
and a.RN = b.RN+1
group by coalesce(b.START_TIME, a.START_TIME), 
         a.EV_CUSTOM1
person msheikh25    schedule 26.05.2016
comment
Спасибо за ваш ответ. это тоже работает. мне пришлось добавить оператор WHERE между первыми inner join и group by, как в сообщении @EdmondQuinton. мои извинения за то, что я не дал понять, что мне это нужно. - person z_temp_string; 27.05.2016
comment
по какой-то причине я получаю две отдельные строки для сигналов тревоги, длящихся дольше одного дня. Решение @EdmondQuinton не имеет этой проблемы. - person z_temp_string; 27.05.2016
comment
Я не в состоянии воспроизвести это. Судя по вашим примерам данных, если я изменю последний сигнал тревоги с 5/23/2016 11:02:00 AM на 5/24/2016 11:02:00 AM, он все равно будет работать нормально. Я могу посмотреть, есть ли у вас дополнительные данные, но похоже, что у вас уже есть рабочее решение. - person msheikh25; 27.05.2016