middle
Какие основные оконные функции существуют в PostgreSQL?
PostgreSQL предоставляет ранжирующие функции (ROW_NUMBER, RANK, DENSE_RANK, NTILE) и функции смещения (LAG, LEAD, FIRST_VALUE, LAST_VALUE). Вместе с агрегатными функциями (SUM, AVG, COUNT) в оконном контексте они покрывают большинство аналитических задач.
Ранжирующие функции
Пример
SELECT
name, department, salary,
ROW_NUMBER() OVER (ORDER BY salary DESC) AS row_num,
RANK() OVER (ORDER BY salary DESC) AS rank,
DENSE_RANK() OVER (ORDER BY salary DESC) AS dense_rank,
NTILE(4) OVER (ORDER BY salary DESC) AS quartile
FROM employees;
| Функция | Описание | Одинаковые значения |
|---|---|---|
ROW_NUMBER() |
Уникальный номер строки | Разные номера |
RANK() |
Ранг с пропусками | Одинаковый ранг, следующий с пропуском |
DENSE_RANK() |
Ранг без пропусков | Одинаковый ранг, следующий без пропуска |
NTILE(n) |
Разбивка на n групп | Равномерное распределение |
Функции смещения
Пример
SELECT
id, amount, created_at,
LAG(amount, 1) OVER (ORDER BY created_at) AS prev_amount,
LEAD(amount, 1) OVER (ORDER BY created_at) AS next_amount,
FIRST_VALUE(amount) OVER (PARTITION BY account_id ORDER BY created_at) AS first_tx,
LAST_VALUE(amount) OVER (
PARTITION BY account_id ORDER BY created_at
ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
) AS last_tx
FROM transactions;
Практические задачи
-- Топ-3 транзакции по каждому счёту
WITH ranked AS (
SELECT *, ROW_NUMBER() OVER (PARTITION BY account_id ORDER BY amount DESC) AS rn
FROM transactions
)
SELECT * FROM ranked WHERE rn <= 3;
-- Разница с предыдущей транзакцией
SELECT
id, amount,
amount - LAG(amount) OVER (ORDER BY created_at) AS diff_with_prev,
created_at - LAG(created_at) OVER (ORDER BY created_at) AS time_gap
FROM transactions
WHERE account_id = 100;
-- Процент от общей суммы
SELECT
account_id, amount,
ROUND(amount * 100.0 / SUM(amount) OVER (), 2) AS pct_of_total,
ROUND(amount * 100.0 / SUM(amount) OVER (PARTITION BY account_id), 2) AS pct_of_account
FROM transactions;
-- Нарастающий итог с ежедневной группировкой
SELECT
date(created_at) AS day,
SUM(amount) AS daily_total,
SUM(SUM(amount)) OVER (ORDER BY date(created_at)) AS cumulative_total
FROM transactions
GROUP BY date(created_at);
На собеседовании: обязательный вопрос — отличие ROW_NUMBER, RANK, DENSE_RANK на одинаковых значениях. Классическая задача: «топ-N записей в каждой группе» — решается через ROW_NUMBER + PARTITION BY + CTE с фильтром.