Сопоставьте результаты двух агрегатов. В таблице есть строка с пропущенным значением: что вернёт запрос и п...

Сопоставьте результаты двух агрегатов. В таблице есть строка с пропущенным значением: что вернёт запрос и почему?

WITH sales(amount) AS (
    VALUES (100), (NULL), (250)
)
SELECT
    COUNT(*) AS row_count,
    COUNT(amount) AS value_count
FROM sales;
Проходите собеседования с ИИ помощником Hintsage

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

Запрос вернёт row_count = 3 и value_count = 2. COUNT(*) считает все строки, а COUNT(amount) — только строки, где вычисленное значение amount не равно NULL.

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

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

Такой подход нужен для отчётов, где важно отдельно видеть общий объём записей и количество строк с заполненными данными. Например, число заказов и число заказов, у которых указана дата оплаты, — это разные показатели.

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

Строка с NULL не исчезает из таблицы и учитывается в COUNT(*). Однако у неё нет значения amount, которое можно посчитать через COUNT(amount).

Неверный выбор агрегата может исказить отчёт: система покажет не количество продаж, а количество продаж с заполненной суммой. Особенно опасно это при расчёте долей, полноты данных и контрольных показателей загрузки.

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

В данном примере есть три строки: со значениями 100, NULL и 250. Поэтому COUNT(*) возвращает 3.

COUNT(amount) логически применяет выражение amount к каждой строке и учитывает только два ненулевых результата. Итог — 2.

WITH sales(amount) AS ( VALUES (100), (NULL), (250) ) SELECT COUNT(*) AS row_count, COUNT(amount) AS value_count FROM sales;

Это правило относится не только к простому столбцу: COUNT(expression) также игнорирует строки, для которых выражение возвращает NULL. Например, COUNT(amount * 2) не посчитает строки с amount IS NULL.

Если нужно посчитать все строки, используют COUNT(*). Если нужно посчитать заполненные значения конкретного столбца или выражения, используют COUNT(column) либо COUNT(expression). Фильтр WHERE работает раньше агрегации и удаляет строки из входного набора целиком, тогда как COUNT(column) оставляет строки в наборе, но не учитывает их в конкретном счётчике.

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

В отчёте по загрузке заказов требовались два показателя: общее число заказов и число заказов с указанной суммой. Вариант с двумя COUNT(*) давал одинаковые значения и скрывал пропуски; вариант с фильтрацией по сумме правильно считал заполненные строки, но одновременно менял набор строк для других агрегатов.

Выбранное решение — использовать COUNT(*) и COUNT(amount) в одном запросе. Оно сохраняет общий набор заказов и позволяет одновременно измерять полноту заполнения суммы, поэтому отдельный запрос или повторную выборку выполнять не нужно.

Если показатель должен считать только строки, удовлетворяющие условию, применяют условное выражение, например COUNT(CASE WHEN amount > 0 THEN 1 END). Такой вариант обычно переносимее, чем диалектные конструкции, но условие нужно формулировать явно и учитывать, что результат CASE без сработавшей ветви может быть NULL.

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

  1. Будет ли COUNT(amount) считать нулевые значения?

Да. Ноль — обычное значение, а не NULL, поэтому строка с amount = 0 будет учтена. COUNT(amount) исключает именно NULL, а не нули, пустые строки или отрицательные числа.

  1. Что вернут агрегаты, если таблица после фильтра не содержит строк?

COUNT(*) и COUNT(column) вернут 0, потому что количество строк и ненулевых значений равно нулю. Это отличается, например, от некоторых других агрегатов: SUM и AVG обычно возвращают NULL, если входной набор пуст или все значения агрегируемого выражения равны NULL.

  1. Чем отличается COUNT(column) от COUNT(DISTINCT column)?

COUNT(column) считает все ненулевые значения, включая повторы. COUNT(DISTINCT column) сначала устраняет дубликаты ненулевых значений, а затем считает оставшиеся; NULL также не учитывается. Поэтому при значениях 10, 10, 20, NULL результаты будут соответственно 3 и 2.