Выражения и NULL: вопросы для собеседования (часть 4)

NULL в SQL ведёт себя неинтуитивно: NULL = NULL возвращает не TRUE, агрегатные функции игнорируют NULL, а CASE WHEN требует аккуратного обращения с пустыми значениями. Интервьюеры любят задачи-ловушки на тему IS NULL, COALESCE, NULLIF и трёхзначной логики. Непонимание NULL — одна из самых частых ошибок даже у опытных кандидатов.

Агрегация, GROUP BY и HAVINGДаты и времяEXPLAIN и оптимизацияJOIN и операции множествОсновы SQL-запросовСтроки и приведение типовПодзапросы и CTEОконные функции

Вопросы 1620 из 20

16В таблице `orders` поле `promo_code` может быть `NULL`. Что произойдёт со строкой, где `promo_code = NULL`, при фильтре `WHERE promo_code <> 'NONE'`?
AСтрока попадёт в результат, потому что `NULL` считается «не равным» строке `'NONE'`
BСтрока не попадёт в результат, потому что выражение `promo_code <> 'NONE'` даёт `UNKNOWN`
CСтрока попадёт в результат только если в запросе указан `ORDER BY promo_code`
DЗапрос упадёт с ошибкой, потому что оператор `<>` нельзя применять к `NULL`
Ответ: Сравнение с `NULL` даёт `UNKNOWN`, а в `WHERE` остаются только строки со значением `TRUE`.

В трёхзначной логике сравнение `NULL <> 'NONE'` возвращает не `TRUE` и не `FALSE`, а `UNKNOWN`. Условие `WHERE` пропускает только `TRUE`, поэтому строки с `NULL` отбрасываются. Чтобы такие строки явно включить, добавляют `OR promo_code IS NULL` или используют `COALESCE(promo_code, '...')` в зависимости от смысла фильтра.

Подробный разбор →
17Дано выражение: `CASE WHEN status = 'paid' THEN amount ELSE 0 END`. Что вернётся, если `status` равно `NULL`, а `amount` равно 500?
AВернётся `NULL`, потому что любое участие `NULL` в выражении автоматически делает результат неопределённым
BВернётся `0`: условие `status = 'paid'` даёт `UNKNOWN` из-за `NULL`, и поэтому сработает ветка `ELSE`
CВернётся `500`: значение `amount` подставится напрямую, ведь поле `status` не равно строке `'paid'`
DБудет ошибка выполнения, потому что сравнение со значением `NULL` запрещено в выражении `CASE`
Ответ: Если условие `WHEN` даёт не `TRUE` (включая `UNKNOWN` из-за `NULL`), сработает ветка `ELSE`.

При `status = NULL` сравнение `status = 'paid'` возвращает `UNKNOWN`, а не `TRUE` или `FALSE`. В выражении `CASE` ветка `WHEN` срабатывает только при `TRUE`, поэтому в нашем случае выберется `ELSE 0`, и результат будет `0`, даже если `amount` равно 500. Чтобы явно учитывать пропуски, можно добавить отдельную ветку `WHEN status IS NULL THEN ...`. Запрета на сравнение с `NULL` в `CASE` нет — оно просто даёт `UNKNOWN`.

Подробный разбор →
18Нужно посчитать количество оплаченных заказов (значение столбца `status` равно `'paid'`). Какое выражение корректно вернёт число оплаченных, если в этом столбце бывают `NULL`?
A`COUNT(CASE WHEN status = 'paid' THEN 1 ELSE 0 END)`: ветка `ELSE 0` учитывается в счёте как не-`NULL` значение
B`COUNT(status = 'paid')`: выражение возвращает логический результат, который агрегатом считается напрямую
C`COUNT(CASE WHEN status = 'paid' THEN 1 END)`: для не-`paid` веток получится `NULL`, в счёт попадут только нужные
D`COUNT(COALESCE(status, 'paid'))`: значение `NULL` заменяется на `'paid'` ещё до подсчёта строк агрегатом
Ответ: `COUNT(column)` считает не-`NULL`, поэтому удобно делать `COUNT(CASE WHEN ... THEN 1 END)` — там `NULL` для не-подходящих строк.

`COUNT` считает количество не-`NULL` значений. Если использовать `COUNT(CASE WHEN status = 'paid' THEN 1 END)`, то для оплаченных вернётся `1`, а для остальных (включая `NULL`) — `NULL`, поэтому будут посчитаны только оплаченные. Вариант с `ELSE 0` неверен: `0` — это не `NULL`, значит будут посчитаны все строки. `COALESCE(status, 'paid')` подменяет `NULL` на `'paid'` и тоже искажает счёт.

Подробный разбор →
19Есть `LEFT JOIN` пользователей с их платежами. Если у пользователя нет платежей, `payments.amount` будет `NULL`. Как правильно посчитать «выручку на пользователя», трактуя отсутствие платежей как 0?
A`SUM(payments.amount) / COUNT(users.user_id)`
B`SUM(users.user_id) / COUNT(COALESCE(payments.amount, 0))`
C`COUNT(payments.amount) / SUM(users.user_id)`
D`SUM(COALESCE(payments.amount, 0)) / COUNT(users.user_id)`
Ответ: После `LEFT JOIN` отсутствующие значения становятся `NULL`; для метрик их подставляют как 0 через `COALESCE` перед агрегированием.

При `LEFT JOIN` строки без совпадений получают `NULL` в полях правой таблицы. Если отсутствие платежей трактуется как 0, безопаснее заменить `payments.amount` на 0 до суммирования: `SUM(COALESCE(payments.amount, 0))`. Деление на `COUNT(users.user_id)` даёт среднее по всем пользователям, включая тех, у кого платежей не было. `SUM` сам по себе пропускает `NULL`, а формулы с `SUM(user_id)` или `COUNT(amount) / SUM(user_id)` суммируют идентификаторы и считают не ту величину.

Подробный разбор →
20Нужно пометить пользователей без заказов как `'no_orders'`. Есть поле `orders_count`: для некоторых оно `NULL` (данные не загрузились), а для реально без заказов — 0. Какой `CASE` корректнее различит эти ситуации?
A`CASE WHEN orders_count = 0 THEN 'no_orders' ELSE 'has_orders' END` с одной общей веткой `ELSE`
B`CASE WHEN orders_count IS NULL THEN 'no_orders' ELSE 'has_orders' END` с веткой `IS NULL`
C`CASE WHEN orders_count = 0 THEN 'no_orders' WHEN orders_count IS NULL THEN 'unknown' ELSE 'has_orders' END`
D`CASE WHEN orders_count <> 0 THEN 'has_orders' ELSE 'no_orders' END` с проверкой через оператор `<>`
Ответ: Если `NULL` и 0 имеют разный смысл, их нужно обрабатывать разными ветками `CASE`.

Здесь 0 означает «точно нет заказов», а `NULL` означает «неизвестно/нет данных». Если их смешать, аналитика будет неверной. Корректный подход — отдельная ветка для 0 и отдельная для `IS NULL`: `CASE WHEN orders_count = 0 THEN 'no_orders' WHEN orders_count IS NULL THEN 'unknown' ELSE 'has_orders' END`. Сравнения вроде `<>` или `=` с `NULL` всегда возвращают `NULL` и неявно отправляют такие записи в `ELSE`, что искажает интерпретацию.

Подробный разбор →
1234

Хотите тренировать интерактивно?

В приложении — таймер, прогресс, стрики и 1700+ вопросов по всем темам.

Тренировать SQL в Telegram

Другие темы: SQL

Агрегация, GROUP BY и HAVINGДаты и времяEXPLAIN и оптимизацияJOIN и операции множествОсновы SQL-запросовСтроки и приведение типовПодзапросы и CTEОконные функции