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

Как реализовать условную агрегацию в SQL (например, посчитать суммы по отдельным статусам в одной выборке) и какие подводные камни здесь существуют?

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

Ответ.

Условная агрегация используется, когда требуется агрегировать значения (например, сумму или количество) по различным условиям в рамках одного запроса. Исторически, для достижения подобных целей SQL-разработчикам приходилось писать множественные подзапросы или делать джойны и группировки отдельно по каждой категории, что вело к менее читаемому и менее производительному коду.

Проблема заключалась в трудности одновременного вычисления нескольких агрегатов с разными условиями — каждый отдельный агрегат обычно требует фильтрации, и простое применение SUM() или COUNT() не учитывает нужное условие внутри агрегатной функции.

Решение — использование конструкции CASE внутри агрегатной функции, что позволяет "разделять" агрегаты по разным условиям на лету, не прибегая к множественным объединениям:

Пример кода:

SELECT department, SUM(CASE WHEN status = 'approved' THEN amount ELSE 0 END) AS approved_sum, SUM(CASE WHEN status = 'pending' THEN amount ELSE 0 END) AS pending_sum, COUNT(CASE WHEN status = 'rejected' THEN 1 END) AS rejected_count FROM payments GROUP BY department;

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

  • Позволяет агрегировать по нескольким условиям одновременно в одном запросе.
  • Работает во всех диалектах SQL (точная поддержка синтаксиса может отличаться).
  • Может использоваться не только с SUM, но и с COUNT, AVG и др.

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

Можно ли обойтись без конструкции CASE для условной агрегации?

Некорректный ответ — использовать WHERE в запросе напрямую вместе с агрегатной функцией. На самом деле WHERE фильтрует строки до агрегации, а не внутри каждой агрегатной колонки.

Пример кода (неверный способ):

SELECT COUNT(*) FROM payments WHERE status = 'approved'; SELECT COUNT(*) FROM payments WHERE status = 'pending';

Эти запросы нельзя склеить в одну строку результата по разным условиям без CASE.

Что произойдет, если использовать NULL внутри условного выражения?

Если CASE не возвращает альтернативное значение, то по умолчанию оно будет NULL, а агрегатная функция будет игнорировать NULL.

Пример кода:

SUM(CASE WHEN status = 'approved' THEN amount END) -- Если статус не 'approved', то NULL, и строка игнорируется в SUM

Можно ли использовать IF вместо CASE?

В некоторых диалектах SQL (например, MySQL) это возможно, но такой код становится непереносимым между разными базами данных. В универсальных запросах всегда применяйте CASE.

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

  • Использование WHERE вместо вложенной логики внутри CASE приводит к необходимости выполнять множественные одинарные запросы, что ухудшает производительность.
  • Забыв задать ELSE 0 у CASE, можно получить некорректное количество/сумму из-за пропусков по NULL.
  • Копипаст CASE без нужной проверки приводит к логическим ошибкам и неверным расчетам.

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

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

Аналитик попытался посчитать суммы по каждому статусу, написав несколько отдельных подзапросов. Внешний отчет стал сложным, добавление новых статусов потребовало переписывания кода каждый раз.

Плюсы:

  • Просто реализовать для одной метрики. Минусы:
  • Трудно масштабировать, низкая производительность и повторяемость кода.

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

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

Плюсы:

  • Высокая читаемость, простота масштабирования, производительность. Минусы:
  • Требует знания синтаксиса CASE и его нюансов в разных СУБД.