Ошибка в отчёте: уникальные значения столбца посчитаны через COUNT DISTINCT , но строки с NULL не вошли. Ка...

Ошибка в отчёте: уникальные значения столбца посчитаны через COUNT(DISTINCT), но строки с NULL не вошли. Как получить NULL как отдельную категорию, не смешав его с реальным значением?

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

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

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

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

Агрегаты SQL предназначены для обобщения множества строк, но SQL различает отсутствие значения и обычное значение. Поэтому COUNT(*) считает строки, а COUNT(столбец) и COUNT(DISTINCT столбец) учитывают только известные, то есть ненулевые значения.

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

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

Пусть в столбце находятся значения A, A, B, NULL, NULL. Ожидаемое количество категорий с учётом NULL равно трём: A, B и NULL.

COUNT(DISTINCT столбец) вернёт два, потому что посчитает только A и B. Ошибка особенно опасна при расчёте числа уникальных клиентов, кодов или статусов: отсутствие идентификатора может незаметно исчезнуть из отчёта.

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

Безопасный общий подход — посчитать уникальные ненулевые значения и отдельно проверить, встречается ли хотя бы один NULL:

SELECT COUNT(DISTINCT category) + CASE WHEN COUNT(*) > COUNT(category) THEN 1 ELSE 0 END AS categories_count FROM products;

COUNT(*) > COUNT(category) означает, что хотя бы одна строка содержит NULL. Если такие строки есть, к числу уникальных ненулевых значений добавляется одна категория NULL.

Другой вариант — заменить NULL маркером через COALESCE, а затем применить COUNT(DISTINCT). Маркер должен быть гарантированно невозможным реальным значением; иначе реальное значение и NULL ошибочно сольются в одну категорию.

Подход с отдельной проверкой NULL обычно надёжнее, потому что не зависит от выбора строкового или числового маркера. Для сложного выражения вместо столбца важно использовать одно и то же выражение в COUNT и проверке наличия NULL.

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

В отчёте по товарам требовалось показать количество уникальных категорий, включая товары с отсутствующей категорией. Вариант с COALESCE(category, 'UNKNOWN') был простым, но создавал риск конфликта: значение UNKNOWN могло реально появиться в данных.

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

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

  1. Что вернёт подсчёт, если все строки имеют NULL?

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

  2. Почему нельзя бездумно использовать COALESCE с фиксированным маркером?

    Если маркер может встретиться в исходных данных, он станет неотличим от преобразованного NULL. Например, реальное значение 'UNKNOWN' и NULL после COALESCE(category, 'UNKNOWN') будут считаться одной категорией. Маркер нужно выбирать вне допустимого домена или использовать отдельную проверку NULL.

  3. Что произойдёт на пустом наборе строк?

    В агрегатном запросе без GROUP BY COUNT(DISTINCT столбец) вернёт ноль, COUNT(*) также вернёт ноль, а проверка наличия NULL будет ложной. Итог составит ноль, что корректно: на пустом наборе нет ни одной категории, включая NULL.