АналитикаАнализ данныхАналитик данных

В чём преимущество оконной агрегации перед группировкой при расчёте доли заказа в выручке клиента?

В чём преимущество оконной агрегации перед группировкой при расчёте доли заказа в выручке клиента?

Проходите собеседования с ИИ помощником Hintsage

Краткий ответ

Оконная агрегация рассчитывает итог по группе, но сохраняет каждую исходную строку. Поэтому для каждого заказа можно одновременно показать его сумму, общую выручку клиента и долю заказа в этой выручке. Обычная группировка схлопывает строки клиента в одну запись, из-за чего детализация заказов теряется.

Исторический контекст

Реляционная агрегация изначально была ориентирована на получение сводных результатов: например, общей выручки по клиенту или количества заказов по месяцу. Такой подход удобен для отчётов верхнего уровня, но недостаточен для построчного анализа.

Оконные функции появились как механизм аналитических вычислений поверх набора строк без его схлопывания. Они позволяют сопоставлять каждую строку с характеристиками её группы, соседними строками или упорядоченной историей.

Постановка проблемы

Пусть в таблице есть отдельные заказы клиента. Для каждого заказа требуется показать его сумму, общую сумму заказов клиента и отношение первой величины ко второй.

При использовании GROUP BY получится одна строка на клиента. Если затем присоединять этот результат обратно к заказам, запрос станет сложнее, а при неверном ключе соединения можно получить дублирование или потерю строк.

Главный риск — перепутать агрегацию результата с вычислением показателя внутри каждой строки. Это приводит к отчёту, в котором нельзя понять вклад конкретного заказа.

Подробное решение

Оконная агрегация выполняется поверх логической группы, заданной в секции PARTITION BY, но не удаляет строки этой группы. Для заказа клиента окно PARTITION BY client_id содержит все заказы того же клиента.

Минимальный пример:

SELECT client_id, order_id, amount, SUM(amount) OVER (PARTITION BY client_id) AS client_revenue, amount / NULLIF(SUM(amount) OVER (PARTITION BY client_id), 0) AS order_share FROM orders;

SUM(amount) OVER (PARTITION BY client_id) вычисляет общую выручку клиента отдельно для каждой строки. NULLIF защищает от деления на ноль, если в источнике допустимы нулевые суммы.

Если нужна доля заказа внутри периода, условие периода должно быть отражено в наборе данных или в оконной логике. Нельзя бездумно использовать ORDER BY внутри окна: для обычной суммы по всей группе он не нужен, а при его добавлении может измениться рамка окна и получится накопительный итог.

GROUP BY предпочтительнее, когда нужен только один результат на группу: например, месячная выручка клиента. Оконная функция предпочтительнее, когда требуется сохранить детализацию. Она может быть менее эффективной на очень больших наборах, поэтому важно заранее отфильтровать ненужные строки и проверить план выполнения.

Следует также учитывать уровень гранулярности. Если таблица содержит не заказы, а строки товаров внутри заказа, сумма заказа будет повторяться на нескольких строках. Перед расчётом доли нужно выбрать корректный уровень: заказ или товарная строка.

Ситуация из практики

В отчёте по клиентам требовалось найти заказы, которые дали более 30% выручки каждого клиента. Аналитик сначала агрегировал заказы по клиенту, но потерял идентификаторы заказов. Затем он присоединил сводную таблицу обратно к заказам.

Первый вариант с GROUP BY был прост для получения общей выручки, но требовал дополнительного соединения. Это увеличивало объём запроса и риск ошибок при наличии повторяющихся ключей. Вариант с коррелированным подзапросом сохранял детализацию, но мог быть сложнее для оптимизатора и хуже читался.

Выбрали оконную агрегацию: общая выручка вычислялась в строках заказов, после чего рассчитывалась их доля. Результат сохранил одну строку на заказ, упростил фильтрацию по доле и позволил отдельно проверить сумму долей по каждому клиенту.

Что кандидаты часто упускают

  1. Чем оконная функция отличается от обычной агрегатной функции?

Обычная агрегатная функция при использовании с GROUP BY уменьшает число строк до количества групп. Оконная функция вычисляет значение по группе, но возвращает его в каждую исходную строку. Поэтому оконная функция не заменяет группировку во всех задачах: если нужен итог по клиенту одной строкой, GROUP BY остаётся естественным выбором.

  1. Что изменится при добавлении сортировки в оконную сумму?

Сортировка задаёт порядок строк внутри окна. Во многих системах это приводит к расчёту накопительной суммы, особенно если используется стандартная рамка окна, а не вся партиция. Поэтому SUM(amount) OVER (PARTITION BY client_id) означает сумму по всей группе, а вариант с ORDER BY order_date может означать сумму от начала истории до текущего заказа. Для доли заказа в общей выручке сортировка обычно не нужна.

  1. Почему корректный уровень гранулярности важнее самой оконной функции?

Если один заказ представлен пятью товарными строками, оконная сумма по клиенту будет учитывать все товарные строки, а его сумма может повторяться пять раз. В результате доля будет рассчитана не на уровне заказа, а на уровне строк заказа. Сначала нужно агрегировать данные до уровня заказа либо явно определить, что именно должна измерять доля; иначе технически правильный запрос даст бизнес-логически неверный показатель.