Sqlserver
 sql >> Baza danych >  >> RDS >> Sqlserver

Znajdowanie jednoczesnych zdarzeń w bazie danych między czasami

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.



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Trzy najważniejsze trendy wpływające na administratorów baz danych odpowiedzialnych za monitorowanie SQL Server

  2. Czy istnieje funkcja Max w SQL Server, która przyjmuje dwie wartości, takie jak Math.Max ​​w .NET?

  3. Preferowana metoda przechowywania haseł w bazie danych

  4. Używanie instrukcji IF ELSE opartej na Count do wykonywania różnych instrukcji Insert

  5. Jak zezwolić, aby parametr listy rozwijanej w usługach SSRS miał domyślną wartość — Wszystkie —?