Sqlserver
 sql >> Base de données >  >> RDS >> Sqlserver

Recherche d'événements simultanés dans une base de données entre les heures

Avis de non-responsabilité :j'écris ma réponse en me basant sur le (excellent) message suivant :

https://www.itprotoday.com/sql-server/calculating-concurrent-sessions-part-3 (les parties 1 et 2 sont également recommandées)

La première chose à comprendre ici avec ce problème est que la plupart des solutions actuelles trouvées sur Internet peuvent avoir essentiellement deux problèmes

  • Le résultat n'est pas la bonne réponse (par exemple, si la plage A chevauche B et C mais que B ne chevauche pas C, elles comptent comme 3 plages qui se chevauchent).
  • La façon de le calculer est très inefficace (car est O(n^2) et/ou ils tournent pour chaque seconde de la période)

Le problème de performance courant dans des solutions comme celle proposée par Unreasons est une solution cuadratique, pour chaque appel, vous devez vérifier tous les autres appels s'ils se chevauchent.

il existe une solution commune linéaire algorithmique qui liste tous les "événements" (appel de début et appel de fin) classés par date, et ajoute 1 pour un début et soustrait 1 pour un raccrochage, et rappelle le max. Cela peut être mis en œuvre facilement avec un curseur (la solution proposée par Hafhor semble être dans ce sens) mais les curseurs ne sont pas les moyens les plus efficaces pour résoudre les problèmes.

L'article référencé contient d'excellents exemples, différentes solutions, une comparaison de leurs performances. La solution proposée est :

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

Explication

supposons que cet ensemble de données

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

C'est une façon d'implémenter avec une requête la même idée, en ajoutant 1 pour chaque début d'appel et en soustrayant 1 pour chaque fin.

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

cette partie du C1 CTE prendra chaque heure de début de chaque appel et la numérotera

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

Maintenant ce code

  SELECT endtime, -1, NULL
  FROM Calls

Génèrera toutes les "heures de fin" sans numérotation des lignes

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

Maintenant, en faisant en sorte que l'UNION ait la définition C1 CTE complète, vous aurez les deux tables mélangées

+-------------------------+------+---------------+
|           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 est calculé en triant et en numérotant C1 avec une nouvelle colonne

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

Et c'est là que la magie opère, à tout moment le résultat de #start - #ends est le nombre d'appels simultanés à ce moment.

pour chaque Type =1 (événement de début), nous avons la valeur #start dans la 3ème colonne. et nous avons aussi le #start + #end (dans la 4ème colonne)

#start_or_end = #start + #end

#end = (#start_or_end - #start)

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

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

donc en SQL :

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

Dans ce cas, avec l'ensemble d'appels proposé, le résultat est 2.

Dans l'article proposé, il y a une petite amélioration pour avoir un résultat groupé par exemple par un service ou une "compagnie de téléphone" ou une "centrale téléphonique" et cette idée peut aussi être utilisée pour regrouper par exemple par plage horaire et avoir le maximum de simultanéité heure par heure dans une journée donnée.