Оконные функции против GROUP BY
Оконная функция вычисляет агрегат-подобное значение для каждой строки, не схлопывая результат. GROUP BY схлопывает; OVER() сохраняет все строки. Бери окно, когда нужны и детальные строки, и агрегат в одном запросе.
В тикете на дашборд написано: «покажи каждую продажу, и рядом с ней — какой процент от итога её региона она составила». Джун пишет GROUP BY region, получает четыре строки и с ужасом понимает, что детальные строки исчезли. Сеньор пишет один OVER (PARTITION BY region) и выкатывает это одной строкой. Та же арифметика, противоположная форма результата — и эта разница самый полезный трюк в аналитическом SQL. За следующие десять минут ты разберёшься, почему GROUP BY уничтожает строки и как OVER их сохраняет — чтобы в следующий раз, когда отчёт требует «каждую строку плюс групповой агрегат», ты сразу тянулся к нужному инструменту.
GROUP BY схлопывает, окно хранит каждую строку
GROUP BY — это редьюсер. Ты даёшь ему много строк, он возвращает по одной строке на группу. После GROUP BY region у тебя четыре строки — по одной на регион — и увидеть отдельные продажи уже нельзя, потому что они свёрнуты в агрегат. В этом весь смысл группировки: схлопнуть детали в сводку.
Оконная функция выполняет ту же агрегатную математику, но выдаёт результат рядом с каждой исходной строкой. Ничего не схлопывается. Именно клауза OVER (...) делает функцию оконной: она говорит Postgres «вычисли это по набору связанных строк — окну — но прикрепи ответ к каждой строке, а не сворачивай строки вместе».
-- GROUP BY: 4 строки на выходе, детали потеряны
SELECT region, SUM(amount) AS region_total
FROM sales
GROUP BY region;
-- Окно: каждая строка продажи сохранена, итог региона прикреплён к каждой
SELECT region, salesperson, sale_date, amount,
SUM(amount) OVER (PARTITION BY region) AS region_total
FROM sales;Первый запрос отвечает «сколько продал каждый регион?» Второй — «покажи каждую продажу и итог её региона в одной строке» — а это ровно то, что нужно тикету дашборда. Тот же SUM, та же логика группировки по region; единственное структурное отличие — OVER (PARTITION BY region) вместо GROUP BY region.
Паттерн «процент от итога» в одном запросе
Поскольку окно хранит каждую строку, ты можешь смешать детальное значение и агрегат в одном SELECT и поделить их — без self-join, без подзапроса:
SELECT region, salesperson, amount,
ROUND(
100.0 * amount / SUM(amount) OVER (PARTITION BY region),
1
) AS pct_of_region
FROM sales
ORDER BY region, pct_of_region DESC;amount — собственное значение строки; SUM(amount) OVER (PARTITION BY region) — итог её региона, посчитанный по всем строкам того же региона. Делишь и получаешь долю каждой продажи в её регионе — на строку. С обычным GROUP BY для этого пришлось бы считать итоги отдельно и присоединять их обратно — больше кода и второй проход по данным.
Когда что брать — правило выбора
Спроси себя: звучит ли в требовании «на каждую строку» или «на строку» рядом с агрегатом? Именно этот вопрос определяет инструмент.
Выбор не про то, что «лучше»; он про форму ответа, который тебе нужен:
- Нужна только сводка (по одной строке на группу)? Бери
GROUP BY. Отчёты, счётчики по статусам, дневные итоги. - Нужны детальные строки и агрегат в одной строке? Бери окно. Процент от итога, ранг внутри группы, текущий баланс, «как эта строка соотносится со средним по своей группе».
Надёжное продакшен-правило: если в требовании рядом с агрегатом стоит слово «каждый» или «на строку» — тебе нужно окно. «Покажи каждый заказ с пожизненным итогом его клиента» — окно. «Покажи итог на клиента» — GROUP BY. Перепутать их — классический баг джуна: тянуться к GROUP BY, а потом обнаружить, что нужные детальные строки исчезли, или пытаться положить неагрегированную колонку в GROUP BY-запрос и упереться в column must appear in the GROUP BY clause.
▸Почему это работает
Почему нельзя просто SELECT salesperson, SUM(amount) ... GROUP BY region и получить оба? Потому что после группировки salesperson неоднозначен — в регионе много продавцов, и чьё имя должна нести единственная строка вывода? SQL отказывается, требуя, чтобы каждая выбранная колонка была либо сгруппирована, либо агрегирована. Окна обходят это целиком: строки никогда не схлопываются, поэтому salesperson остаётся однозначным в своей строке, а SUM(amount) OVER (...) едет рядом. Логика агрегации из раздела 03-aggregation и логика окон здесь — два ответа на «свести», различаемые лишь тем, выживают ли детали.
Ты запускаешь SELECT region, salesperson, SUM(amount) OVER (PARTITION BY region) FROM sales; по таблице из 8 продаж в 4 регионах. Сколько строк вернётся?
Требование гласит: «перечисли каждое событие с накопительным числом событий для этого пользователя». Какой инструмент подходит и почему?
Расставь шаги, чтобы добавить «процент от итога региона» к каждой строке продажи:
- 1 Начни с детального запроса: SELECT region, salesperson, amount FROM sales
- 2 Добавь SUM(amount) OVER (PARTITION BY region) — итог региона, сохранённый на строку
- 3 Раздели amount строки на этот оконный итог
- 4 Умножь на 100.0 и сделай ROUND, чтобы получить читаемый процент
- 5 ORDER BY region, pct DESC, чтобы это подать
- 01В одном предложении: в чём структурная разница между GROUP BY и оконной функцией?
- 02Нужно показать каждый заказ рядом с пожизненным итогом его клиента. GROUP BY или окно — и почему?
- 03Почему SELECT salesperson, SUM(amount) ... GROUP BY region падает, а то же с OVER (PARTITION BY region) работает?
GROUP BY и оконные функции отвечают на один вопрос «свести» противоположной формой результата. GROUP BY — редьюсер: он сворачивает детальные строки в одну на группу, поэтому после него отдельных строк уже нет. Оконная функция добавляет OVER (...) к агрегату, так что та же математика считается по окну связанных строк, но ответ прикрепляется к каждой исходной строке; число строк неизменно. Этот инвариант числа строк и есть правило выбора: если требование нуждается и в детальных строках, и в агрегате — процент от итога, ранг внутри группы, текущий баланс, сравнение со средним по группе — тебе нужно окно. Если нужна только сводка, GROUP BY проще. Теперь, встретив тикет «покажи каждый X с Y его группы», тянись к OVER (PARTITION BY ...) раньше, чем напишешь GROUP BY — детальные строки останутся на месте. Следующий урок раскрывает две клаузы, формирующие окно: PARTITION BY, которая перезапускает окно на каждой группе, и ORDER BY внутри OVER, которая упорядочивает строки внутри него — и тихо меняет то, что окно содержит.
Практика
Начни сверху. Задачи идут от простого к сложному: вспомнить факт, применить к случаю, затем senior-уровень. Открой, попробуй, потом открой ответ.
Что-то непонятно?
Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.