Zastrzeżenie:piszę swoją odpowiedź na podstawie (doskonałego) następującego postu:
https://www.itprotoday.com/sql-server/calculating-concurrent-sessions-part-3 (zalecane są również części 1 i 2)
Pierwszą rzeczą, którą należy zrozumieć w związku z tym problemem, jest to, że większość obecnych rozwiązań znalezionych w Internecie może mieć zasadniczo dwa problemy
- Wynik nie jest poprawną odpowiedzią (na przykład jeśli zakres A pokrywa się z B i C, ale B nie pokrywa się z C, liczą się jako 3 nakładające się zakresy).
- Sposób obliczania tego jest bardzo nieefektywny (ponieważ jest to O(n^2) i/lub cyklicznie co sekundę w okresie)
Typowy problem z wydajnością w rozwiązaniach takich jak proponowane przez Unreasons jest rozwiązaniem cuadratic, dla każdego połączenia musisz sprawdzić wszystkie inne połączenia, jeśli się pokrywają.
istnieje wspólne algorytmiczne rozwiązanie liniowe, które polega na wypisaniu wszystkich „zdarzeń” (rozmowa rozpoczęcia i zakończenia) uporządkowanych według daty i dodaniu 1 dla początku i odjęcia 1 dla rozłączenia oraz zapamiętania max. Można to łatwo zaimplementować za pomocą kursora (rozwiązanie zaproponowane przez Hafhora wydaje się właśnie takie), ale kursory nie są najskuteczniejszymi sposobami rozwiązywania problemów.
W cytowanym artykule znajdują się doskonałe przykłady, różne rozwiązania, porównanie ich wydajności. Proponowane rozwiązanie to:
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
Wyjaśnienie
załóżmy, że ten zestaw danych
+-------------------------+-------------------------+
| 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 |
+-------------------------+-------------------------+
Jest to sposób na zaimplementowanie w zapytaniu tego samego pomysłu, dodając 1 za każdy początek wywołania i odejmując 1 za każde zakończenie.
SELECT starttime AS ts, +1 AS TYPE,
ROW_NUMBER() OVER(ORDER BY starttime) AS start_ordinal
FROM Calls
ta część C1 CTE przyjmie każdy czas rozpoczęcia każdego połączenia i numer go
+-------------------------+------+---------------+
| 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 |
+-------------------------+------+---------------+
Teraz ten kod
SELECT endtime, -1, NULL
FROM Calls
Wygeneruje wszystkie „czasy końcowe” bez numerowania wierszy
+-------------------------+----+------+
| 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 |
+-------------------------+----+------+
Teraz, gdy UNION ma pełną definicję C1 CTE, obie tabele będą mieszane
+-------------------------+------+---------------+
| 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 to obliczane sortowanie i numerowanie C1 z nową kolumną
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 |
+-------------------------+------+-------+--------------+
I właśnie tam pojawia się magia, w dowolnym momencie wynikiem #start - #ends jest liczba współbieżnych połączeń w tej chwili.
dla każdego Type =1 (zdarzenie startowe) mamy wartość #start w trzeciej kolumnie. i mamy też #start + #end (w czwartej kolumnie)
#start_or_end = #start + #end
#end = (#start_or_end - #start)
#start - #end = #start - (#start_or_end - #start)
#start - #end = 2 * #start - #start_or_end
więc w SQL:
SELECT MAX(2 * start_ordinal - start_or_end_ordinal) AS mx
FROM C2
WHERE TYPE = 1
W tym przypadku z proponowanym zestawem wywołań wynik to 2.
W proponowanym artykule jest niewielka poprawa polegająca na grupowaniu wyniku na przykład według usługi lub „firmy telefonicznej” lub „centrali telefonicznej” i ten pomysł można również wykorzystać do grupowania na przykład według przedziałów czasowych i mieć maksymalną współbieżność godzina po godzinie w danym dniu.