ПрограммированиеBackend разработчик, BI аналитик

Как реализовать надежную агрегацию с уникальными условиями (conditional aggregation) в SQL при построении отчётов с несколькими фильтрами? Какие тонкости существуют при смешивании HAVING, CASE и агрегатных функций?

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

Ответ.

Агрегация с условиями — классическая задача для отчетности. Изначально для подсчета разных показателей создавались отдельные запросы с фильтрами. Вскоре появилось более компактное решение — условная агрегация с использованием CASE внутри агрегатных функций (например, SUM(CASE WHEN ...)). Проблема проявляется при совмещении фильтров, группировок и заголовков итогов: можно легко получить неверные суммы либо ошибочно интерпретировать результат.

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

SELECT employee_id, SUM(CASE WHEN status = 'processing' THEN 1 ELSE 0 END) as processing_count, SUM(CASE WHEN status = 'done' THEN 1 ELSE 0 END) as done_count FROM Orders GROUP BY employee_id;

Ключевые особенности:

  • Независимо от WHERE-фильтра все нужные суммы считаются в одном запросе.
  • CASE внутри SUM позволяет строить сложные многопоказательные отчеты.
  • HAVING применяется к уже аггрегированным данным, для пост-фильтрации результата группировки.

Вопросы с подвохом.

Что произойдет, если условие CASE возвращает NULL вместо 0?

Агрегатная функция SUM игнорирует NULL. Поэтому, если писать CASE WHEN ... THEN 1 END, пропущенные строки не учтутся. Лучше всегда явно задавать ELSE 0.

SUM(CASE WHEN status = 'processing' THEN 1 ELSE 0 END)

Может ли WHERE-фильтр изменить итоговую сумму по статусам при условной агрегации?

Да: если основной WHERE ограничивает выборку (например, WHERE region = 'west'), то подсчеты будут вестись только по отфильтрованным данным. Для глобальных итогов используйте подзапрос или убирайте фильтр.

Можно ли использовать HAVING для фильтрации строк до группировки?

Нет. HAVING фильтрует уже сгруппированные данные по агрегатам. Фильтрация исходных строк — через WHERE.

Типовые ошибки и анти-паттерны

  • Отсутствует ELSE, CASE возвращает NULL, итоги неверны.
  • Смешивают WHERE и HAVING, получая не те строки.
  • Используют несколько запросов вместо единого с CASE.

Пример из жизни

Негативный кейс

В аналитическом отчете использовали:

SUM(CASE WHEN status = 'approved' THEN 1 END)

Было много NULL, итоговые суммы занижены. Из-за фильтра WHERE часть нужных строк терялась.

Плюсы:

  • Код короткий.

Минусы:

  • Некорректные результаты, неправильно объяснён отчет бизнесу.

Позитивный кейс

Использовали:

SUM(CASE WHEN status = 'approved' THEN 1 ELSE 0 END)

Код поддерживали в едином запросе, фильтры на итог — через HAVING.

Плюсы:

  • Корректные, прозрачные показатели.
  • Легко добавить новый статус.

Минусы:

  • Запросы становятся длиннее, нужна аккуратность с фильтрами.