SQL CASE, COALESCE и NULL: как не сломать сегменты и метрики
Разбираем NULL, CASE и COALESCE на задачах аналитика: как группировать пустые значения, создавать сегменты и показывать ноль вместо пропущенных данных.

Пустое значение в отчёте редко остаётся просто пустым. Оно может означать неизвестный канал, отсутствие платежа, ещё не наступивший период или ошибку загрузки. SQL различает NULL и обычный текст, поэтому неосторожное условие легко убирает часть пользователей из выборки или превращает “нет данных” в ложный ноль.
Коротко
NULL означает отсутствие известного значения, а не пустую строку и не ноль. `CASE` создаёт новое значение по условиям, `COALESCE` выбирает первое непустое значение. Вместе они помогают сделать бизнес-правила явными и сохранить правильный знаменатель метрики.
- Для проверки NULL используй `IS NULL` или `IS NOT NULL`, а не `= NULL`.
- `COALESCE(value, 0)` превращает пропуск в ноль только там, где это действительно означает ноль.
- `CASE` полезен для сегментов, флагов и читаемых категорий.
- Не смешивай неизвестный канал и органический канал без отдельного решения.
- Проверяй, изменился ли знаменатель после фильтра по NULL.
NULL — не пустая строка
В таблице `users` у части пользователей может быть неизвестный channel. Это не то же самое, что `channel = ''`, и не то же самое, что `channel = 'organic'`. NULL говорит: значение неизвестно или не было записано.
| Значение | Смысл | Как проверить |
|---|---|---|
| NULL | значение отсутствует или неизвестно | channel IS NULL |
| пустая строка | поле записано, но строка пустая | channel = '' |
| 0 | числовой ноль | amount = 0 |
select
count(*) filter (where channel is null) as missing_channel,
count(*) filter (where channel = '') as empty_channel,
count(*) as all_users
from users;Почему channel = NULL не работает
SQL использует трёхзначную логику: условие может быть TRUE, FALSE или UNKNOWN. Сравнение любого значения с NULL не возвращает TRUE, поэтому `where channel = null` не найдёт пропуски. Нужна специальная проверка `IS NULL`.
select
user_id,
signup_date,
channel
from users
where channel is null
order by signup_date, user_id;Если после добавления WHERE количество пользователей стало меньше, чем ожидалось, отдельно посмотри NULL-группу. Пропуски часто прячутся именно там.
CASE: создать сегмент из условия
`CASE` возвращает значение в зависимости от условий. Он подходит, когда в исходной таблице нет готовой бизнес-категории: можно разделить пользователей по каналу, возрасту когорты или активности. Важно задать `ELSE`, иначе все неописанные случаи станут NULL.
select
user_id,
channel,
case
when channel = 'paid' then 'paid'
when channel in ('organic', 'referral') then 'owned'
when channel is null then 'unknown'
else 'other'
end as channel_group
from users
order by user_id;- Условия CASE проверяются сверху вниз.
- Первое совпавшее условие определяет результат.
- `ELSE` защищает от неожиданных NULL в новой колонке.
- Категории должны быть взаимоисключающими, если потом считаешь их сумму.
COALESCE: показать отсутствие как ноль
После LEFT JOIN у пользователя без платежа поле `amount` будет NULL. Для отчёта о выручке это обычно нужно показать как 0, но только после того, как ты сохранил пользователя в знаменателе. `COALESCE` выбирает первое значение, которое не является NULL.
select
u.user_id,
u.channel,
coalesce(sum(p.amount), 0) as revenue
from users as u
left join payments as p using (user_id)
group by u.user_id, u.channel
order by revenue desc, u.user_id;Для суммы платежей отсутствие строк часто можно показать нулём. Для даты последнего платежа NULL обычно означает “платежа не было” и не должен превращаться в произвольную дату.
Как не потерять сегмент в GROUP BY
GROUP BY соберёт все NULL-значения в одну группу. Это полезно для контроля качества, но не всегда хорошо для бизнес-отчёта. Сначала покажи unknown отдельно, оцени размер группы и только потом решай, можно ли распределить её по другим категориям.
| Ситуация | Рабочее решение |
|---|---|
| Канал неизвестен | показать unknown и исправить трекинг |
| Платежей нет | COALESCE(sum(amount), 0) для денежной метрики |
| Дата возврата неизвестна | оставить NULL и не подставлять фиктивную дату |
| Категория не предусмотрена | CASE с явным ELSE other |
select
coalesce(channel, 'unknown') as channel_group,
count(*) as users_count
from users
group by coalesce(channel, 'unknown')
order by users_count desc;Частые ошибки
Большинство проблем возникает не из-за синтаксиса, а из-за подмены смысла. Ноль, неизвестность и отсутствие строки могут выглядеть одинаково в таблице, но приводят к разным продуктовым выводам.
- Проверять NULL через `= NULL`.
- Использовать COALESCE для дат и создавать ложные значения.
- Забыть ELSE в CASE и получить новые пропуски.
- Отфильтровать NULL до расчёта и уменьшить знаменатель.
- Склеить unknown с organic без проверки качества канала.
- Считать сумму сегментов и не заметить, что часть строк ушла в NULL.
Мини-практика и следующий шаг
Найди пользователей с неизвестным channel, затем создай channel_group через CASE. После этого присоедини payments и выведи ноль для пользователей без платежей. В конце сравни сумму пользователей по группам с общим количеством строк.
Материалы по теме
SQL-проверки качества данных: дубли, пропуски и скачки метрик
Практический чеклист SQL-проверок перед дашбордом: найти дубли, пропущенные ключи, события без пользователей и неожиданные скачки дневного объёма.
Читать материал
SQL даты и время в аналитике: периоды, недели и возраст пользователя
Как работать с датами в SQL: строить полные периоды, группировать события по дням и неделям, считать возраст пользователя и не ошибаться на границах интервалов.
Читать материал
Когорты и retention в SQL: как считать возвращаемость по датам
Разбираем когортный retention в SQL: как определить дату старта, посчитать D1 и D7, собрать матрицу возвращаемости и сравнить каналы без самообмана.
Читать материал