V řešených příkladech jsou použitá otevřená datová sada COVID-19: Celkový (kumulativní) počet osob s prokázanou nákazou dle krajských hygienických stanic včetně laboratoří, počet vyléčených, počet úmrtí a provedených testů (v2) poskytovaná Ministerstvem Zdravotnictví ČR k datu 2022-02-22. 「přehled datových sad」「lokální kopie」
Datový soubor byl nahrán do tabulky kryl.covid_daily v databázi Oracle, s kterou se pracuje na cvičení DB2. Strukturu tabulky lze zjistit příkazem DESC. Kumulativní datové atributy byly při nahrávání odstraněny. K tabulce bylo přidáno oprávnění GRANT SELECT TO public, tedy je čitelná pro každého uživatele.
DESC covid_daily
;
Name Null Type
--------------------- -------- ----------
DATUM NOT NULL DATE
POCET_NAKAZENYCH NUMBER(10)
POCET_VYLECENYCH NUMBER(10)
POCET_UMRTI NUMBER(10)
PCR_TESTU NUMBER(10)
AG_TESTU NUMBER(10)
První náhled na dataset
Pro elementární prozkoumání datasetu lze využít agregační funkce, nejčastěji COUNT(), MIN(), MAX(). Agregační funkce se aplikují na všechny záznamy, případně na podmnožiny rozlišené dle atributů v GROUP BY části dotazu, je-li uvedena. Hvězdička v argumentu COUNT(*) je specialita pro spočtení všech záznamů, častěji bývá jako argument použit název atributu nebo výpočet.
SELECT COUNT(*)
FROM covid_daily
;
COUNT(*)
----------
757
Pro zjištění extrémních hodnot v konkrétním sloupci tabulky lze použít funkce MAX a MIN s příslušným názvem sloupce. V argumentu lze kromě jednoho atributu použít i výpočet využívající data z více atributů. Pokud nás tedy zajímá maximální hodnota provedených testů bez jejich rozlišení na AG a PCR, lze formulovat požadavek jako MAX(ag_testu + pcr_testu).
Výsledek by byl odlišný, kdyby se sčítala maxima AG a PCR testů, jak je naznačeno v sum2 selectu. Sum1 vyjadřuje maximální denní počet testů, kdežto Sum2 sečte dohromady maxima z různých dnů.
SELECT
MAX(ag_testu) AS ag,
MAX(pcr_testu) AS pcr,
MAX(ag_testu + pcr_testu) AS sum1,
MAX(ag_testu) + MAX(pcr_testu) AS sum2
FROM covid_daily
;
AG PCR SUM1 SUM2
---------- ---------- ---------- ----------
390651 156558 416331 547209
Identifikace záznamu s extrémní hodnotou
Pomocí agregačních funkcí je sice možné najít konkrétní extrémní hodnotu, ale již není triviální najít záznam, ve kterém se extrémní hodnota nacházela. Pro řešení takové otázky se nabízí dva přístupy – dohledat zpětně hodnotu z výsledku selectu; místo agregační funkce vhodně použít řazení a omezení počtu záznamů ve výsledku.
K omezení počtu vrácených řádek je v Oracle speciální syntaxe FETCH NEXT x ROWS ONLY [SQL:2008] fungující jako ekvivalent klíčového slova LIMIT x [nestandardní], které se běžně vyskytuje v PostgreSQL, SQLite a jiných nejen relačních databázích. K seřazení výsledku se používá klíčové slovo ORDER BY za nímž následuje seznam atributů, podle kterých řadit. V ukázce je vidět 5 dnů s nejvyšším počtem spotřebovaných AG testů.
SELECT
datum,
pcr_testu,
ag_testu
FROM covid_daily
ORDER BY ag_testu DESC
FETCH NEXT 5 ROWS ONLY
;
DATUM PCR_TESTU AG_TESTU
--------- ---------- ----------
06-APR-21 25680 390651
17-MAY-21 36490 362986
24-MAY-21 30291 335988
10-MAY-21 22225 335511
03-MAY-21 19691 329840
K druhému přístupu je potřeba vhodně použít výsledek poddotazu na maximální hodnotu, která má být následně nalezena a napárována. Pokud poddotaz vrací přesně jednu hodnotu, lze celý subdotaz použít jakou součást výrazu ve WHERE klauzuli. V tomto případě je použit operátor shodnosti.
Pokud poddotaz vrací více než jednu hodnotu (více sloupců, řádků nebo obojí) lze ho používat jako virtuální tabulku a pomocí JOIN ji napárovat na ostatní relevantní tabulky. V tomto případě je párována na původní covid_daily tabulku a to tak, aby se spočtená maximální hodnota nacházela v atributu ag_testu původní tabulky.
Nevýhoda tohoto přístupu oproti řazení a omezení počtu záznamů, je v nejistotě, kolik záznamů bude výsledkem dotazu. Uvažte situaci, kdy místo maxima je hledáno minimum. Takových dnů, kdy se neprovedl žádný AG test, je v datasetu 280. V předchozím případě lze triviálně zvolit, že v případě několika dnů s extrémní hodnotou bude vybrán například ten nejstarší (díky explicitnímu řazení). ORDER BY klauzuli a omezení počtu řádků lze samozřejmě použít i zde, ale pak je zřejmé, že dotaz je zbytečně komplikovaný.
SELECT
datum,
pcr_testu,
ag_testu
FROM covid_daily
WHERE AG_TESTU = (
SELECT MAX(ag_testu) FROM covid_daily
)
;
SELECT
datum,
pcr_testu,
ag_testu
FROM covid_daily d1 INNER JOIN
(
SELECT MAX(ag_testu) AS ag
FROM covid_daily
) d2 ON d1.ag_testu = d2.ag
;
DATUM PCR_TESTU AG_TESTU
--------- ---------- ----------
06-APR-21 25680 390651
Seskupování záznamů dle data
K použití agregátních funkcí na určité podmnožiny datasetu slouží GROUP BY část příkazu. Za klíčovým slovem následuje posloupnost atributů či výrazů, podle jejiž hodnot budou záznamy rozčleněny do skupin.
V řešeném příkladu bude hledán průměrný počet nově nakažených za celý měsíc, aby byly eliminovány denní výkyvy a byl poskytnut přehled o delších časových úsecích. Oracle má specifickou syntax pro získání měsíce z datetime hodnoty. EXTRACT(MONTH FROM datum) zpracuje hodnotu v atributu datum a vrátí číslo 1-12 příslušného měsíce. Podobně lze pomocí klíčových slov YEAR, DAY, HOUR, MINUTE, SECOND vrátit i jiné složky data.
Do projekce v dotazu je možné při použití GROUP BY umístit pouze výrazy, podle kterých se záznamy seskupují, a výrazy, které umožní z množiny záznamů spočíst právě jednu hodnotu (typicky agregátní funkce). V příkladu je použita funkce AVG() vracející průměr z příslušných hodnot. Funkce ROUND() spočtenou hodnotu zaokrouhlí na zvolený počet desetinných míst pro lepší čitelnost.
Za zmínku stojí speciální výraz ORDER BY 3, kterým je dán pokyn pro seřazení výsledku dle třetího sloupce. Tímto způsobem se tedy lze vyhnout opakováním zdlouhavého výrazu.
SELECT
EXTRACT(YEAR FROM datum) AS year,
EXTRACT(MONTH FROM datum) AS month,
ROUND(AVG(pocet_nakazenych),0) AS avg_pocet
FROM covid_daily
GROUP BY EXTRACT(YEAR FROM datum),
EXTRACT(MONTH FROM datum)
ORDER BY 3 DESC
;
YEAR MONTH AVG_POCET
---------- ---------- ----------
2022 2 22823
2022 1 18093
2021 11 13545
2021 12 9933
2021 3 9696
... ... ...
Výpočet přírůstku v čase
Častou úlohou pojící se s časovými řadami bývá určení rozdílu dvou hodnot v následujících obdobích k identifikaci trendu. V SQL lze tohoto docílit v podstatě dvěma způsoby.
První možností je pomocí JOIN spojit datovou tabulku samu se sebou a využít spojující klauzuli k provedení zamýšleného časového posunu. Asi nejčastěji je při spojování k vidění kód a JOIN b ON a.fk_id = b.id pro prosté spojení dle cizího klíče a příslušného primárního klíče v druhé tabulce. Nicméně v spojení je možné použít jednak jiné operátory než shodnost, jednak i složitější výrazy namísto prostého názvu atributu. V příkladu je dosaženo spojení se záznamem přesně jeden den starým pomocí odečtení jednoho dne od datumu.
Alternativou je využití Window funkcí [SQL:2003], což jsou analytické funkce pracující s klíčovým slovem OVER v projekční části dotazu. Za tímto slovem následuje definice pro virtuální seřazení, či seskupení používaného datasetu ve zbytku dotazu. Samotnou použitou funkcí mohou být buď klasické agregační funkce (viz dále) nebo speciální funkce pracující pouze s klíčovým slovem OVER.
V řešeném příkladu lze hodnotu z předcházejícího řádku získat pomocí volání LAG(ag_testu, 1) OVER (ORDER BY datum), které vybere hodnotu ag_testu, která je posunuta o jeden řádek vůči současnému řádku při řazení dle datumu. Za zmínku stojí, že v tomto případě není nutné, aby celý dotaz byl řazen podle datumu. Dokonce je možné, aby řazení celého dotazu bylo jiné, než řazení uvnitř window funkce.
SELECT
c1.datum,
c1.ag_testu AS ag,
c2.ag_testu AS ag_prev,
c1.ag_testu - c2.ag_testu AS diff
FROM covid_daily c1 LEFT JOIN covid_daily c2
ON c1.datum -1 = c2.datum
;
SELECT
datum,
ag_testu AS ag,
LAG(ag_testu, 1) OVER
(ORDER BY datum) AS ag_prev,
ag_testu - LAG(ag_testu, 1) OVER
(ORDER BY datum) AS diff
FROM covid_daily
;
DATUM AG AG_PREV DIFF
--------- ---------- ---------- ----------
... ... ... ...
02-NOV-20 188 0 188
03-NOV-20 302 188 114
04-NOV-20 633 302 331
05-NOV-20 839 633 206
06-NOV-20 563 839 -276
... ... ... ...
Výpočet klouzavého průměru
Při práci s daty často bývá problém se zohledněním výkyvů v důsledku krátkých cyklů – např. doprava je hustější v ranní a odpolední špičce, či v tomto případě se o víkendech méně testuje než ve všední dny. Jedna ze strategií k potlačení tohoto je sledované hodnoty zprůměrovat za určité vhodně zvolené období definované jako okolí současné zpracovávané hodnoty.
Vytvoření sedmidenního klouzavého průměru by v SQL bez window funkce bylo poměrně krkolomné. Například by bylo možné spojit tabulku 6x se sebou s různým odpočtem dnů podobně jako v předchozím příkladu, a následně sečíst sledované hodnoty z těchto tabulek a vydělit sedmi.
Použití window funkce pro vyčíslení 7denního průměru počtu nakažených je v ukázce. Oproti předchozímu příkladu je zde použita syntaxe ROWS BETWEEN 3 PRECEDING AND 3 FOLLOWING určující, že ve výpočtu mají být zohledněny vždy 3 předcházející, současný a 3 následující záznamy při řazení dle datumu.
Alternativou by bylo nevytvářet okno -3 až +3, ale -6 až současná řádka. To by mělo za výhodu, že i pro poslední záznam by bylo možné stanovit hodnotu klouzavého průměru a nebylo by potřeba čekat další 3 dny. Této variantě by odpovídala syntax BETWEEN 6 PRECEDING AND CURRENT ROW nebo též zkratkou jen jako 6 PRECEDING.
V definici intervalu window funkce lze namísto konkrétní hodnoty počtu řádek uvést klíčové slovo UNBOUNDED znamenající "až do začátku/konce". To lze elegantně využít k výpočtu kumulativního součtu atributu, např. počtu nakažených od začátku evidence. Nevím, jak jinak by šlo v SQL bez tohoto kumulativní součet získat.
K procvičení: vytvořte dotaz, který vypočte, ve kterém dni došlo k překročení milníku 1M potvrzených nákaz.
SELECT
datum,
pocet_nakazenych,
ROUND(AVG(pocet_nakazenych) OVER
(
ORDER BY datum
ROWS BETWEEN 3 PRECEDING AND 3 FOLLOWING
), 1) AS "7D_AVG"
FROM covid_daily
ORDER BY datum
;
DATUM POCET_NAKAZ 7D_AVG
--------- ----------- ----------
... 0 ...
29-FEB-20 0 .7
01-MAR-20 3 .9
02-MAR-20 0 1.3
03-MAR-20 2 2.9
04-MAR-20 1 3.9
05-MAR-20 3 4.3
06-MAR-20 11 5.1
Využití pořadí záznamu v podmnožině
Jednou z užitečných funkcí je RANK(), který vrátí pořadí dané řádky při využití příslušného členění a řazení. V předchozím příkladu byla hledána extrémní hodnota počtu testů. S využitím následující techniky by bylo možné řešit i obecnější otázky jako např. v jakých dnech byla extrémní hodnota v jednotlivých letech.
V ukázce je použit blok OVER(PARTITION BY EXTRACT(year FROM datum) ORDER BY pocet_nakazenych DESC). Dochází tedy k sestavení virtuální tabulky (neboli okna) členěné podle roku a řazené sestupně dle počtu nakažených v daný den. RANK(), jak již bylo zmíněno, vrátí hodnotu 1 - 356 vyjadřující, na kolikátém místě se v daný rok tento počet nakažených umístil.
Jelikož není způsob jak výsledek window funkce použít ve filtraci příslušného dotazu, je tento dotaz obalen pomocným SELECT *, jehož jediný úkol je vybrat řádky, jejichž umístěni odpovídalo hodnotě 1, tedy maximu daného roku.
Tato otázka by šla opět řešit pomocí GROUP BY a zpětného napárování výsledku dotazu.
K procvičení: vytvořte alternativní dotaz bez použití window funkcí, který vyřeší tuto otázku. Je možné bez window funkcí najít pro každý rok 5 dnů s největšími přírůstky? Lze bez window funkce najít v každém roce dny s 10. nejvyšším přírůstkem?
SELECT *
FROM(
SELECT
datum,
pocet_nakazenych,
RANK() OVER
(
PARTITION BY EXTRACT(year FROM datum)
ORDER BY pocet_nakazenych DESC
) AS rank
FROM covid_daily
)
WHERE rank = 1
;
DATUM POCET_NAKAZ RANK
--------- ----------- ----------
30-DEC-20 17124 1
25-NOV-21 27937 1
01-FEB-22 57235 1
Organizace zanořených dotazů
Při tvorbě složitějších analytických dotazů často dochází k tvorbě poddotazů do několika hierarchických úrovní. Takový dotaz může být obtížné přečíst a pochopit, co vlastně koná. Podobně jako v programování lze místo one-lineru mezivýsledek uložit do proměnné a pokračovat později, i SQL má techniku umožňující něco podobného.
Common Table Expressions (CTE) je v podstatě pojmenovaný dočasný result set, s kterým je možné v rámci dotazu pracovat jako s tabulkou. S touto technikou se lze setkat u hierarchických a rekurzivních dotazů, nicméně i pro v tomto ohledu obyčejný dotaz lze CTE uplatnit.
V příkladu je použit dotaz na výpočet plovoucího 7denního průměru. Pro přehlednost byl pomocí WITH avgs AS vytvořen CTE pojmenovaný jako "avgs" s tímto výpočtem. V následném dotazu, který hledá 5 dnů s největším mezidenním přírůstkem, je CTE odkazováno jako FROM avgs a s výsledkem poddotazu je pracováno jako s klasickou tabulkou.
Samotné vyhodnocení nejvyšších přírůstků využívá koncepty představné již dříve. Za zmínku stojí rozšířené volání LAG("7d_avg", 1, 0), které vrátí hodnotu na předchízejícím řádku a pokud takový řádek nebude existovat, vrátí defaultní hodnotu 0. Tímto způsobem se odstraní problém s NULL hodnotami u prvních dnů v datasetu.
Alternativou k CTE by mohlo být založit databázový pohled obsahující poddotaz nebo dočasnou tabulku obsahující výsledek poddotazu. Nevýhodou těchto řešení oproti CTE je vzniknutí nového objektu v databázi a nutnost provést více příkazů (CTE je součástí dotazu).
WITH avgs AS (
SELECT
datum,
pocet_nakazenych,
ROUND(AVG(pocet_nakazenych) OVER
(
ORDER BY datum
ROWS BETWEEN 3 PRECEDING AND 3 FOLLOWING
), 0) AS "7d_avg"
FROM covid_daily
ORDER BY datum
)
SELECT
datum,
"7d_avg",
LAG("7d_avg", 1, 0)
OVER(ORDER BY datum) AS "7d_avg_prev",
"7d_avg" - LAG("7d_avg", 1, 0)
OVER(ORDER BY datum) AS diff
FROM avgs
ORDER BY diff DESC
FETCH NEXT 5 ROWS ONLY
;
DATUM 7D_AVG 7D_AVG_PREV DIFF
--------- ---------- ----------- ----------
23-JAN-22 29935 25838 4097
25-JAN-22 34010 31060 2950
29-JAN-22 37971 35457 2514
15-JAN-22 14534 12230 2304
17-JAN-22 18791 16613 2178
Komplexní příklad #1
Otázka: kolik bylo v jednotlivých měsících růstových, resp. klesajících dnů. Růstovým dnem se rozumí situace, kdy se 7denní průměr v daném dnu zvýšil oproti stejnému dni v předcházejícím týdnu.
Analýza: z otázky přímo vyplývají dva koncepty, které se v řešení budou vyskytovat. Prvním z nich je kalkulace 7denního klouzavého průměru, což již bylo řešeno dříve pomocí window funkce za použití definice ROWS BETWEEN a obalení příslušnou aggregační funkcí. Druhým konceptem je srovnávání hodnoty oproti hodnotě stejného významu v časově předchozím záznamu. To bylo v textu výše demonstrováno pomocí window funkce LAG, případně vhodným použitím JOIN.
Ze zadání dále lze vyvodit požadavek na označení řádku dvěma různými popisky - růstový, či klesající. V zadání není explicitně řečeno, jak se zachovat u stagnujícího stavu, např. na začátku sledovaného období, kdy denní přírůstky byly nulové. V praxi to znamená, že bude potřeba spočtenou hodnotu rozdílu převést na hodnotu indikující sledovaný stav. K aplikaci této podmínečné logiky lze použít v projekci výraz CASE WHEN ... THEN ....
Za zvážení stojí 2 přístupy - vytvořit jeden stavový atribut nabývající např. texty "růst", "pokles", "stagnace"; či vytvořit 3 atributy (růst, pokles, stagnace) obsahující logickou hodnotu, zda situace v daný den nastala.
Posledním poznatkem je nutnost vytvořit agregace na úrovni kalendářních měsíců počítající dny, kdy nastala jedna ze sledovaných situací. Na mysl by tedy měla přijít agregační funkce COUNT a funkce pro práci s částmi datumů EXTRACT, která bude využita v GROUP BY klauzuli.
Komentář řešení: jelikož je dotaz rozsáhlý a vyžaduje několik mezivýpočtů, volím pro čitelnost vynesení poddotazů jako CTE. Pokud je potřeba v jednom dotazu uvést vícero CTE, klíčové slovo WITH je použito pouze jednou a jednotlivé CTE jsou oddělené čárkou.
Výpočet avgs je shodný jako v předchozím textu. U nastavení okna pro klouzavý průměr volím ROWS BETWEEN 3 PRECEDING AND 3 FOLLOWING, tedy každou hodnotu průměruji pomocí 3 časově předcházejících a 3 časově následujících hodnot k vyhlazení týdenních cyklů. Alternativou by bylo uvažovat předcházejících 6 hodnot, ale to ve výsledku ovlivní jen zařazení hraničních hodnot do příslušných měsíců.
Výpočet states je podobný jako hlavní dotaz v předchozím textu. Původní atribut diff není zde v projekci explicitně zmíněn, ale jeho výpočet je použit v CASE WHEN bloku k přepsání kladných hodnot na text +++, záporných na --- a zbylých (nula) na x.
Výpočet by_months by sám o sobě mohl být odpovědí na řešenou otázku, jelikož seskupuje výsledek výpočtu states dle roku-měsíce a počítá výskyty různých stavů. Každý záznam odpovídá kombinaci roku-měsíce a stavu, např. 2021, 1, '+++', 12. I když tento formát není špatný, pro další zpracování se může hodit spíše forma podobné kontingenční tabulky v tabulkovém procesoru, kdy v jednom záznamu by byla jak informace o počtu růstových, tak klesajících dnů.
K transformaci tabulky lze použít Oracle nativní operaci PIVOT [nestandardní]. V samotném příkazu se udává, jaké hodnoty má zavést jako samostatné sloupce výsledku FOR state IN ('+++', '---'), tedy hodnoty růstu a poklesu (nikoliv stagnace) z atributu state. Kromě toho je nutné uvést, jakou hodnotu má do nových sloupců dosadit. Zde MAX(c) je použit kvůli tomu, že hodnoty jsou již předpočítané z předchozí agregace. Zbylé položky projekce, tedy y a m jsou implicitně chápány jako atributy, které PIVOT používá ke grupování výsledných záznamů.
V jiných systémech nemusí existovat ekvivalent PIVOT a je nutné použít jako alternativu CASE nebo FILTER [SQL:2003] na úrovni projekce viz 「alternativy a matice kompatibility」.
WITH avgs AS (
SELECT
datum,
pocet_nakazenych,
ROUND(AVG(pocet_nakazenych) OVER
(
ORDER BY datum
ROWS BETWEEN 3 PRECEDING AND 3 FOLLOWING
), 0) AS "7d_avg"
FROM covid_daily
ORDER BY datum
),
states AS(
SELECT
datum,
CASE WHEN "7d_avg" - LAG("7d_avg", 7, 0) OVER
(ORDER BY datum) > 0 THEN '+++'
WHEN "7d_avg" - LAG("7d_avg", 7, 0) OVER
(ORDER BY datum) < 0 THEN '---'
ELSE 'x' END as state
FROM avgs
),
by_months AS(
SELECT
EXTRACT(year FROM datum) as y,
EXTRACT(month FROM datum) as m,
state,
count(*) as c
FROM states
GROUP BY (EXTRACT(year FROM datum),
EXTRACT(month FROM datum), state)
)
SELECT *
FROM by_months
PIVOT
(
MAX(c)
FOR state IN ('+++', '---')
)
ORDER BY y, m
;
Y M '+++' '---'
---------- ---------- ---------- ----------
2021 1 12 19
2021 2 28
2021 3 4 27
2021 4 1 29
2021 5 31
2021 6 4 26
2021 7 16 15
2021 8 22 9
2021 9 30
2021 10 31
2021 11 28 2
2021 12 31
Komplexní příklad #2
Otázka: jaká byla nejdelší období růstu, resp. poklesů denních nákaz? Uvažuje se opět rozdíl 7denních průměrů stejného dnu v po sobě následujících týdnech.
Analýza: otázka vychází ze stejného výpočtu jako příklad #1. Lze tedy využít výpočet předcházející seskupení záznamů podle roku-měsíce.
Samotný problém spočívá v určení, kolik předchozích dnů vůči současnému mělo stejný stav, tj. růst, pokles, či stagnace. Zároveň je potřeba najít, kdy momentální období končí. V ideálním světě by bylo možné pomocí syntaxe nastavení velikosti okna window funkce nějakou podmínkou požadovat, aby okno takový interval samo maximalizovalo a následně pak stačilo jen zavolat funkci COUNT. Mám za to, že taková možnost bohužel neexistuje ani v SQL standardu, ani jako nestandardní funkce běžných relačních databází.
Je tedy potřeba přistoupit k řešení trikem. Se stávajícími nástroji je možné identifikovat dny, ve kterých dojde ke změně trendu, a vyfiltrovat je do pomocné tabulky. Následně bude možné pro každý den zlomu najít nejbližší další den zlomu, čímž bude vymezen trendový interval. Se znalostí intervalů je triviální spočíst jejich velikost v počtu dnů a vhodně seřadit.
Komentář řešení: výpočty klouzavých průměrů avgs a trendových stavů v daný den states jsou přejaté z předchozího příkladu.
Výpočet changes slouží k nalezení změny trendu pomocí logické podmínky, že hodnota state na předcházející řádce je odlišná od té, na současném řádku LAG(state, 1) OVER(ORDER BY datum) <> state. Tím, že není uveden ELSE blok, bude většina záznamů obsahovat NULL v tomto sloupci.
Výpočet state_durations nejprve vyfiltruje záznamy, které mají vyplněn odvozený atribut prev z předchozího výpočtu a tedy jsou hraniční při změně trendu. Následně pomocí funkce LEAD nalezne další záznam s vyplněnou hodnotou a spočte jejich časový rozdíl v dnech. Jako konec intervalu datum_to je určen den předcházející nalezené hodnotě.
(pozn. místo LEAD by bylo možné použít LAG s zápornou hodnotou -1 v argumentu, případně s obráceným řazením)
Samotný dotaz pouze seřadí předchozí výpočet a vyřadí neukončené intervaly, tedy nejnovější aktivní trend v datech. Zajímavým okem pozorovatelným výstupem je, že se zdá, že může existovat roční cyklus ohledně nejdelších rostoucí a klesající intervalů – dlouhé růsty začínají na konci srpna; dlouhé poklesy začínají v první půlce dubna.
WITH avgs AS ( ... ),
states AS( ... ),
changes AS(
SELECT
datum,
state,
CASE WHEN LAG(state, 1) OVER
(ORDER BY datum) <> state
THEN 'change' END as prev
FROM states
),
state_durations AS(
SELECT
datum AS datum_from,
LEAD(datum, 1) OVER
(ORDER BY datum) -1 AS datum_to,
state,
LEAD(datum, 1) OVER
(ORDER BY datum) - datum AS days
FROM changes
WHERE prev IS NOT NULL
)
SELECT
datum_from,
datum_to,
state,
days
FROM state_durations
WHERE days IS NOT NULL
ORDER BY days DESC
;
DATUM_FROM DATUM_TO STA DAYS
---------- --------- --- ------
23-AUG-21 28-NOV-21 +++ 98
11-APR-21 26-JUN-21 ––– 77
18-AUG-20 28-OCT-20 +++ 72
04-APR-20 12-MAY-20 ––– 39
04-DEC-20 09-JAN-21 +++ 37
29-OCT-20 03-DEC-20 ––– 36
05-MAR-21 09-APR-21 ––– 36
29-FEB-20 03-APR-20 +++ 35
29-JAN-21 04-MAR-21 +++ 35
29-NOV-21 31-DEC-21 ––– 33
01-JAN-22 02-FEB-22 +++ 33