Выражения и NULL: вопросы для собеседования (часть 4)
NULL в SQL ведёт себя неинтуитивно: NULL = NULL возвращает не TRUE, агрегатные функции игнорируют NULL, а CASE WHEN требует аккуратного обращения с пустыми значениями. Интервьюеры любят задачи-ловушки на тему IS NULL, COALESCE, NULLIF и трёхзначной логики. Непонимание NULL — одна из самых частых ошибок даже у опытных кандидатов.
Вопросы 16–20 из 20
16В таблице `orders` поле `promo_code` может быть `NULL`. Что произойдёт со строкой, где `promo_code` = `NULL`, при фильтре `WHERE promo_code <> 'NONE'`?
AСтрока всегда попадёт в результат, потому что `NULL` «не равно» `NONE`
BСтрока не попадёт в результат, потому что выражение `promo_code <> 'NONE'` даст `UNKNOWN`
CСтрока попадёт в результат только если есть `ORDER BY`
DSQL выдаст ошибку, потому что `<>` нельзя применять к `NULL`
Ответ: Сравнение с `NULL` даёт `UNKNOWN`, а `WHERE` оставляет только `TRUE`.
В три-значной логике результат сравнения с `NULL` — это `UNKNOWN`. Для строки с `promo_code = NULL` условие `promo_code <> 'NONE'` не является ни `TRUE`, ни `FALSE`. В `WHERE` отбираются только строки, для которых условие `TRUE`, поэтому строка с `NULL` будет исключена. Если нужно явно включать `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`
B`0`
C`500`
DОшибка, потому что `status` = `NULL`
Ответ: Если условие `WHEN` даёт не `TRUE` (включая `UNKNOWN` из-за `NULL`), сработает `ELSE`.
При `status = NULL` сравнение `status = 'paid'` возвращает `UNKNOWN`. В `CASE` ветка `WHEN` выбирается только при `TRUE`. Так как условие не `TRUE`, будет выбран `ELSE 0`. Поэтому результат — 0, даже если `amount` = 500. Чтобы явно обработать пропуск, можно добавить ветку `WHEN status IS NULL THEN ...`.
18Нужно посчитать количество оплаченных заказов (`status = 'paid'`). Какое выражение корректно вернёт число оплаченных, если в столбце `status` бывают `NULL`?
A`COUNT(CASE WHEN status = 'paid' THEN 1 ELSE 0 END)`
B`COUNT(status = 'paid')`
C`COUNT(CASE WHEN status = 'paid' THEN 1 END)`
D`COUNT(COALESCE(status, 'paid'))`
Ответ: `COUNT(column)` считает не-`NULL`, поэтому удобно делать `COUNT(CASE WHEN ... THEN 1 END)` — там `NULL` для не-подходящих строк.
`COUNT` считает количество не-`NULL` значений. Если использовать `COUNT(CASE WHEN status = 'paid' THEN 1 END)`, то для оплаченных вернётся 1, а для остальных (включая `NULL` в `status`) — `NULL`, поэтому будут посчитаны только оплаченные. Вариант с `ELSE 0` неверен: 0 — это не `NULL`, значит будут посчитаны все строки.
19Есть `LEFT JOIN` пользователей с их платежами. Если у пользователя нет платежей, `payments.amount` будет `NULL`. Как правильно посчитать «выручку на пользователя», трактуя отсутствие платежей как 0?
A`SUM(payments.amount) / COUNT(users.user_id)`
B`SUM(COALESCE(payments.amount, 0)) / COUNT(users.user_id)`
C`COUNT(payments.amount) / SUM(users.user_id)`
D`SUM(users.user_id) / COUNT(COALESCE(payments.amount, 0))`
Ответ: После `LEFT JOIN` отсутствующие значения становятся `NULL`; для метрик часто подставляют 0 через `COALESCE` перед агрегированием.
При `LEFT JOIN` строки без совпадений получают `NULL` в полях правой таблицы. Если вы хотите считать отсутствие платежей как 0, безопасно заменить `payments.amount` на 0 до суммирования: `SUM(COALESCE(payments.amount, 0))`. Деление на `COUNT(users.user_id)` даёт среднее по всем пользователям, включая тех, у кого платежей не было.
20Нужно пометить пользователей, у которых нет заказов, как `'no_orders'`. Есть поле `orders_count`, которое для некоторых пользователей равно `NULL` (данные не загрузились), а для реально без заказов — 0. Какой `CASE` корректнее различит эти ситуации?
A`CASE WHEN orders_count = 0 THEN 'no_orders' ELSE 'has_orders' END`
B`CASE WHEN orders_count IS NULL THEN 'no_orders' ELSE 'has_orders' END`
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`. Это делает смысл признака прозрачным и устойчивым.
Хотите тренировать интерактивно?
В приложении — таймер, прогресс, стрики и 1700+ вопросов по всем темам.
Тренировать в Telegram