open atlas
↑ К треку
SQL и PostgreSQL вглубь SQL · 04 · 01

Оконные функции против GROUP BY

Оконная функция вычисляет агрегат-подобное значение для каждой строки, не схлопывая результат. GROUP BY схлопывает; OVER() сохраняет все строки. Бери окно, когда нужны и детальные строки, и агрегат в одном запросе.

SQL Middle ◷ 14 min
Уровень
ОсновыJuniorMiddleSenior
Уже знаешь этот юнит? Пройди быструю проверку за минуту →

В тикете на дашборд написано: «покажи каждую продажу, и рядом с ней — какой процент от итога её региона она составила». Джун пишет 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. 1 Начни с детального запроса: SELECT region, salesperson, amount FROM sales
  2. 2 Добавь SUM(amount) OVER (PARTITION BY region) — итог региона, сохранённый на строку
  3. 3 Раздели amount строки на этот оконный итог
  4. 4 Умножь на 100.0 и сделай ROUND, чтобы получить читаемый процент
  5. 5 ORDER BY region, pct DESC, чтобы это подать
Вспомните перед уходом
  1. 01
    В одном предложении: в чём структурная разница между GROUP BY и оконной функцией?
  2. 02
    Нужно показать каждый заказ рядом с пожизненным итогом его клиента. GROUP BY или окно — и почему?
  3. 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-уровень. Открой, попробуй, потом открой ответ.

вспомнитьприменитьуглубить0 из 7 завершено
Связанные уроки

Что-то непонятно?

Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.

хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources2
expand
  1. 01
  2. 02

Trademarks belong to their respective owners. Editorial reference only.