1. Продвинутая фильтрация и агрегация данных: группировка и условия в аналитических отчетах
Продвинутая фильтрация и агрегация данных: группировка и условия в аналитических отчетах
Представьте, что вы аналитик в крупном маркетплейсе. У вас есть таблица с миллионами строк о продажах за год. Руководство задает вопрос: «Какой регион принес больше всего прибыли в категории электроники в прошлом квартале, если учитывать только заказы дороже 5000 рублей?». Базовый навык SELECT * здесь бессилен — он просто вывалит на вас гору сырых данных. Чтобы ответить на вопрос бизнеса, нужно научиться не просто извлекать данные, а «схлопывать» их, превращая хаос транзакций в стройные показатели.
Логика агрегации: от строк к смыслам
Агрегация — это процесс объединения множества значений в одно результирующее. В SQL за это отвечают функции, которые работают с набором строк: COUNT, SUM, AVG, MIN и MAX. Однако главная сложность для аналитика заключается не в вызове функции, а в понимании контекста, в котором она применяется. Когда мы считаем SUM(amount), SQL должен понимать, по какому признаку суммировать: по всей таблице, по каждому пользователю или по каждой дате.
Ключевым инструментом здесь выступает оператор GROUP BY. Он работает как сортировщик на почте: сначала распределяет все письма по мешкам (группам), а затем выполняет действие над содержимым каждого мешка. Если вы указываете GROUP BY category, база данных создаст невидимые границы между товарами разных типов и только потом применит к ним, например, AVG(price).
> Важное правило: Все столбцы, которые вы перечисляете в SELECT и которые не обернуты в агрегатную функцию, обязаны присутствовать в блоке GROUP BY. Игнорирование этого правила — самая частая причина ошибок в аналитических запросах.
Рассмотрим пример с интернет-магазином. Допустим, у нас есть таблица orders со столбцами user_id, order_date и revenue. Если мы хотим узнать средний чек каждого клиента, запрос будет выглядеть так:
SELECT user_id, AVG(revenue) FROM orders GROUP BY user_id. Здесь база данных сначала найдет все строки одного пользователя, «схлопнет» их и выдаст одно число для каждого ID.
Тонкая настройка фильтров: WHERE против HAVING
Одной из самых больших ловушек для начинающих аналитиков является различие между WHERE и HAVING. Оба оператора фильтруют данные, но делают это в разные моменты «жизненного цикла» запроса. Представьте процесс приготовления кофе: WHERE — это выбор качественных зерен до помола, а HAVING — это проверка температуры уже готового напитка.
Оператор WHERE отсекает лишние строки до того, как начнется группировка и расчеты. Если вы хотите посчитать продажи только за декабрь, вы используете WHERE order_date >= '2023-12-01'. Это экономит ресурсы базы данных, так как ей не приходится обрабатывать ненужные данные.
Оператор HAVING применяется после того, как данные были сгруппированы. Он работает с результатами агрегатных функций. Если вам нужно найти не просто всех клиентов, а только «китов» (тех, кто суммарно потратил более 100 000 руб.), вы не сможете использовать WHERE SUM(revenue) > 100000, потому что на этапе WHERE база еще не знает сумму. Правильный путь — использовать HAVING SUM(revenue) > 100000.
| Оператор | Когда применяется | С чем работает | Пример использования | | :--- | :--- | :--- | :--- | | WHERE | До группировки | Со значениями в конкретных строках | Исключить тестовые заказы | | HAVING | После группировки | С результатами функций (SUM, COUNT и т.д.) | Оставить категории с выручкой > 1 млн |
Глубокий разбор: Анализ эффективности маркетинговых каналов
Разберем сложную задачу. Допустим, у нас есть таблица ad_campaigns с данными о затратах на рекламу и таблица conversions с данными о покупках. Нам нужно понять, какие рекламные каналы принесли нам более 50 конверсий в марте, при этом средняя стоимость привлечения (CPA) должна быть ниже 500 рублей.
Шаг 1: Фильтрация исходных данных.
Сначала мы отсекаем все записи, не относящиеся к марту. Это делается через WHERE. Мы также исключаем «мусорные» клики, где стоимость привлечения была нулевой (технические ошибки).
Шаг 2: Группировка.
Мы группируем данные по столбцу utm_source (источник трафика). Теперь база данных «видит» отдельно Facebook, Google и TikTok.
Шаг 3: Расчет метрик.
Внутри каждой группы мы считаем COUNT(conversion_id) для общего количества покупок и SUM(cost) / COUNT(conversion_id) для вычисления средней стоимости одной покупки.
Шаг 4: Итоговая фильтрация агрегатов.
Теперь мы применяем HAVING. Нам нужно оставить только те группы, где количество больше 50, а вычисленный CPA меньше 500.
Шаг 5: Сортировка.
В аналитике важно видеть лидеров первыми. Используем ORDER BY по убыванию прибыли.
Пример в цифрах: если канал 'Google_Ads' принес 100 покупок с общими затратами 40 000 руб., то на шаге 3 мы получим CPA = 400. Условие HAVING (100 > 50 И 400 < 500) выполняется, и этот канал попадет в наш отчет. Если же 'FB_Ads' принес 200 покупок, но затраты составили 120 000 руб. (CPA = 600), он будет отсеян на этапе HAVING.
Работа с неочевидными агрегатами: DISTINCT и NULL
В бизнес-аналитике часто возникает задача посчитать количество уникальных сущностей. Например, сколько уникальных пользователей совершили покупки в конкретный день. Обычный COUNT(user_id) может выдать ложный результат, если один и тот же человек купил три раза — он будет посчитан трижды. Для чистоты данных используется COUNT(DISTINCT user_id).
Еще один подводный камень — это значения NULL (пустоты). Важно помнить:
COUNT(*) считает абсолютно все строки, включая те, где все поля пустые.COUNT(column_name) считает только те строки, где в указанном столбце есть данные (пропускает NULL).SUM или AVG просто игнорируют NULL. Если у вас 10 строк, в 5 из которых revenue равен 100, а в других 5 — NULL, то AVG(revenue) вернет 100, а не 50. Это критично для расчета среднего чека или ARPU (Average Revenue Per User).Практические сценарии использования CASE внутри агрегатов
Настоящая магия аналитики начинается, когда мы комбинируем агрегатные функции с оператором CASE. Это позволяет создавать сложные отчеты в одну таблицу (так называемый «pivot» или разворот данных).
Представьте, что вам нужно вывести отчет, где в строках — даты, а в столбцах — выручка отдельно по мобильным устройствам и отдельно по десктопам. Вместо того чтобы делать два разных запроса, мы пишем:
SUM(CASE WHEN device = 'mobile' THEN revenue ELSE 0 END) AS mobile_rev.
Этот прием позволяет «на лету» сегментировать данные внутри одной группы. Например, так можно посчитать долю возвратов в общем объеме продаж: мы суммируем только те строки, где статус заказа 'returned', и делим на общее количество строк.
> Если вы хотите быстро оценить качество данных, используйте связку COUNT(column) и COUNT(*). Если числа сильно различаются, значит, в данных много пропусков, и ваши средние значения (AVG) могут быть искажены.
Если из этой главы запомнить три вещи — это:
WHERE фильтрует сырые строки, а HAVING — уже посчитанные итоги (агрегаты).GROUP BY обязателен для любого столбца в SELECT, который не находится внутри функции.COUNT(DISTINCT ...) — ваш лучший друг для подсчета реального количества клиентов или сессий, исключающий дубли.