sql >> Databasteknik >  >> RDS >> Sqlserver

Hitta samtidiga händelser i en databas mellan tider

Ansvarsfriskrivning:Jag skriver mitt svar baserat på följande (utmärkta) inlägg:

https://www.itprotoday.com/sql-server/calculating-concurrent-sessions-part-3 (Del 1 och 2 rekommenderas också)

Det första att förstå här med det problemet är att de flesta av de nuvarande lösningarna som finns på internet i princip kan ha två problem

  • Resultatet är inte det korrekta svaret (till exempel om intervall A överlappar med B och C men B inte överlappar C räknas de som tre överlappande intervall).
  • Sättet att beräkna det är väldigt ineffektivt (eftersom är O(n^2) och/eller de cirklar för varje sekund i perioden)

Det vanliga prestandaproblemet i lösningar som Unreasons föreslår är en cuadratisk lösning, för varje samtal måste du kontrollera alla andra samtal om de överlappar varandra.

det finns en algoritmisk linjär gemensam lösning som är att lista alla "händelser" (startsamtal och avsluta samtal) sorterade efter datum, och lägg till 1 för en start och subtrahera 1 för ett avbrott, och kom ihåg max. Det kan enkelt implementeras med en markör (lösning föreslagen av Hafhor verkar vara på det sättet) men markörer är inte de mest effektiva sätten att lösa problem.

Den refererade artikeln har utmärkta exempel, olika lösningar, prestandajämförelse av dem. Den föreslagna lösningen är:

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

Förklaring

anta att denna uppsättning data

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

Detta är ett sätt att med en fråga implementera samma idé, lägga till 1 för varje start av ett samtal och subtrahera 1 för varje slut.

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

denna del av C1 CTE kommer att ta varje starttid för varje samtal och numrera det

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

Nu den här koden

  SELECT endtime, -1, NULL
  FROM Calls

Kommer att generera alla "sluttider" utan radnumrering

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

Om du nu gör att UNION har den fullständiga C1 CTE-definitionen kommer du att ha båda tabellerna blandade

+-------------------------+------+---------------+
|           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 är beräknad sortering och numrering C1 med en ny 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 |
+-------------------------+------+-------+--------------+

Och det är där magin uppstår, när som helst resultatet av #start - #ends är mängden samtidiga samtal för närvarande.

för varje Typ =1 (starthändelse) har vi #startvärdet i den tredje kolumnen. och vi har också #start + #end (i den fjärde kolumnen)

#start_or_end = #start + #end

#end = (#start_or_end - #start)

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

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

så i SQL:

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

I det här fallet med den föreslagna uppsättningen samtal är resultatet 2.

I den föreslagna artikeln finns det en liten förbättring att ha ett grupperat resultat av till exempel en tjänst eller ett "telefonbolag" eller "telefoncentral" och denna idé kan också användas för att gruppera till exempel efter tidslucka och ha maximal samtidighet timme för timme under en viss dag.



  1. Hur man får gårdagens datum i SQLite

  2. Vanligt tabelluttryck i MySQL

  3. Tillåter oracle alternativet för oengagerad läsning?

  4. Hur man konverterar DateTime till VarChar