Найдите перекрывающиеся события в SQL и сохраните более длинные

#sql #sql-server

Вопрос:

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

Например: Объект 4 инициировал событие 1, но во время выполнения этого события он также инициировал Событие 2. Поскольку событие 2 находится внутри временных рамок выполнения События 1, его необходимо отбросить.

 DateIni             DateEnd               ObjectId  Description
2021-04-24 11:59:24 2021-04-24 12:00:24   4         Event 1
2021-04-24 11:59:25 2021-04-24 12:00:23   4         Event 2
2021-04-23 20:23:35 2021-04-23 20:24:35   11        Event 3
2021-04-23 20:23:37 2021-04-23 20:24:32   11        Event 4
2021-04-23 15:17:12 2021-04-23 15:18:12   2         Event 5
2021-04-23 15:17:14 2021-04-23 15:18:09   2         Event 6
 

Я делаю это, но до сих пор безуспешно.

 from Table A
INNER join Table B
    on (A.DateEnd < B.DateIni AND A.DateIni >= B.DateEnd)
    or (A.DateEnd >= B.DateIni AND A.DateIni <= B.DateEnd)
    or (A.DateEnd > B.DateIni AND A.DateIni < B.DateEnd)
    or (A.DateEnd <= B.DateIni AND A.DateIni > B.DateEnd)
    and B.ObjectId = A.ObjectId 
    and B.Description != A.Description
 

Есть какие-нибудь предложения о том, как это можно сделать?

Ответ №1:

Возможно ли включить событие, даты которого не пересекаются? Если нет — тогда было бы проще просто использовать row_number() поверх набора и получить первое событие.

Вы можете сделать что — то подобное-могут быть лучшие варианты, но это должно сработать:

  --==== Sample data
Declare @testData Table (DateIni datetime, DateEnd datetime, ObjectId int, Description varchar(30));
 Insert Into @testData (DateIni, DateEnd, ObjectId, [Description])
 Values ('2021-04-24 11:59:24', '2021-04-24 12:00:24',  4, 'Event 1')
      , ('2021-04-24 11:59:25', '2021-04-24 12:00:23',  4, 'Event 2')
      , ('2021-04-24 12:59:25', '2021-04-24 13:00:23',  4, 'Event 7')
      , ('2021-04-23 20:23:35', '2021-04-23 20:24:35', 11, 'Event 3')
      , ('2021-04-23 20:23:37', '2021-04-23 20:24:32', 11, 'Event 4')
      , ('2021-04-23 21:23:37', '2021-04-23 21:24:32', 11, 'Event 8')
      , ('2021-04-23 15:17:12', '2021-04-23 15:18:12',  2, 'Event 5')
      , ('2021-04-23 15:17:14', '2021-04-23 15:18:09',  2, 'Event 6')
      , ('2021-04-23 16:17:14', '2021-04-23 16:18:09',  2, 'Event 9');

 --==== Result using above sample data
   With previousDates
     As (
 Select *
      , PreviousEndDate = lag(td.DateEnd, 1) over(Partition By td.ObjectId Order By td.DateIni)
      , PreviousStartDate = lag(td.DateIni, 1) over(Partition By td.ObjectId Order By td.DateIni)
   From @testData               td
        )
 Select pd.DateIni
      , pd.DateEnd
      , pd.ObjectId
      , pd.Description
   From previousDates           pd     
  Where 1 = Case When pd.DateIni Between pd.PreviousStartDate And pd.PreviousEndDate Then 0
                 When pd.DateEnd Between pd.PreviousStartDate And pd.PreviousEndDate Then 0
                 Else 1
             End;
   
 

Теперь — если все, что нас интересует, это когда началось следующее событие — и если это следующее событие началось до завершения предыдущего события:

  --==== Result using above sample data
   With previousDates
     As (
 Select *
      , PreviousEndDate = lag(td.DateEnd, 1) over(Partition By td.ObjectId Order By td.DateIni)
   From @testData               td
        )
 Select pd.DateIni
      , pd.DateEnd
      , pd.ObjectId
      , pd.Description
   From previousDates           pd     
  Where pd.PreviousEndDate Is Null
     Or pd.PreviousEndDate < pd.DateIni;
 

Или — мы можем определить, является ли это событие первым событием, и отфильтровать его таким образом:

  --==== Result using above sample data
   With firstEvents
     As (
 Select *
      , isFirstEvent = iif(lag(td.DateEnd, 1, td.DateEnd) over(Partition By td.ObjectId Order By td.DateIni) <= td.DateEnd, 1, 0)
   From @testData               td
        )
 Select fe.DateIni
      , fe.DateEnd
      , fe.ObjectId
      , fe.Description
   From firstEvents             fe
  Where fe.isFirstEvent = 1;
 

Комментарии:

1. Да, возможно существование события, которое не имеет никакого перекрытия. Первый вариант отлично работает, потому что в большинстве случаев оба события заканчиваются примерно в одно и то же время.

Ответ №2:

Вы бы просто использовали not exists :

 select t.*
from t
where not exists (select 1
                  from t t2
                  where t2.objectid = t.objectid and
                        t2.dateini < t.dateini and
                        t2.dateend > t.dateend
                 );
 

Для повышения производительности вам нужен индекс (objectid, dateini, dateend) .

Комментарии:

1. Я пытался это сделать, но так как это события с одинаковыми датами и датами, это решение может отбросить события, которые не должны быть отброшены.

2. @DiogoMartins . . . Я не понимаю. Это не приведет к удалению событий, имеющих одинаковые даты окончания ini.

3. Извините, я допустил ошибку, когда скопировал функцию. Это работает, но по сравнению с другим ответом он медленнее.

4. @DiogoMartins . . . Это не делает тех же предположений о данных, так что на самом деле это лучше подходит для вопроса, который вы на самом деле задали.