NULL в SQL — не ноль и не пустая строка. Это отсутствие значения. И именно из-за непонимания этого факта новички делают ошибки, которые трудно отловить.
NULL ≠ 0 и NULL ≠ ''
SELECT 0 = 0; -- TRUE
SELECT '' = ''; -- TRUE
SELECT NULL = NULL; -- NULL (не TRUE!)
Любое сравнение с NULL возвращает NULL — не TRUE и не FALSE. Это называется трёхзначная логика (Three-Valued Logic): TRUE, FALSE, NULL.
Как правильно проверять NULL
-- ❌ Так не работает
SELECT * FROM users WHERE phone = NULL; -- вернёт 0 строк всегда
SELECT * FROM users WHERE phone != NULL; -- тоже 0 строк
-- ✅ Правильно
SELECT * FROM users WHERE phone IS NULL;
SELECT * FROM users WHERE phone IS NOT NULL;
NULL в логических выражениях
| Выражение | Результат |
|---|---|
NULL AND TRUE | NULL |
NULL AND FALSE | FALSE |
NULL OR TRUE | TRUE |
NULL OR FALSE | NULL |
NOT NULL | NULL |
NULL AND FALSE = FALSE — потому что неважно, что второй операнд: результат всегда FALSE. Это единственный случай, когда NULL не «заражает» выражение.
NULL в агрегатных функциях
COUNT(*) считает все строки, включая NULL. COUNT(col) — только строки, где col не NULL.
SELECT
COUNT(*) AS total_rows, -- 100
COUNT(phone) AS with_phone, -- 73 (только не NULL)
AVG(salary) AS avg_salary -- игнорирует NULL
FROM employees;
Важно:
AVG,SUM,MIN,MAX— все игнорируют NULL. Это может исказить результат, если NULL обозначает «0».
-- Если NULL = отсутствие продаж (= 0), а не пропущенные данные:
AVG(COALESCE(sales, 0)) -- вместо AVG(sales)
COALESCE: замена NULL на значение по умолчанию
-- Первый не-NULL аргумент
COALESCE(a, b, c, ...)
SELECT
name,
COALESCE(phone, email, 'Нет контактов') AS contact
FROM users;
-- ❌ COALESCE здесь не поможет — деление на ноль всё равно произойдёт
SELECT revenue / COALESCE(visits, 0) AS revenue_per_visit -- ERROR: division by zero
-- ✅ NULLIF вернёт NULL вместо 0, и деление даст NULL (безопасно)
SELECT revenue / NULLIF(visits, 0) AS revenue_per_visit
FROM stats;
Почему
COALESCE(visits, 0)не работает: он заменяет NULL на 0, но еслиvisitsуже равен 0 (не NULL), деление всё равно произойдёт.NULLIF(visits, 0)же превращает именно ноль в NULL — что и нужно.
В разных СУБД есть свои аналоги COALESCE:
IFNULL(a, b)в MySQL,ISNULL(a, b)в SQL Server,NVL(a, b)в Oracle. Но все они принимают только два аргумента.COALESCE— стандартный SQL и принимает любое количество аргументов. С точки зрения производительности разницы практически нет — оптимизаторы обрабатывают их одинаково.
NULLIF: превращаем значение в NULL
NULLIF(a, b) -- вернёт NULL если a = b, иначе вернёт a
-- Классика: защита от деления на ноль
SELECT amount / NULLIF(quantity, 0) AS unit_price
FROM items;
-- Убрать пустые строки как NULL
SELECT NULLIF(TRIM(comment), '') AS comment
FROM reviews;
NULL в JOIN
CREATE TABLE a (id INT, val TEXT);
CREATE TABLE b (id INT, val TEXT);
INSERT INTO a VALUES (1, 'x'), (NULL, 'y');
INSERT INTO b VALUES (1, 'p'), (NULL, 'q');
SELECT * FROM a JOIN b ON a.id = b.id;
-- Результат: только строка (1, 'x') — (1, 'p')
-- NULL JOIN NULL не совпадает!
NULL никогда не равен NULL в условиях JOIN. Если нужно соединить по NULL:
SELECT * FROM a JOIN b ON a.id IS NOT DISTINCT FROM b.id;
-- IS NOT DISTINCT FROM обрабатывает NULL = NULL как TRUE
NULL в ORDER BY
-- По умолчанию в PostgreSQL NULL идут последними при ASC
SELECT name, score FROM results ORDER BY score ASC;
-- Можно явно управлять
ORDER BY score ASC NULLS FIRST; -- NULL в начале
ORDER BY score DESC NULLS LAST; -- NULL в конце
NULL в UNIQUE ограничении
В большинстве СУБД (PostgreSQL, MySQL) несколько NULL в уникальном столбце — разрешены. NULL ≠ NULL, поэтому конфликта нет. Но в SQL Server — иначе, допускается только один NULL.
Типичные ловушки на собеседовании
Сколько строк вернёт запрос?
SELECT * FROM users WHERE age != 25;
Строки где age IS NULL не попадут в результат! Чтобы включить:
WHERE age != 25 OR age IS NULL;
Что выведет этот запрос?
SELECT 1 + NULL; -- NULL
SELECT CONCAT('hi', NULL); -- NULL (в большинстве СУБД)
SELECT 'hi' || NULL; -- NULL в PostgreSQL
-- Исключение: в Oracle конкатенация 'hi' || NULL вернёт 'hi' — там NULL в строках игнорируется
Что вернёт COUNT при всех NULL?
SELECT COUNT(col) FROM t; -- 0 (все NULL игнорируются)
SELECT COUNT(*) FROM t; -- N (количество строк)
Итого
- Проверяй NULL только через
IS NULL/IS NOT NULL - NULL «заражает» арифметику и сравнения — результат тоже NULL
- Агрегаты игнорируют NULL — думай, это правильно для твоей задачи?
- В JOIN строки с NULL не совпадают — используй
IS NOT DISTINCT FROMесли нужно COALESCE— для подстановки дефолта,NULLIF— для создания NULL из значения