sql >> Databáze >  >> RDS >> Sqlserver

Hledání simultánních událostí v databázi mezi časy

Prohlášení:Píšu svou odpověď na základě (výborného) následujícího příspěvku:

https://www.itprotoday.com/sql-server/calculating-concurrent-sessions-part-3 (doporučuje se také část 1 a 2)

První věc, kterou je třeba v tomto problému pochopit, je, že většina současných řešení nalezených na internetu může mít v zásadě dva problémy

  • Výsledkem není správná odpověď (pokud se například rozsah A překrývá s B a C, ale B se nepřekrývá s C, počítají se jako 3 překrývající se rozsahy).
  • Způsob výpočtu je velmi neefektivní (protože je O(n^2) a / nebo kolísají každou sekundu v období)

Běžný problém s výkonem v řešeních, jako jsou navrhovaná Unreasons, je kvadratické řešení, u každého hovoru musíte zkontrolovat všechny ostatní hovory, zda se nepřekrývají.

existuje algoritmické lineární společné řešení, které je seznam všech "událostí" (zahájení hovoru a ukončení hovoru) seřazené podle data a přičtení 1 pro začátek a odečtení 1 pro zavěšení a zapamatujte si max. To lze snadno implementovat pomocí kurzoru (řešení navržené Hafhorem se zdá být tímto způsobem), ale kurzory nejsou nejefektivnějším způsobem řešení problémů.

Odkazovaný článek má vynikající příklady, různá řešení a jejich srovnání výkonu. Navrhované řešení je:

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

Vysvětlení

předpokládejme, že tato sada dat

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

Toto je způsob, jak s dotazem implementovat stejný nápad, přidáním 1 pro každý začátek hovoru a odečtením 1 pro každý konec.

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

tato část C1 CTE vezme každý čas zahájení každého hovoru a očísluje jej

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

Nyní tento kód

  SELECT endtime, -1, NULL
  FROM Calls

Vygeneruje všechny „časy ukončení“ bez číslování řádků

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

Nyní vytvoříte UNION tak, aby měl plnou definici C1 CTE, budete mít obě tabulky smíšené

+-------------------------+------+---------------+
|           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 je vypočtené řazení a číslování C1 s novým sloupcem

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

A tam je to kouzlo, v kterémkoli okamžiku je výsledkem #start - #ends počet souběžných hovorů v tuto chvíli.

pro každý Typ =1 (událost start) máme ve 3. sloupci hodnotu #start. a máme také #start + #end (ve 4. sloupci)

#start_or_end = #start + #end

#end = (#start_or_end - #start)

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

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

takže v SQL:

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

V tomto případě s navrženou sadou volání je výsledkem 2.

V navrhovaném článku je malé vylepšení mít seskupený výsledek například službou nebo "telefonní společností" nebo "telefonní centrálou" a tuto myšlenku lze také použít k seskupení například podle časového úseku a dosáhnout maximální souběžnosti hodinu po hodině v daném dni.



  1. Základy tabulkových výrazů, část 9 – Pohledy, srovnání s odvozenými tabulkami a CTE

  2. Funkce TO_YMINTERVAL() v Oracle

  3. Jak dám každému registrovanému uživateli jeho vlastní URL pomocí PHP?

  4. Regulární výrazy Oracle. Nebezpečný rozsah