Здесь живёт «нет глубины». NULL ломает привычную логику тихо, без ошибки — и запрос молча возвращает не то. Разберём ловушки так, чтобы ты их видел заранее.
Поймёшь, что NULL — это «неизвестно», а не 0 и не пустая строка. Научишься проверять его через
IS NULL, «чинить» через COALESCE/NULLIF, и обходить две главные ловушки —
<> и NOT IN, которые молча теряют строки.
NULL значит «значения нет» / «неизвестно». Это не число 0 и не пустая строка ''.
У Dmitry и Grace страна — NULL (мы просто не знаем её), а у Jack NULL — это email.
Раз значение неизвестно, любой вопрос про него получает ответ «а бог его знает» — и вот с этого начинаются проблемы.
В обычных языках условие либо истина, либо ложь. В SQL их три: TRUE, FALSE и
UNKNOWN. Любое сравнение с NULL даёт UNKNOWN — даже NULL = NULL.
Запусти и посмотри (пустая ячейка в выводе — это и есть NULL/UNKNOWN):
Первый столбец — пусто (UNKNOWN), второй — t (true). Вывод: = NULL и <> NULL
никогда не работают. Проверяют NULL только так:
col IS NULL — значение отсутствует;col IS NOT NULL — значение есть.И запомни главное про фильтр: WHERE оставляет строку, только если условие — ровно TRUE.
FALSE и UNKNOWN отбрасываются одинаково. Поэтому строка с NULL тихо выпадает из выборки.
Сколько строк вернёт SELECT * FROM customers WHERE country = NULL;?
country = NULL для каждой строки даёт UNKNOWN, а WHERE оставляет
только TRUE. Значит — 0 строк, без ошибки. Чтобы найти Dmitry и Grace, нужно WHERE country IS NULL.Почти любое выражение с NULL внутри само становится NULL. NULL + 5 → NULL.
'Hi ' || NULL → NULL (склейка строк тоже «заражается»). Два инструмента лечат это:
| Функция | Что делает | Пример |
|---|---|---|
COALESCE(a, b, …) | берёт первое не-NULL из списка | COALESCE(country, 'Unknown') → страна или «Unknown» |
NULLIF(a, b) | даёт NULL, если a = b; иначе a | NULLIF(stock, 0) → превращает 0 в NULL |
COALESCE — «подставь запасное значение вместо дырки». NULLIF — обратное: «сделай это значение дыркой».
Классика NULLIF — защита от деления на ноль: x / NULLIF(y, 0) вернёт NULL вместо ошибки.
Хочешь «всех, кроме USA» и пишешь WHERE country <> 'USA'. Для Dmitry и Grace страна NULL,
значит NULL <> 'USA' → UNKNOWN → строка выпадает. Ты молча потерял два ряда, хотя они не из USA.
Ещё злее NOT IN со списком, где есть NULL: x NOT IN ('USA', NULL) становится
UNKNOWN для всех строк — запрос вернёт ноль строк. Классический баг на проде.
Как правильно:
OR country IS NULL, либоcountry IS DISTINCT FROM 'USA' — оно считает
NULL «отличным от 'USA'» и вернёт TRUE, не UNKNOWN.Что вернёт SELECT name FROM customers WHERE country NOT IN ('USA', NULL);?
NULL в списке делает NOT IN результатом UNKNOWN для каждой
строки, и WHERE отбрасывает все. Ноль строк. Именно поэтому NOT IN с возможным NULL
заменяют на NOT EXISTS или IS DISTINCT FROM.Два факта на стыке с прошлыми темами (пригодятся в упражнениях):
NULL. COUNT(col), SUM, AVG считают только
не-NULL значения; COUNT(*) считает строки и NULL не пропускает. (Это из
Темы 04.)LEFT JOIN сам рождает NULL. Если справа пары нет — все её столбцы становятся
NULL. Отсюда приём «анти-join»: LEFT JOIN … WHERE правая.id IS NULL находит строки без пары.А из Темы 01 вспомни: порядок NULL в сортировке задаётся отдельно —
ORDER BY col NULLS LAST (или NULLS FIRST).
Пиши запрос, жми «Проверить» (⌘/Ctrl+Enter). Сначала думай сам — усилие памяти и строит навык.
Подсказка и решение рядом, бери их, только когда правда застрял. Держи схему под рукой.
Задачи идут по нарастанию.
1. Разминка. Выведи name покупателей, у которых не указана страна (country).
2. Наоборот: выведи name покупателей, у которых есть email.
3. Одной строкой — два числа: сколько всего покупателей и у скольких заполнен email.
Числа должны отличаться.
4. Выведи name и страну каждого покупателя, но вместо пустой страны показывай текст
'Unknown'. Столбцы: name, country.
5. Собери один столбец label вида Alice Johnson (alice@example.com).
У кого email пустой — покажи Jack Wilson (no email). (Без COALESCE вся склейка стала бы NULL.)
6. Для каждого товара выведи name и price / stock как ratio.
У двух товаров stock = 0 — прямое деление даст ошибку. Сделай так, чтобы там был NULL, а не падение.
7. Выведи name всех покупателей, чья страна не 'USA'.
Важно: покупателей с неизвестной страной (NULL) тоже считаем «не из USA» и включаем.
(Наивное country <> 'USA' их потеряет.)
8. Найди покупателей без единого заказа — выведи name.
Здесь NOT IN безопасен: customer_id в заказах никогда не NULL.
9. Выведи order_id заказов, у которых нет оплаты (нет строки в payments).
Подсказка идеи: LEFT JOIN оставит такие заказы, а поля оплаты у них будут NULL.
10. Для каждого заказа выведи order_id и оплаченную сумму amount;
если оплаты нет — покажи 0. Столбцы: order_id, amount.
11. Повтор из Темы 01. Выведи name и country всех покупателей: сортировка по
country в обратном алфавитном порядке (DESC), покупателей без страны (NULL) —
в самое начало, при равной стране — по name по возрастанию. (важен порядок)
12. Капстоун. По каждому заказу, кроме отменённых (status <> 'cancelled'),
выведи order_id, status и оплаченную сумму amount (нет оплаты → 0).
Отсортируй по сумме по убыванию, при равной сумме — по order_id по возрастанию. (важен порядок)
1) = NULL / <> NULL не работают — только IS NULL / IS NOT NULL.
2) <> и NOT IN молча теряют строки с NULL — спасают IS DISTINCT FROM и NOT EXISTS.
3) Любая арифметика/склейка с NULL даёт NULL — закрывай дырки COALESCE.