Запрос максимального количества одновременных промежутков времени

У меня есть таблица SQL Server с двумя полями даты и времени (CnxStartdatetime, CnxEnddatetime). Каждая строка представляет собой передачу информации. Я пытаюсь найти максимальное количество одновременных передач на основе этих двух меток времени. У меня есть рабочий запрос, но он медленный и чрезвычайно громоздкий. Я знаю, что должен быть лучший способ сделать это, но не могу придумать ни одного.

Для текущей версии, если я запускаю ее с 5 «уровнями» и получаю результаты, мне нужно вернуться и добавить тонну SQL, чтобы проверить, есть ли экземпляры 6 одновременных передач и т. д. Как только запрос получает 7-8 «уровни "глубоко он становится очень медленным.

Фрагмент текущей версии:

select 
    t1.id, t2.id, t3.id, t4.id, t5.id, t6.id, t7.id, t8.id, t9.id, t10.id

FROM
dbo.MyTable t1, dbo.MyTable t2, dbo.MyTable t3, dbo.MyTable t4, dbo.MyTable t5,
dbo.MyTable t6, dbo.MyTable t7, dbo.MyTable t8, dbo.MyTable t9, dbo.MyTable t10
WHERE
(((t2.cnxstartdatetime >= t1.cnxstartdatetime) and (t2.cnxstartdatetime <= t1.cnxenddatetime))
or ((t2.cnxenddatetime >= t1.cnxstartdatetime) and (t2.cnxenddatetime <= t1.cnxenddatetime)))
AND
t2.id != t1.id
AND
(((t3.cnxstartdatetime >= t2.cnxstartdatetime) and (t3.cnxstartdatetime >= t1.cnxstartdatetime)and (t3.cnxstartdatetime <= t1.cnxenddatetime) and (t3.cnxstartdatetime <= t2.cnxenddatetime))
or ((t3.cnxenddatetime >= t2.cnxstartdatetime) and (t3.cnxenddatetime >= t1.cnxstartdatetime)and (t3.cnxenddatetime <= t1.cnxenddatetime) and (t3.cnxenddatetime <= t2.cnxenddatetime)))
AND
t3.id != t2.id AND t3.id != t1.id
AND
(((t4.cnxstartdatetime >= t3.cnxstartdatetime) and (t4.cnxstartdatetime >= t1.cnxstartdatetime)and (t4.cnxstartdatetime >= t2.cnxstartdatetime) and (t4.cnxstartdatetime <= t1.cnxenddatetime) and (t4.cnxstartdatetime <= t3.cnxenddatetime)and (t4.cnxstartdatetime <= t2.cnxenddatetime))
or ((t4.cnxenddatetime >= t3.cnxstartdatetime) and (t4.cnxenddatetime >= t1.cnxstartdatetime)and (t4.cnxenddatetime >= t2.cnxstartdatetime) and (t4.cnxenddatetime <= t1.cnxenddatetime)and (t4.cnxenddatetime <= t3.cnxenddatetime)and (t4.cnxenddatetime <= t2.cnxenddatetime)))
AND
t4.id != t3.id AND t4.id != t2.id AND t4.id != t1.id
... *snip*

Изменить Во многих ответах предлагается использовать cross join. Это не дает результатов, которые я ищу. Вот пример результатов cross join для "перекрытий" одной записи. Это список, который он дает мне для ID 11787 Как видите, 11781 не перекрывается 11774 Это просто список любой записи, временной интервал которой пересекается 11787

11774    2011-04-29 01:02:56.780    2011-04-29 01:02:58.793
11777    2011-04-29 01:02:56.780    2011-04-29 01:02:58.843
11778    2011-04-29 01:02:56.780    2011-04-29 01:02:58.950
11775    2011-04-29 01:02:56.793    2011-04-29 01:02:58.843
11776    2011-04-29 01:02:56.793    2011-04-29 01:02:58.890
11780    2011-04-29 01:02:58.310    2011-04-29 01:03:02.687
11779    2011-04-29 01:02:58.327    2011-04-29 01:03:02.543
11787    2011-04-29 01:02:58.530    2011-04-29 01:03:08.827 **
11781    2011-04-29 01:02:59.030    2011-04-29 01:03:05.187
11782    2011-04-29 01:02:59.247    2011-04-29 01:03:05.467
11784    2011-04-29 01:02:59.293    2011-04-29 01:03:05.810
11791    2011-04-29 01:03:00.107    2011-04-29 01:03:13.623
11786    2011-04-29 01:03:00.843    2011-04-29 01:03:08.983
11783    2011-04-29 01:03:02.560    2011-04-29 01:03:05.793
11785    2011-04-29 01:03:02.717    2011-04-29 01:03:07.357
11790    2011-04-29 01:03:05.200    2011-04-29 01:03:14.153
11804    2011-04-29 01:03:05.687    2011-04-29 01:03:25.577
11811    2011-04-29 01:03:07.093    2011-04-29 01:03:35.153
11799    2011-04-29 01:03:07.123    2011-04-29 01:03:24.437
11789    2011-04-29 01:03:08.793    2011-04-29 01:03:13.577

Я также пытался написать CTE с рекурсией, но я не могу понять, как гарантировать, что текущий ID не соответствует ни одному предыдущему ID в текущем стеке параллелизма. Приведенное ниже просто повторяется до тех пор, пока не достигнет предела.

WITH TransmissionConcurrency (StartTime, EndTime, ConcurrencyLevel) AS
(
    SELECT
        CnxStartDatetime AS StartTime,
        CnxEndDatetime AS EndTime,
        1 AS ConcurrencyLevel
    FROM dbo.MyTable

    UNION ALL

    SELECT
        CASE WHEN d.CnxStartDatetime > tc.StartTime THEN d.CnxStartDatetime ELSE tc.StartTime END AS StartTime,
        CASE WHEN d.CnxEndDatetime < tc.EndTime THEN d.CnxEndDatetime ELSE tc.EndTime END AS EndDate,
        tc.ConcurrencyLevel + 1 as ConcurrencyLevel
    FROM dbo.MyTable d
        INNER JOIN TransmissionConcurrency tc ON
            ((d.CnxStartDatetime between tc.StartTime and tc.EndTime)
            or
            (d.CnxEndDatetime between tc.StartTime and tc.EndTime)
            or
            (d.CnxStartDatetime <= tc.StartTime and d.CnxEndDatetime >= tc.EndTime))
)

SELECT * 
FROM TransmissionConcurrency
ORDER BY ConcurrencyLevel, StartTime, EndTime

Я придумал диаграмму ниже, чтобы попытаться лучше объяснить, что я ищу.

A         [--------]
B    [-----]
C              [------]
D   [---]
E             [---]
F         [-]

В этом случае методы cross join сообщат мне, что максимальный параллелизм с A равен 6 (A с B, C, D, E and F). То, что я ищу, будет максимальным параллелизмом 3 (A с B,F или A с C,E)


person Jeff Swensen    schedule 29.04.2011    source источник
comment
возможный дубликат Поиск одновременных событий в базе данных между временами   -  person Martin Smith    schedule 29.04.2011


Ответы (4)


Джефф. Однажды я написал аналогичный запрос, но в Oracle, не уверен, что он будет работать в SQL-Server, но попробовать стоит: может быть, это даст вам некоторое представление:

select
  t.time as b,
  lead(t.time)  over (order by t.time, t.weight desc) as e,
  sum(t.weight) over (order by t.time, t.weight desc) as cnt
from
  ( select trunc(:aStartWith)   as time,  0 as weight from dual
    union all
    select req_recieved as time, +1 as weight
      from log_tbl
      where trunc(req_recieved, 'mi') between :aStartWith - interval '10' minute and :aEndWith + interval '10' minute
    union all
    select response_sent as time, -1 as weight
      from log_tbl
      where trunc(req_recieved, 'mi') between :aStartWith - interval '10' minute and :aEndWith + interval '10' minute
    union all
    select trunc(:aEndWith) as time,  0 as weight from dual
  ) t

Общая идея заключается в том, что я просматриваю все запросы между датой :aStartWith и датой :aEndWith, присваивая весовую долю +1 каждому запросу, который начинается в заданный период, и -1 каждому запросу, заканчивающемуся в тот же период.

Здесь я предполагаю, что запросы уже не те 10 минут (where trunc(req_recieved, 'mi') between :aStartWith - interval '10' minute and :aEndWith + interval '10' minute); и select ... from dual — граничные условия.

Затем с помощью аналитических функций я нахожу время окончания запроса (lead(t.time) over (order by t.time, t.weight desc) as e) и суммирую веса для текущего запроса — это даст количество запросов, начинающихся в момент времени b и заканчивающихся в момент времени e (sum(t.weight) over (order by t.time, t.weight desc) as cnt).

Чтобы найти максимальное количество запросов, вы можете просто обернуть этот запрос желаемыми оценками.

Не могли бы вы попробовать, работает ли этот сценарий для вас? Надеюсь, получится :)

person andr    schedule 29.04.2011
comment
Итак, это основано на передаче пар временных меток, представляющих промежутки времени, для проверки количества запросов, происходящих в течение промежутка времени? Учитывая, что время начала/окончания запросов сводится к миллисекундам, как именно я должен придумать все подходящие пары временных меток начала/окончания для передачи? Я могу быть смущен тем, как работает ваше решение. - person Jeff Swensen; 29.04.2011

declare @T table (ID int, Starts datetime, Ends datetime)
insert into @T (ID, Starts, Ends) values
(1, '2000-12-30', '2000-12-31'),
(2, '2001-01-01', '2001-01-10'),
(3, '2001-01-02', '2001-01-05'),
(4, '2001-01-03', '2001-01-04'),
(5, '2001-01-05', '2001-01-10')

select T1.ID, count(*) as Levels
from @T as T1
  cross join @T as T2
where
  T1.Starts < T2.Ends and
  T1.Starts > T2.Starts
group by T1.ID

select top 1 T1.ID, count(*) as Levels
from @T as T1
  cross join @T as T2
where
  T1.Starts < T2.Ends and
  T1.Starts > T2.Starts
group by T1.ID
order by count(*) desc

Результат

ID          Levels
----------- -----------
3           1
4           2
5           1

(3 row(s) affected)

ID          Levels
----------- -----------
4           2

Если вам нужны задействованные строки, вы можете использовать это:

select T2.*
from (select top 1 T1.ID
      from @T as T1
        cross join @T as T2
      where
        T1.Starts < T2.Ends and
        T1.Starts > T2.Starts
      group by T1.ID
      order by count(*) desc) as C
  inner join @T as T1
    on C.ID = T1.ID
  inner join @T as T2
    on T1.Starts < T2.Ends and
       T1.Starts > T2.Starts or
       T2.ID = C.ID

Результат:

ID          Starts                  Ends
----------- ----------------------- -----------------------
2           2001-01-01 00:00:00.000 2001-01-10 00:00:00.000
3           2001-01-02 00:00:00.000 2001-01-05 00:00:00.000
4           2001-01-03 00:00:00.000 2001-01-04 00:00:00.000
person Mikael Eriksson    schedule 29.04.2011
comment
Я добавил некоторую информацию к вопросу, чтобы показать, что это не совсем то, что я ищу. - person Jeff Swensen; 29.04.2011
comment
@Jeff Добавлена ​​​​еще одна версия. Виной тому не перекрестное соединение, а предложение where. Я предполагаю, что дубликат, предложенный Мартином, - это то, что вам нужно. Разница между этим и моим изменением заключается в том, что при использовании between between включается. Эта версия подсчитывает максимальное количество одновременных уровней, а не общее количество перекрывающихся промежутков. - person Mikael Eriksson; 30.04.2011

Это скорее отчетное решение, чем "стандартный" запрос к базе данных. Лучший вариант для этого — написать где-нибудь количество транзакций в начале каждой транзакции). Все другие решения будут медленными. Но если вам это действительно нужно...

Самое простое решение — разбить период времени на небольшие части (например, дни) и проанализировать подсчеты в каждой части периода. Вот пример:

DECLARE @table TABLE
    (
      starts DATETIME ,
      ends DATETIME ,
      trn INT
    )

INSERT  INTO @table
        ( starts ,
          ends ,
          trn
        )
        SELECT  '2003-01-01' ,
                '2003-01-03' ,
                1
        UNION
        SELECT  '2003-01-02' ,
                '2003-01-04' ,
                2
        UNION
        SELECT  '2003-01-02' ,
                '2005-06-06' ,
                3 ;
WITH    numbers
          AS ( SELECT   Row_NUmber() OVER ( ORDER BY o.object_id, o2.object_id ) Number
               FROM     sys.objects o
                        CROSS JOIN sys.objects o2
             ),
        Maxx
          AS ( SELECT   MIN(starts) MaxStart ,
                        MAX(ends) MaxEnd
               FROM     @table
             ),
        DDays
          AS ( SELECT   MIN(starts) DDay
               FROM     @table
               UNION ALL
               SELECT   DDay + 1
               FROM     DDays
               WHERE    dday + 1 <= ( SELECT    MaxEnd
                                      FROM      Maxx
                                    )
             )
    SELECT  DDay ,
            COUNT(*) Transactions
    FROM    @Table T
            JOIN DDays D ON D.DDay >= T.starts
                            AND D.DDay <= T.ends
    GROUP BY DDay
    HAVING COUNT(*)>1
    ORDER BY COUNT(*) DESC
OPTION  ( MAXRECURSION 0 )

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

person Dalex    schedule 29.04.2011
comment
У меня такое ощущение, что этот подход имеет ту же проблему, что и andr, в том, что временные метки, о которых я говорю, отличаются на миллисекунды, в то время как ваш подход требует, чтобы я придумал некоторый диапазон периодов времени, которые охватывают все возможные сценарии. Или я неправильно понимаю, как это работает? - person Jeff Swensen; 29.04.2011

Я знаю, что курсоры не одобряются, но перекрестные соединения тоже. Это возвращает 8 для предоставленных демонстрационных данных.

-- assuming times table with columns s and e
declare @s datetime, @e datetime;
declare @t table(d datetime);
declare c cursor for select s,e from times order by s;
open c
while(1=1) begin
  fetch next from c into @s,@e
  if @@FETCH_STATUS<>0 break;
  update top(1) @t set d=@e where d<=@s;
  if @@ROWCOUNT=0 insert @t(d) values(@e);
end
close c
deallocate c

select COUNT(*) as MaxConcurrentTimeSpans from @t
person Hafthor    schedule 11.10.2013