Что такое оконные функции (Window Functions)?
Оконные функции (Window Functions) — функции, которые выполняют вычисления над набором строк, связанных с текущей строкой, без сворачивания результата в одну строку. В отличие от агрегатных функций с GROUP BY, каждая строка сохраняется в результате, а к ней добавляется вычисленное значение.
Аналогия из жизни: представьте таблицу зарплат в компании.
GROUP BYсложит все зарплаты отдела и покажет одну строку. Оконная функция покажет каждого сотрудника, но рядом допишет, какая средняя зарплата в его отделе.
Общий синтаксис
Пример
функция() OVER (
[PARTITION BY столбец_разделения]
[ORDER BY столбец_сортировки]
[ROWS | RANGE BETWEEN начало AND конец]
)
PARTITION BY— разделяет набор строк на группы (аналогGROUP BY, но без сворачивания)ORDER BY— определяет порядок строк внутри каждого разделаROWS/RANGE BETWEEN— определяет рамку (frame) — подмножество строк, участвующих в вычислении
Функции ранжирования
| Функция | Описание |
|---|---|
ROW_NUMBER() |
Уникальный номер строки |
RANK() |
Ранг с пропуском позиций |
DENSE_RANK() |
Ранг без пропуска позиций |
NTILE(n) |
Деление на n групп |
Пример
SELECT
name, department, salary,
ROW_NUMBER() OVER (PARTITION BY department ORDER BY salary DESC) AS row_num,
RANK() OVER (PARTITION BY department ORDER BY salary DESC) AS rnk,
DENSE_RANK() OVER (PARTITION BY department ORDER BY salary DESC) AS dense_rnk
FROM employees;
Функции смещения
| Функция | Описание |
|---|---|
LAG(col, n, default) |
Значение из предыдущей строки (на n позиций назад) |
LEAD(col, n, default) |
Значение из следующей строки (на n позиций вперёд) |
FIRST_VALUE(col) |
Первое значение в рамке окна |
LAST_VALUE(col) |
Последнее значение в рамке окна |
Пример
SELECT
name, salary,
LAG(salary, 1, 0) OVER (ORDER BY hire_date) AS prev_salary,
salary - LAG(salary, 1, 0) OVER (ORDER BY hire_date) AS salary_diff
FROM employees;
Агрегатные функции как оконные
SUM, AVG, COUNT, MIN, MAX могут использоваться для вычисления накопительных (running) итогов:
Пример
SELECT
order_date, amount,
SUM(amount) OVER (ORDER BY order_date) AS running_total,
AVG(amount) OVER (ORDER BY order_date ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) AS moving_avg_3
FROM orders;
Рамки окна (Window Frame)
Пример
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW -- от начала до текущей строки
ROWS BETWEEN 3 PRECEDING AND 3 FOLLOWING -- 3 строки до и после
ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING -- от текущей до конца
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING -- все строки раздела
Различие ROWS и RANGE: ROWS работает с физическими строками, RANGE — с логическими значениями (строки с одинаковым значением ORDER BY считаются одной «позицией»).
Практический пример: топ-3 сотрудника по зарплате в каждом отделе
Пример
SELECT * FROM (
SELECT
name, department, salary,
DENSE_RANK() OVER (PARTITION BY department ORDER BY salary DESC) AS rnk
FROM employees
) ranked
WHERE rnk <= 3;
Важное
- Оконные функции выполняются после
WHERE,GROUP BY,HAVING, но доORDER BYиLIMIT - Оконные функции нельзя использовать в
WHEREиHAVING— для этого нужен подзапрос или CTE - Именованное окно позволяет избежать дублирования:
WINDOW w AS (PARTITION BY department ORDER BY salary), затемOVER w - По умолчанию, если указан
ORDER BYбез рамки, PostgreSQL используетRANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
Частые ошибки
- Путаница между
RANK()иDENSE_RANK()— забывают, чтоRANK()пропускает позиции - Использование
LAST_VALUE()без явного указания рамки — по умолчанию рамка заканчивается на текущей строке. Нужно явно указатьROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING - Попытка фильтрации по оконной функции в
WHEREвместо оборачивания в подзапрос - Забывают
PARTITION BYи получают вычисление по всему набору данных вместо групп
На собеседовании: покажите, что понимаете отличие от
GROUP BY— оконные функции не сворачивают строки. Назовите 3-4 функции и приведите пример сPARTITION BY. Частая ошибка — не знать про рамки окна (ROWS/RANGE) и их влияние на результат.