sql >> Database teknologi >  >> RDS >> Sqlserver

Finde samtidige hændelser i en database mellem tidspunkter

Ansvarsfraskrivelse:Jeg skriver mit svar baseret på følgende (udmærkede) indlæg:

https://www.itprotoday.com/sql-server/calculating-concurrent-sessions-part-3 (Del 1 og 2 anbefales også)

Den første ting at forstå her med det problem er, at de fleste af de nuværende løsninger, der findes på internettet, grundlæggende kan have to problemer

  • Resultatet er ikke det rigtige svar (f.eks. hvis område A overlapper med B og C, men B ikke overlapper C, tæller de som 3 overlappende områder).
  • Måden at beregne det på er meget ueffektiv (fordi er O(n^2) og/eller de cykler for hvert sekund i perioden)

Det almindelige præstationsproblem i løsninger som foreslået af Unreasons er en cuadratisk løsning, for hvert opkald skal du tjekke alle de andre opkald, hvis de er overlappet.

der er en algoritmisk lineær fælles løsning, som er at liste alle "begivenheder" (start opkald og afslutning af opkald) sorteret efter dato, og læg 1 til for en start og træk 1 fra for et læg på, og husk max. Det kan nemt implementeres med en markør (løsning foreslået af Hafhor ser ud til at være på den måde), men markører er ikke de mest effektive måder at løse problemer på.

Den refererede artikel har fremragende eksempler, forskellige løsninger, ydelsessammenligning af dem. Den foreslåede løsning er:

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
 

Forklaring

antag dette sæt 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 | +-------------------------+-------------------------+

Dette er en måde at implementere den samme idé med en forespørgsel ved at tilføje 1 for hver start af et opkald og trække 1 fra for hver afslutning.

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

denne del af C1 CTE vil tage hvert starttidspunkt for hvert opkald og nummerere 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 denne kode

  SELECT endtime, -1, NULL
  FROM Calls
 

Vil generere alle "sluttider" uden rækkenummerering

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

Når du nu gør UNION til at have den fulde C1 CTE-definition, vil du have begge tabeller blandet

+-------------------------+------+---------------+ | 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 er beregnet sortering og nummerering C1 med en ny kolonne

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

Og det er dér, hvor magien opstår, når som helst resultatet af #start - #ends er mængden af ​​samtidige opkald i dette øjeblik.

for hver Type =1 (starthændelse) har vi #startværdien i 3. kolonne. og vi har også #start + #end (i 4. kolonne)

#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 dette tilfælde med det foreslåede sæt af opkald er resultatet 2.

I den foreslåede artikel er der en lille forbedring for at have et grupperet resultat efter f.eks. en tjeneste eller et "telefonselskab" eller "telefoncentral", og denne idé kan også bruges til at gruppere f.eks. efter tidsrum og have den maksimale samtidighed time for time på en given dag.



  1. Timeout-indstilling for SQL Server

  2. Opsætning af Django og PostgreSQL på to forskellige EC2-instanser

  3. Hvad er Multi Dimension OLAP CUBE og giv et eksempel på en terning med mere end 3 dimensioner

  4. Sådan skriver du komplekse forespørgsler i SQL