Оконные функции удобны не только для ранжирования. Частая задача на практике — посчитать агрегаты по группе и при этом оставить каждую строку исходной таблицы.
Представим таблицу заказов:
orders(id, customer_id, amount, created_at)
Хотим увидеть все заказы и одновременно понимать, сколько денег каждый клиент потратил в сумме.
Первая мысль — посчитать через
GROUP BY:SELECT
customer_id,
SUM(amount) AS total_amount
FROM orders
GROUP BY customer_id;
Да, сумму он посчитает. Но список заказов пропадёт — останется по одной строке на клиента.
Если коротко:
GROUP BY считает по группе, но детали внутри группы выбрасывает.Решение через оконную функцию:
SELECT
id,
customer_id,
amount,
SUM(amount) OVER (PARTITION BY customer_id) AS total_amount
FROM orders;
PARTITION BY customer_id разбивает строки на независимые окна по клиентам.SUM(...) OVER (...) считает сумму внутри каждого окна, но строки не схлопывает — каждая остаётся на месте.В результате каждая строка — отдельный заказ и
total_amount — сумма всех заказов этого клиента.Фильтрация по агрегату без
GROUP BY — например, нужно выбрать только заказы тех клиентов, у которых суммарный оборот больше 1000:SELECT *
FROM (
SELECT
*,
SUM(amount) OVER (PARTITION BY customer_id) AS total_amount
FROM orders
) t
WHERE total_amount > 1000;
Обратите внимание: фильтрация происходит уже после вычисления оконной функции. Никаких
GROUP BY, никаких дополнительных JOIN.Та же логика через
JOIN (для сравнения):SELECT o.*
FROM orders o
JOIN (
SELECT customer_id, SUM(amount) AS total_amount
FROM orders
GROUP BY customer_id
) s ON s.customer_id = o.customer_id
WHERE s.total_amount > 1000;
Работает, но выглядит тяжелее: отдельный подзапрос, джойн, дублирование таблицы.
Можно посчитать сразу несколько агрегатов по тем же окнам:
SELECT
id,
customer_id,
amount,
SUM(amount) OVER (PARTITION BY customer_id) AS total_amount,
COUNT(*) OVER (PARTITION BY customer_id) AS orders_count
FROM orders;
Получаем и общую сумму, и количество заказов клиента — прямо в каждой строке.
🔥 Используйте оконные функции, когда нужно посчитать итог по группе, но не потерять сами строки. Это базовый приём аналитического SQL, который постоянно встречается в реальных задачах.
➡️ SQL Ready | #практика