Проанализируйте запрос: фильтр заведомо отбрасывает все строки. Какой результат вернёт агрегат без GROUP BY?
WITH sales(amount) AS (
VALUES (100), (250)
)
SELECT COUNT(*) AS sale_count
FROM sales
WHERE amount > 1000;
Запрос вернёт одну строку со значением sale_count = 0. Агрегат без GROUP BY формирует одну общую группу даже тогда, когда после фильтрации в ней не осталось исходных строк.
Агрегатные функции SQL предназначены для получения сводного результата по набору строк. Чтобы такой запрос имел предсказуемый результат и для непустого, и для пустого набора, SQL определяет отдельную семантику агрегирования без GROUP BY: создаётся одна неявная группа.
Это позволяет получать отчёты со значением 0, когда подходящих записей нет, вместо отсутствия результата как такового.
Фильтрация выполняется до агрегирования. В примере условие amount > 1000 удаляет обе строки, поэтому агрегат получает пустой входной набор.
Важно отличать две ситуации: запрос без GROUP BY возвращает одну строку со сводным значением, а группировка по столбцу не создаёт группу для отсутствующих значений. Ошибка в этом различии может привести к неверной обработке пустых отчётных периодов.
Логически запрос обрабатывается так:
FROM формирует две строки из CTE.WHERE оставляет ноль строк.GROUP BY означает одну неявную общую группу.COUNT(*) считает строки в этой группе и получает 0.SELECT возвращает одну строку результата.COUNT(*) считает строки, поэтому для пустого входа возвращает ноль. Это отличается от многих других агрегатов: например, SUM(amount) для пустого набора обычно возвращает NULL, поскольку сумма не определена без исходных значений.
Если добавить GROUP BY amount, строки для групп, которых нет во входном наборе, не появятся вовсе. Поэтому для отчёта, где обязательна одна строка на каждый запрошенный период, часто отдельно формируют набор периодов и присоединяют к нему агрегированные данные.
Сервис строит ежедневный отчёт по продажам. В день без продаж запрос с COUNT(*) без группировки возвращает одну строку с нулём, что удобно для API и итогового отчёта.
Вариант с группировкой непосредственно по дате проще для обычных дней, но не возвращает даты, для которых нет строк продаж. Вариант с календарной таблицей требует дополнительного соединения, зато позволяет показать все даты, включая дни с нулевыми показателями.
Для общего итога выбран запрос без GROUP BY: он проще и гарантированно возвращает одну строку. Для временного ряда выбрана календарная таблица с LEFT JOIN, поскольку бизнес-требование — отображать и пустые дни.
1. Вернёт ли такой запрос ноль строк, если после WHERE ничего не осталось?
Нет. При отсутствии GROUP BY агрегирование создаёт одну неявную группу, поэтому результат содержит одну строку. Нулевым будет значение COUNT(*), а не количество строк результата.
2. Чем отличается COUNT(*) от COUNT(column) на пустом наборе?
На полностью пустом входе оба выражения возвращают 0. На непустом наборе COUNT(*) считает все строки, а COUNT(column) — только строки, где указанное выражение не равно NULL.
3. Почему группировка по столбцу не возвращает строку с нулём для отсутствующей группы?
GROUP BY формирует группы только из фактически имеющихся строк. Если после фильтрации строк нет, то нет и значений, по которым можно создать группы. Для явного отображения нулевых групп нужно предоставить их отдельным источником, например календарём или таблицей всех категорий, и выполнить LEFT JOIN.