1. Продвинутая агрегация и группировка данных
Продвинутая агрегация и группировка данных
В реальной бизнес-аналитике базового оператора GROUP BY быстро становится недостаточно. Когда перед вами стоит задача собрать сложный финансовый отчет, посчитать конверсию по когортам или подготовить данные для дашборда, стандартная группировка заставляет писать громоздкие запросы с множеством подзапросов.
Современный PostgreSQL предлагает мощный арсенал инструментов, которые позволяют выполнять сложную аналитику прямо на уровне базы данных. Это снижает нагрузку на сервер приложения, ускоряет получение результатов и делает код более читаемым.
Точечная агрегация с помощью FILTER
Часто возникает задача посчитать агрегированные метрики (сумму, количество, среднее) не для всех строк в группе, а только для тех, которые соответствуют определенному условию.
Представьте, что вы анализируете продажи по категориям товаров. Вам нужно в одном отчете показать общую выручку и выручку только от крупных заказов (где чек долл.).
Новички обычно решают эту задачу через конструкцию CASE WHEN:
Этот подход работает, но он избыточен и трудно читается при большом количестве условий. В PostgreSQL существует элегантное решение — модификатор FILTER.
> Использование конструкции FILTER делает запросы не только более лаконичными, но и зачастую более быстрыми, так как планировщик базы данных эффективнее обрабатывает такие выражения по сравнению с условными конструкциями внутри агрегатных функций.
Перепишем наш запрос с использованием нового синтаксиса:
Здесь мы в рамках одного прохода по таблице считаем общую сумму, сумму крупных чеков и количество возвратов. Это идеальный паттерн для построения аналитических витрин данных.
Многоуровневые итоги: ROLLUP
В бизнес-отчетах редко нужна просто плоская таблица с данными. Руководители хотят видеть подытоги (subtotals) по категориям и общий итог (grand total) в самом конце.
Допустим, у нас есть данные о продажах по регионам и городам. Если использовать обычный GROUP BY region, city, мы получим продажи для каждой пары "регион-город". Но как добавить строку с суммой по всему региону и строку с суммой по всей стране?
Без продвинутых функций пришлось бы писать три отдельных запроса и объединять их через UNION ALL. Оператор ROLLUP делает это автоматически, создавая иерархическую группировку.
Результат этого запроса будет содержать три уровня детализации:
NULL, 12000).NULL, NULL, 45000).!Иерархия агрегации данных через ROLLUP
Оператор ROLLUP всегда «сворачивает» данные справа налево. Если вы указали ROLLUP(A, B, C), база данных посчитает итоги для (A, B, C), затем для (A, B), затем для (A) и, наконец, общий итог ().
Все возможные комбинации: CUBE
Если ROLLUP строит строгую иерархию, то оператор CUBE генерирует итоги по всем возможным комбинациям столбцов. Это незаменимый инструмент для создания кросс-таблиц (сводных таблиц), где аналитику нужно смотреть на данные под любым углом.
Представьте кубик Рубика: вы можете посмотреть на него сверху, сбоку или в разрезе. Точно так же CUBE позволяет крутить данные.
Возьмем продажи по продуктам и каналам привлечения (онлайн/офлайн):
Этот запрос вернет:
| Оператор | Суть | Когда использовать |
| :--- | :--- | :--- |
| GROUP BY | Одна плоская группировка | Базовые метрики, где не нужны итоги |
| ROLLUP | Иерархические итоги (справа налево) | Финансовые отчеты, география (Страна Город) |
| CUBE | Все комбинации измерений | Сводные таблицы, многомерный анализ данных |
Идентификация итогов: функция GROUPING
При использовании ROLLUP и CUBE в результирующей таблице появляются значения NULL. Они означают «Все значения» для данного столбца. Но возникает проблема: что если в самих исходных данных есть реальный NULL? Например, город не был указан при оформлении заказа.
Как отличить NULL, означающий «Итог по всем городам», от NULL, означающего «Город неизвестен»? Для этого применяется функция GROUPING().
Она возвращает 1, если значение NULL было сгенерировано оператором ROLLUP/CUBE (то есть это итог), и 0, если это обычные данные.
В этом примере мы используем функцию COALESCE, которая возвращает первое непустое значение. Если GROUPING показывает, что это строка итога, мы выводим красивый текст вместо пустоты. Это позволяет отдавать из базы данных готовый, отформатированный отчет, который не требует дополнительной обработки на стороне frontend.
Агрегация в массивы и JSON
В современной разработке базы данных часто общаются напрямую с API. Если вам нужно передать список заказов пользователя, классический JOIN вернет множество строк с дублирующимися данными пользователя.
PostgreSQL позволяет агрегировать строки не только в числа, но и в структуры данных — массивы и JSON-объекты. Для этого используются функции array_agg и json_agg.
Допустим, нам нужно получить список пользователей, где для каждого пользователя будет массив всех его покупок:
Что здесь происходит?
json_build_object собирает данные каждого заказа в JSON-объект.json_agg берет все эти объекты для конкретного пользователя и складывает их в единый JSON-массив.В результате приложение получает одну строку на одного пользователя, а внутри колонки orders_history лежит готовый JSON, который можно сразу отправить на клиентскую часть. Это радикально сокращает объем передаваемых по сети данных и упрощает код приложения.
Освоение этих паттернов переводит вас из категории людей, которые «умеют писать селекты», в инженеров данных, способных решать архитектурные задачи и оптимизировать бизнес-процессы на самом глубоком уровне.