Разберите результат запроса: вернёт ли он строку, если после фильтрации не останется заказов, и какое значе...

Разберите результат запроса: вернёт ли он строку, если после фильтрации не останется заказов, и какое значение будет у total?

WITH orders(amount) AS (
    VALUES (10), (20)
)
SELECT COUNT(*) AS n,
       SUM(amount) AS total
FROM orders
WHERE amount > 100;
Проходите собеседования с ИИ помощником Hintsage

Краткий ответ

Запрос вернёт одну строку: n будет равен 0, а totalNULL. Отсутствие строк после WHERE не означает отсутствие группы: агрегатный запрос без GROUP BY обрабатывает весь результат как одну неявную группу.

Исторический контекст

Агрегатные функции нужны для получения сводных характеристик множества строк: количества, суммы, среднего, минимума и максимума. Такой подход позволяет формировать отчёты непосредственно на стороне базы данных, не передавая все исходные строки приложению.

В SQL агрегаты сочетаются с табличной моделью результатов. Поэтому запрос может вернуть итоговую строку даже тогда, когда входное множество строк пусто: итог относится к пустой неявной группе.

Постановка проблемы

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

Это важно для отчётов, API и проверок в приложении. Если код ожидает одну строку с числовым итогом, значение NULL в SUM может привести к ошибке преобразования, неверному отображению или неправильной арифметике.

Подробное решение

Без GROUP BY SQL формирует одну неявную группу из всего результата FROM и WHERE. Даже если эта группа пуста, агрегатный запрос всё равно возвращает одну итоговую строку.

В данном случае COUNT(*) считает строки группы и поэтому возвращает 0. SUM(amount) не может получить ни одного числового значения, поэтому результатом является NULL, то есть неизвестное или отсутствующее значение, а не числовой ноль.

WITH orders(amount) AS ( VALUES (10), (20) ) SELECT COUNT(*) AS n, SUM(amount) AS total FROM orders WHERE amount > 100;

Ожидаемый результат:

n | total --+------ 0 | NULL

COUNT(*) отличается от COUNT(amount): первый считает строки, а второй — только строки, где amount не равен NULL. Для пустого результата оба значения будут 0, но при наличии строк с одними NULL COUNT(*) будет положительным, а COUNT(amount) — нулём.

Если приложению нужен именно ноль вместо NULL, это следует выразить явно: COALESCE(SUM(amount), 0). Однако подмена NULL на ноль допустима только если бизнес-смысл одинаков: «нет суммы» действительно означает «сумма равна нулю».

При наличии GROUP BY поведение меняется. Группы строятся по значениям ключа группировки, поэтому при полном отсутствии входных строк обычно не возникает ни одной группы и запрос возвращает ноль строк, а не одну строку с агрегатами.

Ситуация из практики

Сервис формирует дневной отчёт по платежам. В день без платежей запрос с SUM(amount) возвращает одну строку, но total имеет значение NULL, и JSON-сериализатор выдаёт null вместо ожидаемого числа.

Можно обработать значение в приложении. Это централизует бизнес-логику приложения, но создаёт риск, что разные потребители отчёта будут по-разному трактовать NULL.

Можно использовать COALESCE(SUM(amount), 0) в SQL. Такой вариант выбран, если контракт отчёта определяет отсутствие платежей как сумму 0; результат стабилен для всех клиентов, а преобразование выполняется рядом с источником данных.

Если же отчёт должен различать «платежей нет» и «сумма не определена из-за качества данных», заменять NULL на ноль нельзя. В таком случае следует вернуть исходный NULL и явно описать его смысл в контракте.

Что кандидаты часто упускают

  1. Что изменится, если добавить GROUP BY customer_id?

    При пустом результате после WHERE не будет ни одной группы, поэтому запрос вернёт ноль строк. Неявная единственная группа существует только у агрегатного запроса без GROUP BY; группировка по атрибуту требует фактических строк, из которых можно сформировать группы.

  2. Почему SUM возвращает NULL, а не 0 для пустой группы?

    SUM вычисляет итог по имеющимся значениям. У пустого множества нет числового результата, который стандарт SQL мог бы автоматически считать суммой; поэтому возвращается NULL. Если прикладная семантика требует нуля, используют COALESCE, но это уже явное решение на уровне запроса.

  3. Чем COUNT(*) отличается от COUNT(amount) при строках со значением NULL?

    COUNT(*) считает все строки, включая строки, где amount равен NULL. COUNT(amount) считает только ненулевые значения этого выражения. Например, для двух строк (10) и (NULL) первый агрегат даст 2, а второй — 1; это различие часто влияет на расчёт заполненности данных.