Sqlserver
 sql >> Datenbank >  >> RDS >> Sqlserver

Finden gleichzeitiger Ereignisse in einer Datenbank zwischen den Zeiten

Haftungsausschluss:Ich schreibe meine Antwort basierend auf dem (ausgezeichneten) folgenden Beitrag:

https://www.itprotoday.com/sql-server/calculating-concurrent-sessions-part-3 (Teil 1 und 2 werden ebenfalls empfohlen)

Das erste, was hier bei diesem Problem zu verstehen ist, ist, dass die meisten der aktuellen Lösungen, die im Internet gefunden werden, im Wesentlichen zwei Probleme haben können

  • Das Ergebnis ist nicht die richtige Antwort (z. B. wenn sich Bereich A mit B und C überschneidet, aber B nicht mit C überschneidet, zählen sie als 3 sich überschneidende Bereiche).
  • Die Art der Berechnung ist sehr ineffizient (weil es O(n^2) ist und / oder sie für jede Sekunde in der Periode kreisen)

Das häufige Leistungsproblem bei Lösungen wie der von Unreasons vorgeschlagenen ist eine cuadratische Lösung, bei der Sie für jeden Aufruf alle anderen Aufrufe auf Überschneidungen überprüfen müssen.

Es gibt eine algorithmische lineare gemeinsame Lösung, die alle "Ereignisse" (Anruf beginnen und Anruf beenden) nach Datum geordnet auflistet, 1 für einen Start addiert und 1 für ein Auflegen subtrahiert und sich das Maximum merkt. Das kann leicht mit einem Cursor implementiert werden (die von Hafhor vorgeschlagene Lösung scheint so zu sein), aber Cursor sind nicht die effizienteste Art, Probleme zu lösen.

Der Artikel, auf den verwiesen wird, enthält hervorragende Beispiele, verschiedene Lösungen und einen Leistungsvergleich. Die vorgeschlagene Lösung ist:

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

Erklärung

nehmen wir diesen Datensatz an

+-------------------------+-------------------------+
|        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 |
+-------------------------+-------------------------+

Dies ist eine Möglichkeit, dieselbe Idee mit einer Abfrage zu implementieren, indem für jeden Beginn eines Anrufs 1 addiert und für jedes Ende 1 subtrahiert wird.

  SELECT starttime AS ts, +1 AS TYPE,
    ROW_NUMBER() OVER(ORDER BY starttime) AS start_ordinal
  FROM Calls

dieser Teil des C1 CTE nimmt jede Startzeit jedes Anrufs und nummeriert sie

+-------------------------+------+---------------+
|           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 |
+-------------------------+------+---------------+

Jetzt dieser Code

  SELECT endtime, -1, NULL
  FROM Calls

Generiert alle "Endzeiten" ohne Zeilennummerierung

+-------------------------+----+------+
|         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 |
+-------------------------+----+------+

Wenn Sie jetzt die UNION so gestalten, dass sie die vollständige C1-CTE-Definition enthält, haben Sie beide Tabellen gemischt

+-------------------------+------+---------------+
|           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 wird berechnet, indem C1 mit einer neuen Spalte sortiert und nummeriert wird

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 |
+-------------------------+------+-------+--------------+

Und hier passiert die Magie, das Ergebnis von #start - #ends ist zu jeder Zeit die Anzahl gleichzeitiger Aufrufe in diesem Moment.

für jeden Typ =1 (Startereignis) haben wir den #start-Wert in der 3. Spalte. und wir haben auch das #start + #end (in der 4. Spalte)

#start_or_end = #start + #end

#end = (#start_or_end - #start)

#start - #end = #start - (#start_or_end - #start)

#start - #end = 2 * #start - #start_or_end

also in SQL:

SELECT MAX(2 * start_ordinal - start_or_end_ordinal) AS mx
FROM C2
WHERE TYPE = 1

In diesem Fall mit der vorgeschlagenen Menge von Aufrufen ist das Ergebnis 2.

In dem vorgeschlagenen Artikel gibt es eine kleine Verbesserung, um ein gruppiertes Ergebnis beispielsweise nach einem Dienst oder einer „Telefongesellschaft“ oder einer „Telefonzentrale“ zu haben, und diese Idee kann auch verwendet werden, um beispielsweise nach Zeitschlitz zu gruppieren und die maximale Gleichzeitigkeit zu haben Stunde für Stunde an einem bestimmten Tag.