SQL window functions: pořadí, průběžné součty a analytika

Published: 2026-03-24

Window functions provádějí výpočty nad množinou řádků, které souvisejí s aktuálním řádkem — aniž by výsledek zkolabovaly do jednoho řádku jako GROUP BY. Patří mezi nejmocnější funkce v moderním SQL a jsou podporovány v SQLite (3.25+), PostgreSQL, MySQL (8.0+), SQL Serveru a Oracle.

Ukázková data

Ve všech příkladech používáme dvě tabulky:

Tabulka employees:

id name department hire_date salary
1 Alice Engineering 2023-01-15 95000
2 Bob Engineering 2023-06-01 85000
3 Carol Engineering 2024-03-10 85000
4 Dave Sales 2023-02-20 72000
5 Eve Sales 2023-09-05 68000
6 Frank Sales 2024-01-15 65000
7 Grace Marketing 2023-04-01 82000
8 Henry Marketing 2024-06-01 70000

Tabulka monthly_sales:

id employee_id month revenue
1 1 2026-01 22000
2 1 2026-02 25000
3 1 2026-03 28000
4 2 2026-01 19000
5 2 2026-02 21000
6 2 2026-03 20000
7 3 2026-01 15000
8 3 2026-02 17000
9 3 2026-03 19000
10 4 2026-01 18000
11 4 2026-02 22000
12 4 2026-03 17000
13 5 2026-01 14000
14 5 2026-02 16000
15 5 2026-03 19000
16 6 2026-01 11000
17 6 2026-02 13000
18 6 2026-03 15000
19 7 2026-01 9000
20 7 2026-02 12000
21 7 2026-03 14000
22 8 2026-01 7000
23 8 2026-02 9000
24 8 2026-03 11000
Prozkoumat dataset v DbGate Lite

Syntaxe window functions

Každá window function používá klauzuli OVER, která určuje, nad kterými řádky se má výpočet provádět:

function_name(...) OVER (
  [PARTITION BY column]
  [ORDER BY column]
  [frame_clause]
)
  • PARTITION BY — rozdělí řádky do skupin (podobně jako GROUP BY, ale všechny řádky zachová)
  • ORDER BY — určuje pořadí řádků v rámci každé partition
  • Frame clause — omezuje okno na rozsah řádků relativně k aktuálnímu řádku

ROW_NUMBER()

ROW_NUMBER() přiřadí každému řádku v rámci partition jedinečné pořadové číslo. Číslování vždy začíná od 1, bez mezer a bez shodných hodnot.

Příklad: Očíslování všech zaměstnanců podle platu

Příklad: Očíslování zaměstnanců v rámci každého oddělení

Pomocí PARTITION BY se číslování pro každé oddělení znovu začíná od 1:

RANK() a DENSE_RANK()

RANK() a DENSE_RANK() pracují s remízami jinak než ROW_NUMBER():

  • ROW_NUMBER() — vždy jedinečné číslo, remízy se lámou libovolně
  • RANK() — stejné hodnoty dostanou stejnou pozici, poté se čísla přeskočí (1, 2, 2, 4)
  • DENSE_RANK() — stejné hodnoty dostanou stejnou pozici, bez mezer (1, 2, 2, 3)

Příklad: Porovnání všech tří funkcí pro pořadí

Bob a Carol mají oba plat 85000 — všimněte si, jak každá funkce remízu zpracuje:

Příklad: Pořadí zaměstnanců v rámci oddělení

NTILE()

NTILE(n) rozdělí řádky do n přibližně stejně velkých skupin (bucketů), očíslovaných od 1 do n.

Příklad: Rozdělení zaměstnanců do platových kvartilů

Příklad: Rozdělení zaměstnanců v oddělení do dvou úrovní

LAG() a LEAD()

LAG(column, offset) zpřístupní hodnotu z předchozího řádku. LEAD(column, offset) zpřístupní hodnotu z následujícího řádku. Obě funkce mají výchozí offset 1 a vracejí NULL, pokud takový řádek neexistuje.

Příklad: Meziměsíční porovnání tržeb

Porovnejte tržby v každém měsíci s předchozím měsícem pro každého zaměstnance:

Příklad: Náhled tržeb v příštím měsíci

Agregační window functions

Běžné agregační funkce (SUM, AVG, COUNT, MIN, MAX) lze s OVER použít jako window functions. Na rozdíl od GROUP BY zůstane ve výstupu zachován každý řádek.

Příklad: Plat zaměstnance vs. průměr oddělení

Příklad: Procento z celkových mzdových nákladů

Spočítejte, jaký podíl na celkové mzdové náročnosti představuje každý zaměstnanec:

Příklad: Průběžný součet a kumulativní počet

Průběžné součty podle partition

Kombinací PARTITION BY a ORDER BY můžete počítat průběžné součty v rámci skupin.

Příklad: Kumulativní tržby na zaměstnance

Příklad: Měsíční pořadí tržeb napříč všemi zaměstnanci

Zjistěte, jak si každý zaměstnanec vedl v jednotlivých měsících:

Okenní rámce (window frames)

Okenní rámec přesně určuje, které řádky relativně k aktuálnímu řádku jsou do výpočtu zahrnuty. Klauzule rámce se píše za ORDER BY uvnitř OVER.

Syntaxe

ROWS BETWEEN <start> AND <end>

Běžné hranice:

  • UNBOUNDED PRECEDING — od prvního řádku partition
  • N PRECEDING — N řádků před aktuálním řádkem
  • CURRENT ROW — aktuální řádek
  • N FOLLOWING — N řádků za aktuálním řádkem
  • UNBOUNDED FOLLOWING — až po poslední řádek partition

Příklad: 2měsíční klouzavý průměr

Příklad: Průběžné minimum a maximum tržeb

FIRST_VALUE a LAST_VALUE

FIRST_VALUE(column) vrací první hodnotu v rámci okenního rámce. LAST_VALUE(column) vrací poslední hodnotu. Pozor: LAST_VALUE s výchozím rámcem (ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) vidí jen do aktuálního řádku.

Příklad: Porovnání každého měsíce s prvním měsícem

Příklad: Porovnání každého měsíce s nejlepším měsícem

Použijte ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING, abyste viděli celou partition:

Praktický příklad: Nejlépe placený v oddělení

Častý vzor: použijte window function v poddotazu a pak filtrujte podle pořadí.

Příklad: Nejlépe placený zaměstnanec v každém oddělení

Příklad: Měsíc s nejvyššími tržbami pro každého zaměstnance

Window functions vs. GROUP BY

Klíčový rozdíl: GROUP BY zkolabuje řádky do souhrnných řádků, zatímco window functions všechny řádky zachovají a jen přidají vypočtený sloupec.

Příklad: Porovnání vedle sebe

GROUP BY vrací jeden řádek na oddělení:

Window functions zachovají všechny řádky:

Běžné použití window functions

  • Pořadí a žebříčky — ROW_NUMBER, RANK, DENSE_RANK pro dotazy typu top-N
  • Průběžné součty — kumulativní SUM pro finance a sledování zásob
  • Meziobdobová srovnání — LAG/LEAD pro meziměsíční nebo meziroční analýzu
  • Klouzavé průměry — vyhlazování dat pomocí AVG nad rámcem
  • Percentilová analýza — NTILE pro rozdělení dat do stejně velkých skupin
  • Deduplikace — ROW_NUMBER pro výběr jednoho řádku ze skupiny
  • Analýza mezer — LAG pro detekci chybějících sekvencí nebo neobvyklých změn

Tipy k výkonu

  1. Indexujte sloupce v ORDER BY — window functions interně třídí data; indexy pomáhají
  2. Minimalizujte počet partition — velké množství partition zvyšuje výpočetní náročnost
  3. Znovu používejte definice oken — mnoho databází podporuje klauzuli WINDOW, abyste nemuseli opakovat stejnou specifikaci OVER
  4. Filtrujte až po výpočtu oken — window functions se vyhodnocují před WHERE, proto pro filtrování podle pořadí používejte poddotazy nebo CTE
  5. Vyhněte se zbytečným rámcům — rámec specifikujte jen tehdy, když potřebujete jiné chování než výchozí

Shrnutí

  • Window functions počítají hodnoty napříč souvisejícími řádky bez zkolabování výsledné množiny
  • ROW_NUMBER, RANK, DENSE_RANK přiřazují řádkům pořadí
  • LAG a LEAD zpřístupňují hodnoty z předchozích nebo následujících řádků
  • NTILE rozděluje řádky do stejně velkých bucketů
  • FIRST_VALUE a LAST_VALUE vracejí hraniční hodnoty z okna
  • Agregační funkce (SUM, AVG, COUNT, MIN, MAX) s OVER zachovají všechny řádky a zároveň počítají metriky na úrovni skupin
  • PARTITION BY vytváří nezávislé skupiny; ORDER BY určuje pořadí řádků
  • Okenní rámce (ROWS BETWEEN ... AND ...) jemně doladí, které řádky jsou zahrnuty
  • Pro filtrování podle výsledků window functions (např. top-N ve skupině) používejte poddotazy
  • Podporováno v SQLite 3.25+, PostgreSQL, MySQL 8.0+, SQL Serveru a Oracle

Zkuste si příklady upravit, experimentujte s různými partition, řazením a rámci a uvidíte, jak vám window functions mohou zjednodušit analytické dotazy!