Ошибка в отчёте: уникальные значения столбца посчитаны через COUNT(DISTINCT), но строки с NULL не вошли. Как получить NULL как отдельную категорию, не смешав его с реальным значением?
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:
COUNT(*) > COUNT(category) означает, что хотя бы одна строка содержит NULL. Если такие строки есть, к числу уникальных ненулевых значений добавляется одна категория NULL.
Другой вариант — заменить NULL маркером через COALESCE, а затем применить COUNT(DISTINCT). Маркер должен быть гарантированно невозможным реальным значением; иначе реальное значение и NULL ошибочно сольются в одну категорию.
Подход с отдельной проверкой NULL обычно надёжнее, потому что не зависит от выбора строкового или числового маркера. Для сложного выражения вместо столбца важно использовать одно и то же выражение в COUNT и проверке наличия NULL.
В отчёте по товарам требовалось показать количество уникальных категорий, включая товары с отсутствующей категорией. Вариант с COALESCE(category, 'UNKNOWN') был простым, но создавал риск конфликта: значение UNKNOWN могло реально появиться в данных.
Вариант с предварительным удалением NULL также был неприемлем, поскольку искажал бизнес-показатель. Был выбран подсчёт уникальных ненулевых категорий с отдельным добавлением единицы при наличии NULL. В результате NULL учитывался как одна категория, а реальные значения не могли случайно объединиться с ним.
Что вернёт подсчёт, если все строки имеют NULL?
COUNT(DISTINCT столбец) вернёт ноль, поскольку ненулевых значений нет. Проверка COUNT(*) > COUNT(столбец) вернёт истину, поэтому итоговая формула даст одну категорию — NULL.
Почему нельзя бездумно использовать COALESCE с фиксированным маркером?
Если маркер может встретиться в исходных данных, он станет неотличим от преобразованного NULL. Например, реальное значение 'UNKNOWN' и NULL после COALESCE(category, 'UNKNOWN') будут считаться одной категорией. Маркер нужно выбирать вне допустимого домена или использовать отдельную проверку NULL.
Что произойдёт на пустом наборе строк?
В агрегатном запросе без GROUP BY COUNT(DISTINCT столбец) вернёт ноль, COUNT(*) также вернёт ноль, а проверка наличия NULL будет ложной. Итог составит ноль, что корректно: на пустом наборе нет ни одной категории, включая NULL.