Отказ от отговорност:Пиша отговора си въз основа на (отличен) следния пост:
https://www.itprotoday.com/sql-server/calculating-concurrent-sessions-part-3 (Част 1 и 2 също се препоръчват)
Първото нещо, което трябва да разберете тук с този проблем, е, че повечето от настоящите решения, открити в интернет, могат да имат основно два проблема
- Резултатът не е правилният отговор (например ако диапазон A се припокрива с B и C, но B не се припокрива с C, те се считат за 3 припокриващи се диапазона).
- Начинът за изчисляването му е много неефективен (защото е O(n^2) и/или те се въртят за всяка секунда от периода)
Често срещаният проблем с производителността в решения като предложеното от Unreasons е куадратично решение, за всяко повикване трябва да проверявате всички други повиквания, ако се припокриват.
има алгоритмично линейно общо решение, което изброява всички „събития“ (начално повикване и крайно обаждане), подредени по дата, и добавя 1 за начало и изважда 1 за прекъсване и запомня макс. Това може да се приложи лесно с курсор (решението, предложено от Hafhor изглежда е по този начин), но курсорите не са най-ефективните начини за решаване на проблеми.
Посочената статия има отлични примери, различни решения, сравнение на производителността им. Предложеното решение е:
WITH C1 AS
(
SELECT starttime AS ts, +1 AS TYPE,
ROW_NUMBER() OVER(ORDER BY starttime) AS start_ordinal
FROM Calls
UNION ALL
SELECT endtime, -1, NULL
FROM Calls
),
C2 AS
(
SELECT *,
ROW_NUMBER() OVER( ORDER BY ts, TYPE) AS start_or_end_ordinal
FROM C1
)
SELECT MAX(2 * start_ordinal - start_or_end_ordinal) AS mx
FROM C2
WHERE TYPE = 1
Обяснение
да предположим, че този набор от данни
+-------------------------+-------------------------+
| starttime | endtime |
+-------------------------+-------------------------+
| 2009-01-01 00:02:10.000 | 2009-01-01 00:05:24.000 |
| 2009-01-01 00:02:19.000 | 2009-01-01 00:02:35.000 |
| 2009-01-01 00:02:57.000 | 2009-01-01 00:04:04.000 |
| 2009-01-01 00:04:12.000 | 2009-01-01 00:04:52.000 |
+-------------------------+-------------------------+
Това е начин да приложите със заявка същата идея, като добавяте 1 за всяко начало на повикване и изваждате 1 за всяко завършване.
SELECT starttime AS ts, +1 AS TYPE,
ROW_NUMBER() OVER(ORDER BY starttime) AS start_ordinal
FROM Calls
тази част от C1 CTE ще приеме всяко начало на всяко повикване и ще го номерира
+-------------------------+------+---------------+
| ts | TYPE | start_ordinal |
+-------------------------+------+---------------+
| 2009-01-01 00:02:10.000 | 1 | 1 |
| 2009-01-01 00:02:19.000 | 1 | 2 |
| 2009-01-01 00:02:57.000 | 1 | 3 |
| 2009-01-01 00:04:12.000 | 1 | 4 |
+-------------------------+------+---------------+
Сега този код
SELECT endtime, -1, NULL
FROM Calls
Ще генерира всички "крайни времена" без номериране на редове
+-------------------------+----+------+
| endtime | | |
+-------------------------+----+------+
| 2009-01-01 00:02:35.000 | -1 | NULL |
| 2009-01-01 00:04:04.000 | -1 | NULL |
| 2009-01-01 00:04:52.000 | -1 | NULL |
| 2009-01-01 00:05:24.000 | -1 | NULL |
+-------------------------+----+------+
Сега правите UNION да има пълната C1 CTE дефиниция, ще имате смесени и двете таблици
+-------------------------+------+---------------+
| ts | TYPE | start_ordinal |
+-------------------------+------+---------------+
| 2009-01-01 00:02:10.000 | 1 | 1 |
| 2009-01-01 00:02:19.000 | 1 | 2 |
| 2009-01-01 00:02:57.000 | 1 | 3 |
| 2009-01-01 00:04:12.000 | 1 | 4 |
| 2009-01-01 00:02:35.000 | -1 | NULL |
| 2009-01-01 00:04:04.000 | -1 | NULL |
| 2009-01-01 00:04:52.000 | -1 | NULL |
| 2009-01-01 00:05:24.000 | -1 | NULL |
+-------------------------+------+---------------+
C2 се изчислява, като сортира и номерира C1 с нова колона
C2 AS
(
SELECT *,
ROW_NUMBER() OVER( ORDER BY ts, TYPE) AS start_or_end_ordinal
FROM C1
)
+-------------------------+------+-------+--------------+
| ts | TYPE | start | start_or_end |
+-------------------------+------+-------+--------------+
| 2009-01-01 00:02:10.000 | 1 | 1 | 1 |
| 2009-01-01 00:02:19.000 | 1 | 2 | 2 |
| 2009-01-01 00:02:35.000 | -1 | NULL | 3 |
| 2009-01-01 00:02:57.000 | 1 | 3 | 4 |
| 2009-01-01 00:04:04.000 | -1 | NULL | 5 |
| 2009-01-01 00:04:12.000 | 1 | 4 | 6 |
| 2009-01-01 00:04:52.000 | -1 | NULL | 7 |
| 2009-01-01 00:05:24.000 | -1 | NULL | 8 |
+-------------------------+------+-------+--------------+
И там се случва магията, по всяко време резултатът от #start - #ends е количеството съпътстващи повиквания в този момент.
за всеки Type =1 (начално събитие) имаме #start стойността в 3-та колона. и имаме също #начало + #край (в 4-та колона)
#start_or_end = #start + #end
#end = (#start_or_end - #start)
#start - #end = #start - (#start_or_end - #start)
#start - #end = 2 * #start - #start_or_end
така че в SQL:
SELECT MAX(2 * start_ordinal - start_or_end_ordinal) AS mx
FROM C2
WHERE TYPE = 1
В този случай с предложения набор от повиквания резултатът е 2.
В предложената статия има малко подобрение, за да имате групиран резултат например чрез услуга или "телефонна компания" или "телефонна централа" и тази идея може да се използва и за групиране например по времеви интервал и да има максимален едновременност час по час в даден ден.