Dichiarazione di non responsabilità:sto scrivendo la mia risposta in base al seguente post (eccellente):
https://www.itprotoday.com/sql-server/calculating-concurrent-sessions-part-3 (si consigliano anche le parti 1 e 2)
La prima cosa da capire qui con questo problema è che la maggior parte delle attuali soluzioni trovate su Internet possono avere fondamentalmente due problemi
- Il risultato non è la risposta corretta (ad esempio se l'intervallo A si sovrappone a B e C ma B non si sovrappone a C, contano come 3 intervalli sovrapposti).
- Il modo per calcolarlo è molto inefficiente (perché è O(n^2) e/o si alternano per ogni secondo nel periodo)
Il problema di prestazioni comune in soluzioni come quella proposta da Unreasons è una soluzione cuadratica, per ogni chiamata è necessario controllare tutte le altre chiamate se sono sovrapposte.
esiste una soluzione algoritmica lineare comune che è elencare tutti gli "eventi" (inizio chiamata e fine chiamata) ordinati per data, e aggiungere 1 per un inizio e sottrarre 1 per un riaggancio, e ricordare il max. Questo può essere implementato facilmente con un cursore (la soluzione proposta da Hafhor sembra essere in questo modo) ma i cursori non sono i modi più efficienti per risolvere i problemi.
L'articolo di riferimento ha esempi eccellenti, soluzioni diverse, confronto delle prestazioni di loro. La soluzione proposta è:
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
Spiegazione
supponiamo questo insieme di dati
+-------------------------+-------------------------+
| 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 |
+-------------------------+-------------------------+
Questo è un modo per implementare con una query la stessa idea, aggiungendo 1 per ogni inizio di una chiamata e sottraendo 1 per ogni fine.
SELECT starttime AS ts, +1 AS TYPE,
ROW_NUMBER() OVER(ORDER BY starttime) AS start_ordinal
FROM Calls
questa parte del C1 CTE prenderà ogni ora di inizio di ogni chiamata e la numererà
+-------------------------+------+---------------+
| 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 |
+-------------------------+------+---------------+
Ora questo codice
SELECT endtime, -1, NULL
FROM Calls
Genererà tutti gli "endtime" senza numerazione delle righe
+-------------------------+----+------+
| 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 |
+-------------------------+----+------+
Ora facendo in modo che UNION abbia la definizione CTE completa C1, avrai entrambe le tabelle miste
+-------------------------+------+---------------+
| 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 è calcolato ordinando e numerando C1 con una nuova colonna
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 |
+-------------------------+------+-------+--------------+
Ed è lì che si verifica la magia, in qualsiasi momento il risultato di #inizio - #fine è la quantità di chiamate simultanee in questo momento.
per ogni Tipo =1 (evento di inizio) abbiamo il valore #start nella 3a colonna. e abbiamo anche #inizio + #fine (nella 4a colonna)
#start_or_end = #start + #end
#end = (#start_or_end - #start)
#start - #end = #start - (#start_or_end - #start)
#start - #end = 2 * #start - #start_or_end
quindi in SQL:
SELECT MAX(2 * start_ordinal - start_or_end_ordinal) AS mx
FROM C2
WHERE TYPE = 1
In questo caso con l'insieme di chiamate proposto, il risultato è 2.
Nell'articolo proposto, c'è un piccolo miglioramento per avere un risultato raggruppato ad esempio per un servizio o una "compagnia telefonica" o "centrale telefonica" e questa idea può essere utilizzata anche per raggruppare ad esempio per fascia oraria e avere la massima concorrenza ora per ora in un determinato giorno.