Začínáte zde? Tato lekce je součástí úplného kurzu používání SQL pro analýzu dat. Podívejte se na začátek.
V této lekci pojednáme:
- Úvod do funkcí okna
- Základní syntaxe oken
- Obvyklí podezřelí: SUM, COUNT a AVG
- ROW_NUMBER ()
- RANK () a DENSE_RANK ()
- NTILE
- MAS and LEAD
- Definování alias okna
- Pokročilé techniky vytváření oken
Tato lekce využívá data z programu Capital Bikeshare ve Washingtonu DC, který publikuje podrobné historické údaje o úrovni výletu na jejich webových stránkách. Data byla stažena v únoru 2014, ale jsou omezena na data shromážděná během prvního čtvrtletí roku 2012. Každý řádek představuje jednu jízdu. Většina polí je samozřejmostí, kromě rider_type
: „Registrovaný“ označuje měsíční členství v programu spolujízdy, „Neformální“ uvádí, že jezdec koupil třídenní vstupenku. Pole start_time
a end_time
byla vyčištěna z původních formulářů, aby vyhovovaly formátování data SQL – jsou v této tabulce uložena jako časová razítka.
Úvod do funkcí okna
Dokumentace PostgreSQL dělá skvělou práci při zavádění konceptu funkcí oken:
Funkce okna provádí výpočet napříč sadou řádků tabulky, které nějak souvisí s aktuálním řádkem. To je srovnatelné s typem výpočtu, který lze provést agregační funkcí. Na rozdíl od běžných agregačních funkcí však použití okna funkce nezpůsobí seskupení řádků do jednoho výstupního řádku – řádky si zachovají svou samostatnou identitu. V zákulisí je funkce okna přístupná nejen k aktuálnímu řádku výsledku dotazu.
Nejpraktičtějším příkladem je průběžný součet:
Vidíte, že výše uvedený dotaz vytváří agregaci (running_total
) bez použití GROUP BY
. Pojďme rozdělit syntaxi a uvidíme, jak to funguje.
Základní syntaxe oken
První část výše uvedené agregace, SUM(duration_seconds)
, vypadá hodně jako každá jiná agregace. Přidání OVER
ji označuje jako funkci okna. Výše uvedenou agregaci můžete přečíst jako „vezměte součet duration_seconds
přes celou sadu výsledků, v pořadí podle start_time
. „
Pokud byste chtěli zúžit okno z celé datové sady na jednotlivé skupiny v rámci datovou sadu, můžete k tomu použít PARTITION BY
:
Výše uvedený dotaz seskupuje a objednává dotaz podle start_terminal
. V rámci každé hodnoty start_terminal
je seřazeno podle start_time
a průběžného součtu přes aktuální řádek a všechny předchozí řádky duration_seconds
. Posouvejte se dolů, dokud se hodnota start_terminal
nezmění, a všimnete si, že running_total
začíná znovu. To se stane, když seskupujete pomocí PARTITION BY
. V případě, že vás stále untík ORDER BY
přeruší, jednoduše si objedná podle určeného sloupce (s) stejným způsobem jako klauzule ORDER BY
, kromě toho, že s každým oddílem zachází jako s oddělenými. Vytvoří také průběžný součet – bez ORDER BY
bude každá hodnota jednoduše součtem všech duration_seconds
hodnot v příslušných start_terminal
. Zkuste spustit výše uvedený dotaz bez ORDER BY
, abyste získali představu:
ORDER
a definujte, co se označuje jako„ okno “- seřazená podmnožina dat, na základě kterých se provádějí výpočty.
Poznámka: Funkce okna a standardní agregace nelze používat současně konkrétněji, nelze funkce okna zahrnout do klauzule GROUP BY
.
Problém s praxí
Napište modifikaci dotazu výše uvedeného příkladu dotazu, který zobrazuje trvání každé jízdy jako procento z celkového času, který jezdci získali z každého start_terminal
Vyzkoušejte Podívejte se na odpověď
Obvyklí podezřelí: SUM, COUNT a AVG
Při použití funkcí okna můžete použít stejné agregáty, které byste použili za normálních okolností – SUM
, COUNT
a AVG
. Nejjednodušší způsob, jak tomu porozumět, je znovu spustit předchozí příklad s některými dalšími funkcemi. Proveďte
Alternativně stejné funkce s ORDER BY
:
Nezapomeňte tyto dva předchozí dotazy připojit do režimu a spustit je. Tento další praktický problém je velmi podobný příkladům, proto zkuste upravit výše uvedený kód, místo toho, abyste začínali od nuly.
Problém s praxí
Napište dotaz, který ukazuje průběžný součet trvání jízd na kole (podobně jako poslední příklad), ale seskupené podle end_terminal
a s délkou jízdy seřazené sestupně.
Vyzkoušet Podívejte se na odpověď
ROW_NUMBER ()
ROW_NUMBER()
dělá přesně to, co zní – zobrazuje číslo daného řádku. Začíná to na 1 a čísluje řádky podle ORDER BY
části výpisu okna. ROW_NUMBER()
nevyžaduje, abyste v závorce uvedli proměnnou:
Použití klauzule PARTITION BY
vám umožní začněte znovu počítat 1 v každém oddílu. Následující dotaz spustí počítání znovu pro každý terminál:
RANK () a DENSE_RANK ()
RANK()
se mírně liší od ROW_NUMBER()
. Pokud například objednáváte prostřednictvím start_time
, může se stát, že některé terminály budou jezdit se dvěma stejnými počátečními časy. V tomto případě mají stejnou hodnost, zatímco ROW_NUMBER()
jim dává různá čísla. V následujícím dotazu si všimnete 4. a 5. pozorování pro start_terminal
31000 – obě mají hodnost 4 a následující výsledek získá hodnost 6:
Místo RANK()
můžete také použít DENSE_RANK()
v závislosti na vaší aplikaci. Představte si situaci, kdy tři položky mají stejnou hodnotu. Pomocí obou příkazů získají všichni stejnou hodnost. Kvůli tomuto příkladu řekněme „s“ 2. „ Takto by oba příkazy vyhodnotily další výsledky odlišně:
-
RANK()
dá stejným řádkům pořadí 2, poté přeskočí řady 3 a 4, takže další výsledek by byl 5 -
DENSE_RANK()
by stále dal všem identickým řádkům hodnost 2, ale následující řádek by byl 3 – ne řady by byly přeskočeny.
Problém s praxí
Napište dotaz, který zobrazí 5 nejdelších jízd z každého počátečního terminálu, seřazeného podle terminálu, a nejdelší až nejkratší jízdy v každém terminálu. Limit na jízdy, které proběhly před 8. lednem 2012.
Vyzkoušet Podívejte se na odpověď
NTILE
Pomocí funkcí okna můžete určit, do jakého percentilu (nebo kvartilu nebo jakéhokoli jiného pododdělení) spadá daný řádek. Syntaxe je NTILE(*# of buckets*)
. V tomto případě ORDER BY
určuje, který sloupec se použije k určení kvartilů (nebo jakéhokoli počtu upřesněte). Například:
Při pohledu na výsledky z výše uvedeného dotazu můžete vidět, že sloupec percentile
se nepočítá přesně tak, jak byste očekávali. Pokud pouze měl dva záznamy a měřili jste percentily, očekávali byste, že jeden záznam definuje 1. percentil a druhý záznam definuje 100. percentil. Pomocí funkce NTILE
ve skutečnosti uvidíte jeden záznam v 1. percentilu a jeden ve 2. percentilu. Vidíte to ve výsledcích pro start_terminal
31000 – sloupec percentile
vypadá jako číselné hodnocení. Pokud přejdete dolů na start_terminal
31007, je vidět, že správně vypočítává percentily, protože o tom existuje více než 100 záznamů start_terminal
. Pokud pracujete s velmi malými okny, pamatujte na to a zvažte použití kvartilů nebo podobně malých pásma.
Problém s praxí
Napište dotaz, který zobrazuje pouze dobu trvání cesty a percentil, do kterého tato doba klesá (napříč celou datovou sadou – není rozdělena podle terminálu).
Vyzkoušet Podívejte se na odpověď
MAS a VEDOUT
Často může být užitečné porovnat řádky s předchozími nebo následujícími řádky, zejména pokud máte data v pořadí, které dává smysl. Pomocí LAG
nebo LEAD
můžete vytvořit sloupce, které získávají hodnoty z jiných řádky – vše, co musíte udělat, je zadat, ze kterého sloupce se má vytáhnout a o kolik řádků dál byste chtěli tahat. LAG
táhne z předchozích řádků a LEAD
táhne z následujících řádků:
To je zvláště užitečné, pokud chcete vypočítat rozdíly mezi řádky:
První řádek ve sloupci difference
má hodnotu null, protože neexistuje žádný předchozí řádek, ze kterého lze táhnout. Podobně použití LEAD
vytvoří nuly na konci datové sady. Pokud byste chtěli výsledky trochu čistit, můžete je zabalit do vnějšího dotazu a odstranit tak nuly:
Definování aliasu okna
Pokud plánujete psát několik funkcí okna do stejného dotazu, pomocí stejného okna můžete vytvořit alias.Vezměte NTILE
příklad výše:
Lze jej přepsat jako:
WINDOW
klauzule, pokud je zahrnuta, by měla vždy následovat po klauzuli WHERE
.
Pokročilé techniky vytváření oken
Můžete si prohlédnout kompletní seznam oken funkce v Postgresu (používá režim syntaxe) v dokumentaci Postgres. Pokud používáte funkce okna v připojené databázi, měli byste se podívat na příslušného průvodce syntaxí pro váš systém.