Прямой ответ
Группировка схлопывает строки с равным значением признака в одну строку, поэтому агрегат вычисляется по группе, а не по отдельной строке.
| Элемент запроса | Что делает |
|---|---|
| GROUP BY | Выделяет группы по значению признака |
| Агрегатная функция | Вычисляет значение по строкам группы |
| HAVING | Отбирает группы по значению агрегата |
| WHERE | Отбирает строки до группировки |
SELECT
user_id,
COUNT(*) AS orders,
SUM(amount) AS total,
AVG(amount) AS avg_amount
FROM orders
WHERE order_date >= '2026-01-01'
GROUP BY user_id
HAVING SUM(amount) > 1000;
Фильтр WHERE отбирает строки до группировки, поэтому в агрегат попадают только отобранные строки. О оконных функциях — в статье про оконные функции SQL.
GROUP BY и состав группы
Группировка выделяет строки с равным значением признака, поэтому каждая группа даёт одну строку результата.
Признак группировки. Столбец, по которому выделяются группы, поэтому строки с равным значением попадают в одну группу.
Состав группы. Все строки с равным значением признака входят в группу, поэтому агрегат вычисляется по всем строкам группы.
Несколько признаков. Группировка по нескольким столбцам выделяет группы по сочетанию значений, поэтому число групп растёт.
Практическое следствие: столбец, не входящий в GROUP BY и не обёрнутый агрегатом, не имеет однозначного значения в строке результата, поэтому такой запрос отклоняется.
Отдельный аспект — группировка по выражению. Признак группировки может быть выражением, например приведением даты к периоду, поэтому группы выделяются по вычисленному значению.
Агрегатные функции и учёт пустых значений
Агрегатная функция вычисляет значение по строкам группы, поэтому поведение функции при пустых значениях влияет на результат.
Счёт строк. Функция COUNT со звёздочкой считает все строки группы, а COUNT по столбцу — только строки с непустым значением.
Сумма и среднее. Функции SUM и AVG игнорируют пустые значения, поэтому среднее считается по строкам с заполненным значением.
Минимум и максимум. Функции MIN и MAX возвращают крайнее значение среди непустых, поэтому пустые значения не влияют на результат.
Практическое следствие: среднее, посчитанное по столбцу с пустыми значениями, отличается от среднего по всем строкам группы, поскольку знаменатель сокращается.
HAVING против WHERE
Фильтр HAVING отбирает группы по значению агрегата, поэтому он применяется после группировки, а WHERE — до неё.
Фильтр строк. WHERE отбирает строки до группировки, поэтому в агрегат попадают только отобранные строки.
Фильтр групп. HAVING отбирает группы после вычисления агрегата, поэтому условие может ссылаться на значение агрегата.
Порядок применения. Строки отбираются до группировки, группы отбираются после, поэтому фильтр по агрегату невозможен в WHERE.
Практическое следствие: условие на значение агрегата, записанное в WHERE, отклоняется, поскольку агрегат не вычислен до группировки.
О подзапросах и общих табличных выражениях — в статье про CTE в SQL; о стоимости запроса — в статье про оптимизацию SQL-запросов.
Практика: применение агрегации
1. Определяйте признак группировки по вопросу анализа. Группы соответствуют разрезу данных, поэтому результат отвечает на вопрос.
2. Оборачивайте столбцы вне GROUP BY агрегатом. Столбец без агрегата не имеет однозначного значения в строке результата.
3. Отбирайте строки через WHERE до группировки. В агрегат попадают только отобранные строки, поэтому объём расчёта сокращается.
4. Отбирайте группы через HAVING. Условие на значение агрегата применяется после группировки.
5. Учитывайте пустые значения в агрегатах. Среднее и сумма считаются по строкам с заполненным значением.
6. Используйте COUNT со звёздочкой для счёта строк. Счёт по столбцу исключает строки с пустым значением.
7. Проверяйте план запроса с группировкой. Сортировка по признаку группировки занимает время, поэтому объём данных влияет на длительность.
Рекомендации:
- Не смешивайте WHERE и HAVING по смыслу. Фильтр строк сокращает объём до группировки, фильтр групп отбирает результат после.
- Учитывайте число признаков группировки. Группировка по нескольким столбцам увеличивает число групп и сокращает размер каждой.
- Сопоставляйте агрегат с вопросом анализа. Среднее и сумма отвечают на разные вопросы о группе.
- Фиксируйте длительность запроса в метриках. Рост длительности указывает на рост объёма данных либо на смену плана.
- Пересматриваем группировку при изменении структуры данных. Новый признак может изменить разрез анализа.
Практический сценарий: среднее считалось по неполным строкам
Контекст: отчёт показывал средний чек выше ожидаемого, при этом часть заказов не содержала значения суммы.
- Диагностика. Проверка показала, что столбец суммы содержал пустые значения в части строк. Функция AVG игнорировала такие строки, поэтому знаменатель среднего сокращался и значение росло.
- Причина. Пустые значения в столбце суммы не учитывались агрегатом, поэтому среднее считалось по строкам с заполненным значением, а не по всем заказам.
- Действия. (а) Заменили пустые значения нулями либо явно указали обработку пустых значений в запросе, чтобы знаменатель соответствовал числу заказов; (б) добавили счётчик строк группы, чтобы число заказов было видно рядом со средним; (в) пересчитали среднее и сравнили с прежним значением; (г) проверили долю строк с пустым значением, чтобы оценить масштаб расхождения; (д) настроили проверку заполнения столбца суммы, чтобы пустые значения обнаруживались.
- Результат. Средний чек считается по всем заказам, поэтому значение соответствует фактическому распределению сумм.
Вывод: агрегатные функции игнорируют пустые значения, поэтому среднее считается по строкам с заполненным значением и завышается при неполных данных. Обработка пустых значений и счётчик строк группы делают расчёт соответствующим числу записей.
FAQ
Чем HAVING отличается от WHERE? WHERE отбирает строки до группировки, HAVING отбирает группы после вычисления агрегата. Условие на значение агрегата возможно только в HAVING.
Почему столбец без агрегата отклоняется при GROUP BY? Строки группы схлопываются в одну строку результата, поэтому столбец без агрегата не имеет однозначного значения.
Как COUNT со звёздочкой отличается от COUNT по столбцу? COUNT со звёздочкой считает все строки группы, COUNT по столбцу — только строки с непустым значением.
Почему среднее может быть завышено? Агрегат игнорирует пустые значения, поэтому знаменатель сокращается и среднее считается по неполному набору строк.
Источники: PostgreSQL aggregate tutorial, aggregate functions, GROUP BY — ссылки в шапке.