NULL в SQL — что это и как с ним работать

Коротко

NULL в SQL — это отсутствие значения. Не ноль, не пустая строка, не false. NULL означает «неизвестно». Это фундаментальная концепция, без которой невозможно писать корректные запросы — и частая тема на собеседованиях аналитика данных.

Что такое NULL

NULL — это маркер, обозначающий, что значение отсутствует или неизвестно. Важно понимать три вещи:

  • NULL ≠ 0 — ноль это конкретное число, NULL это отсутствие числа
  • NULL ≠ '' — пустая строка это строка длиной 0, NULL это отсутствие строки
  • NULL ≠ FALSE — false это логическое значение, NULL это неизвестность
-- Все три значения разные
SELECT
    CASE WHEN NULL = 0  THEN 'равны' ELSE 'не равны' END,  -- не равны
    CASE WHEN NULL = '' THEN 'равны' ELSE 'не равны' END,  -- не равны
    CASE WHEN NULL = NULL THEN 'равны' ELSE 'не равны' END; -- не равны!

Последняя строка — ключевая. NULL = NULL не возвращает TRUE. Потому что два неизвестных значения не обязаны быть одинаковыми.

Трёхзначная логика

В обычной логике есть TRUE и FALSE. В SQL есть третье значение — UNKNOWN. Любое сравнение с NULL возвращает UNKNOWN:

Выражение Результат
NULL = NULL UNKNOWN
NULL <> NULL UNKNOWN
NULL > 5 UNKNOWN
NULL AND TRUE UNKNOWN
NULL OR TRUE TRUE
NULL OR FALSE UNKNOWN
NOT NULL UNKNOWN

WHERE отбирает только строки, где условие = TRUE. Строки с UNKNOWN отфильтровываются так же, как FALSE.

IS NULL и IS NOT NULL

Для проверки на NULL нельзя использовать =. Используйте IS NULL и IS NOT NULL:

-- Правильно
SELECT * FROM users WHERE email IS NULL;

-- Неправильно — вернёт 0 строк всегда!
SELECT * FROM users WHERE email = NULL;

WHERE email = NULL превращается в UNKNOWN для каждой строки, и ничего не проходит фильтр. Это классическая ошибка новичков.

NULL в агрегатных функциях

Агрегатные функции обрабатывают NULL по-разному:

Функция Поведение с NULL
COUNT(*) Считает все строки, включая NULL
COUNT(column) Пропускает NULL
SUM(column) Игнорирует NULL
AVG(column) Игнорирует NULL (не учитывает в знаменателе)
MIN / MAX Игнорируют NULL
-- Таблица bonuses: {1000, NULL, 2000, NULL, 3000}
SELECT
    COUNT(*)        AS total_rows,      -- 5
    COUNT(amount)   AS non_null_count,  -- 3
    SUM(amount)     AS total,           -- 6000
    AVG(amount)     AS average          -- 2000 (6000/3, не 6000/5!)
FROM bonuses;

AVG игнорирует NULL — делит сумму на количество не-NULL значений. Если вам нужно считать NULL как 0, оберните в COALESCE: AVG(COALESCE(amount, 0)).

COALESCE и NULLIF

COALESCE возвращает первое не-NULL значение из списка аргументов. Используется для подстановки значений по умолчанию:

SELECT
    user_id,
    COALESCE(nickname, first_name, 'Аноним') AS display_name
FROM users;

NULLIF(a, b) — обратная операция: возвращает NULL, если a = b, иначе a. Классическое применение — защита от деления на ноль:

-- Без NULLIF: деление на ноль = ошибка
SELECT revenue / sessions AS cps FROM metrics;

-- С NULLIF: если sessions = 0, вернёт NULL вместо ошибки
SELECT revenue / NULLIF(sessions, 0) AS cps FROM metrics;

Подробнее о разнице между COALESCE и ISNULL — в отдельном посте.

NULL в JOIN

При JOIN строки не соединяются по NULL. NULL = NULL → UNKNOWN, а JOIN требует TRUE:

-- Строки с department = NULL не попадут в результат
SELECT *
FROM employees e
JOIN departments d ON e.department_id = d.id;

Если в employees есть записи с department_id = NULL, они не соединятся ни с одной строкой из departments — даже если в departments тоже есть NULL. Используйте LEFT JOIN, если хотите сохранить такие строки.

Ловушка NOT IN с NULL

Это самая опасная ловушка, связанная с NULL. Если подзапрос в NOT IN возвращает хотя бы один NULL, результат — пустая таблица:

-- Подзапрос возвращает: {1, 2, NULL}
SELECT * FROM orders
WHERE customer_id NOT IN (SELECT id FROM blacklist);
-- Результат: 0 строк!

Почему? customer_id NOT IN (1, 2, NULL) раскрывается в customer_id <> 1 AND customer_id <> 2 AND customer_id <> NULL. Последнее условие всегда UNKNOWN, а TRUE AND UNKNOWN = UNKNOWN. Ни одна строка не проходит.

Решение — используйте NOT EXISTS вместо NOT IN:

SELECT * FROM orders o
WHERE NOT EXISTS (
    SELECT 1 FROM blacklist b WHERE b.id = o.customer_id
);

NULL в ORDER BY

По умолчанию NULL считается «неизвестным» и может оказаться в начале или в конце — зависит от СУБД. В PostgreSQL NULL идёт последним при ASC и первым при DESC. Управлять можно явно:

SELECT * FROM users
ORDER BY last_login NULLS LAST;

SELECT * FROM users
ORDER BY last_login DESC NULLS FIRST;

Типичные ошибки

  1. = NULL вместо IS NULL — самая частая ошибка. WHERE col = NULL не работает
  2. NOT IN с подзапросом, содержащим NULL — возвращает 0 строк. Используйте NOT EXISTS
  3. Ожидание 0 вместо NULL — SUM пустого набора возвращает NULL, не 0. Оберните в COALESCE
  4. AVG с NULL — NULL-строки не учитываются в знаменателе. Это может исказить среднее

Вопросы с собеседований

Что такое NULL в SQL? — NULL — это маркер отсутствия значения. Не ноль, не пустая строка. Любое сравнение с NULL возвращает UNKNOWN, поэтому для проверки используют IS NULL / IS NOT NULL.

Что вернёт NULL = NULL? — UNKNOWN (не TRUE и не FALSE). Два неизвестных значения не считаются равными. Для сравнения двух значений с учётом NULL в PostgreSQL есть IS NOT DISTINCT FROM.

Чем COUNT(*) отличается от COUNT(column)? — COUNT(*) считает все строки. COUNT(column) пропускает строки, где column = NULL. Подробнее — в отдельном разборе.

Почему NOT IN с NULL возвращает пустой результат? — Потому что x <> NULL = UNKNOWN, а UNKNOWN в AND превращает всё выражение в UNKNOWN. Ни одна строка не проходит WHERE. Безопасная альтернатива — NOT EXISTS.

Как заменить NULL на значение по умолчанию? — COALESCE(column, default_value). Например, COALESCE(discount, 0) вернёт 0 вместо NULL.

Как NULL ведёт себя в JOIN? — Строки не соединяются по NULL, потому что NULL = NULL → UNKNOWN, а JOIN требует TRUE. Строки с NULL в ключе соединения «пропадают» при INNER JOIN.

Потренируйтесь на реальных вопросах с собеседований — откройте тренажёр. Там 1500+ вопросов по SQL и другим темам аналитика, включая примеры вопросов по NULL, агрегатам и джойнам.

FAQ

NULL и пустая строка — это одно и то же?

Нет. Пустая строка ('') — это конкретное значение длиной 0. NULL — отсутствие значения. '' IS NULL вернёт FALSE (исключение — Oracle, где пустая строка = NULL, но это особенность конкретной СУБД).

Как посчитать количество NULL в колонке?

COUNT(*) - COUNT(column). COUNT(*) считает все строки, COUNT(column) только не-NULL. Разница — количество NULL.

COALESCE влияет на производительность?

Сам по себе — нет, это простая операция. Но если обернуть индексированный столбец — WHERE COALESCE(col, 0) > 10 — СУБД не сможет использовать индекс. Лучше писать WHERE col > 10 OR col IS NULL.

Можно ли использовать NULL как значение по умолчанию в столбце?

Да, и это поведение по умолчанию, если не указан NOT NULL или DEFAULT. Если столбец допускает NULL и при INSERT значение не передано — запишется NULL. Используйте SQL-тренажёр для практики.