Часть 10 учебника SQL. Оконные функции для аналитика. ROW_NUMBER, RANK, DENSE_RANK для нумерации. LAG/LEAD для соседних значений. Когда брать вместо GROUP BY.
TL;DR: Оконные функции считают значение для каждой строки, не схлопывая их в группы. ROW_NUMBER() OVER (PARTITION BY x ORDER BY y) нумерует. RANK и DENSE_RANK ранжируют с учётом ties. LAG/LEAD берут предыдущее/следующее значение. SUM() OVER (PARTITION BY...) — running total. Оконные функции — самый частый продвинутый вопрос на собесах.
В этой части:
Как работает running total — step-by-step:
GROUP BY схлопывает строки в одну на группу:
ROW_NUMBER() OVER (PARTITION BY x ORDER BY y) нумерует строки в каждой группе.
При одинаковых значениях (ties):
| Значение | ROW_NUMBER | RANK | DENSE_RANK |
|---|---|---|---|
| 100 | 1 | 1 | 1 |
| 100 | 2 | 1 | 1 |
| 90 | 3 | 3 (skip 2) | 2 |
| 80 | 4 | 4 | 3 |
LAG(col, n) OVER (... ORDER BY ts) — значение col за N строк назад.
LEAD(col, n) OVER (... ORDER BY ts) — за N строк вперёд.
Любая агрегатная функция из GROUP BY (SUM, AVG, COUNT, MAX, MIN) работает и как оконная — просто добавь OVER (...) после вызова. Это даёт мощную возможность: посчитать «итог по группе» и оставить рядом с каждой строкой оригинальные значения (без схлопывания).
Пример с цифрами. Таблица 6 заказов из RU и KZ. Хотим к каждой строке дописать total/avg/count по своей стране — но НЕ схлопывать:
Исходная таблица orders (6 строк):
| id | country | amount |
|---|---|---|
| 1 | RU | 50 |
| 2 | RU | 100 |
| 3 | RU | 150 |
| 4 | KZ | 100 |
| 5 | KZ | 300 |
| 6 | KZ | 500 |
| Функция | Когда нужна | Пример |
|---|---|---|
SUM(x) OVER (PARTITION BY g) | Total по группе рядом с каждой строкой | Доля строки от total: amount * 1.0 / SUM(amount) OVER (PARTITION BY country) |
AVG(x) OVER (PARTITION BY g) | Средний чек группы | Сравнить заказ с average: amount - AVG(amount) OVER (PARTITION BY country) |
COUNT(*) OVER (PARTITION BY g) | Сколько у user всего заказов | Показать всем строкам: COUNT(*) OVER (PARTITION BY user_id) |
MAX(x) OVER (PARTITION BY g) | Самый дорогой заказ юзера | Флаг «это его рекорд»: amount = MAX(amount) OVER (PARTITION BY user_id) |
MIN(x) OVER (PARTITION BY g) | Первый заказ юзера | Дата первого: MIN(created_at) OVER (PARTITION BY user_id) |
С ORDER BY те же 5 функций превращаются в running-варианты: running total, moving average, running count, running max/min.