NULL в SQL: шпаргалка для собеседования аналитика

Зачем аналитику знать NULL

NULL — самая частая причина «странных» результатов в SQL. Поле которого было 100 строк, после фильтра стало 50 — и виноват NULL.

На собесе задачи про NULL — способ проверить, вы действительно разбираетесь в SQL или просто зазубрили синтаксис. Самые типовые ловушки — про ответы в WHERE, агрегатах и JOIN.

Главное про NULL

NULL — это не значение. Это отсутствие значения. Отсюда все странности:

NULL = NULL   → UNKNOWN (не TRUE!)
NULL != NULL  → UNKNOWN
NULL + 1NULL
'abc' = NULL  → UNKNOWN
NULL OR TRUETRUE
NULL AND TRUE → UNKNOWN
NULL OR FALSE → UNKNOWN
NULL AND FALSEFALSE

Правило: любая арифметика или сравнение с NULL → NULL (или UNKNOWN в булевой логике).

Проверка на NULL — IS NULL / IS NOT NULL

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

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

email = NULL возвращает UNKNOWN для любой строки, WHERE оставляет только TRUE — значит, 0 строк.

COALESCE — «первое не-NULL»

-- Возвращает первое не-NULL значение
SELECT COALESCE(phone, email, 'unknown') AS contact
FROM users;

-- Заполнить пропуски в агрегате
SELECT user_id, COALESCE(SUM(amount), 0) AS revenue
FROM orders
RIGHT JOIN users USING (user_id)
GROUP BY user_id;

COALESCE — главный инструмент работы с NULL в аналитике.

NULLIF — превращаем значение в NULL

-- Если знаменатель 0, вернём NULL (избежим деления на 0)
SELECT revenue / NULLIF(users, 0) AS arpu FROM stats;

NULLIF(a, b) возвращает NULL если a = b, иначе a. Классический трюк для защиты от division by zero.

Больше таких примеров с разборами — в Telegram-тренажёре. Короткие сессии, прогресс по темам, объяснения после каждого ответа.

NULL в агрегатах

Все агрегаты (кроме COUNT(*)) игнорируют NULL:

-- Дано: [10, 20, NULL, 30]
SELECT
    COUNT(*),          -- 4 (все строки)
    COUNT(amount),     -- 3 (игнорирует NULL)
    SUM(amount),       -- 60
    AVG(amount),       -- 20 (60/3, а не 60/4!)
    MIN(amount),       -- 10
    MAX(amount)        -- 30
FROM orders;

Ловушка на собесе: «Почему AVG даёт 20, а не 15?» — потому что NULL не участвует в расчёте. Если нужно считать NULL как 0 — AVG(COALESCE(amount, 0)).

NULL в WHERE

Любое условие с NULL возвращает UNKNOWN, и строка не попадает в результат:

-- Это исключит строки с email = NULL
SELECT * FROM users WHERE email != 'admin@example.com';

Чтобы включить и NULL:

SELECT * FROM users WHERE email != 'admin@example.com' OR email IS NULL;

NULL в JOIN

INNER JOIN

Строки с NULL в ключе не матчатся — даже между собой:

-- users.manager_id=NULL не соединится ни с чем
SELECT * FROM users u INNER JOIN users m ON u.manager_id = m.id;

LEFT JOIN

Левая таблица сохраняется, в правой NULL:

SELECT u.name, o.order_id
FROM users u
LEFT JOIN orders o ON o.user_id = u.user_id;
-- Для пользователей без заказов: order_id = NULL

ANTI JOIN через LEFT JOIN + IS NULL

Найти пользователей БЕЗ заказов:

SELECT u.*
FROM users u
LEFT JOIN orders o ON o.user_id = u.user_id
WHERE o.user_id IS NULL;

NULL и NOT IN — главная ловушка

-- Если подзапрос возвращает NULL, весь NOT IN даст 0 строк
SELECT * FROM users
WHERE user_id NOT IN (SELECT referred_by FROM users);
-- referred_by может быть NULL → весь фильтр даёт 0 строк

Правильно:

-- Вариант 1: исключить NULL
SELECT * FROM users
WHERE user_id NOT IN (
    SELECT referred_by FROM users WHERE referred_by IS NOT NULL
);

-- Вариант 2: NOT EXISTS (безопасно по умолчанию)
SELECT * FROM u1
WHERE NOT EXISTS (SELECT 1 FROM u2 WHERE u2.referred_by = u1.user_id);

NULL в GROUP BY

Все NULL попадают в одну группу:

SELECT category, COUNT(*) FROM products GROUP BY category;
-- Одна из строк будет category = NULL

Если хотите явно — оберните в COALESCE:

SELECT COALESCE(category, 'unknown') AS category, COUNT(*) FROM products GROUP BY 1;

NULL в DISTINCT

DISTINCT оставляет ровно один NULL:

-- Дано: ['a', NULL, 'b', NULL, 'a']
SELECT DISTINCT col FROM t;
-- Результат: ['a', 'b', NULL]

Если готовишься к собесу — бот @kariernik_bot закрывает 80% технических вопросов. SQL, Python, A/B, продуктовые метрики — всё в одном месте.

NULL в ORDER BY

По умолчанию NULL идут в конце (ASC) или в начале (DESC) в большинстве СУБД. Можно явно:

SELECT * FROM orders ORDER BY shipped_at NULLS FIRST;
SELECT * FROM orders ORDER BY shipped_at NULLS LAST;

Конкатенация строк с NULL

-- PostgreSQL: || с NULL даёт NULL
SELECT first_name || ' ' || last_name FROM users;
-- Если last_name = NULL, результат = NULL

-- Решение — CONCAT (игнорирует NULL)
SELECT CONCAT(first_name, ' ', last_name) FROM users;
-- Или COALESCE
SELECT COALESCE(first_name, '') || ' ' || COALESCE(last_name, '') FROM users;

10 задач с собесов

1. Заменить NULL в amount на 0

SELECT COALESCE(amount, 0) FROM orders;

2. Найти строки, где email пустой

SELECT * FROM users WHERE email IS NULL OR email = '';

Важно: пустая строка '' — это не NULL.

3. Безопасное деление (не делить на 0)

SELECT revenue / NULLIF(users_count, 0) AS arpu FROM stats;

4. Пользователи без заказов

SELECT u.* FROM users u
LEFT JOIN orders o ON o.user_id = u.user_id
WHERE o.user_id IS NULL;

5. Количество пользователей с телефоном (не NULL)

SELECT COUNT(phone) AS with_phone, COUNT(*) AS total FROM users;

6. Средний чек, NULL считается как 0

SELECT AVG(COALESCE(amount, 0)) FROM orders;

7. Заказы, где статус не 'canceled'

-- ✅ Включаем NULL в статусе
SELECT * FROM orders WHERE status != 'canceled' OR status IS NULL;

8. Топ пользователей по выручке (NULL внизу)

SELECT user_id, SUM(amount) FROM orders GROUP BY user_id
ORDER BY 2 DESC NULLS LAST;

9. Подсчёт заказов с оплатой и без

SELECT
    COUNT(*) FILTER (WHERE paid_at IS NOT NULL) AS paid,
    COUNT(*) FILTER (WHERE paid_at IS NULL) AS unpaid
FROM orders;

10. Процент NULL в колонке

SELECT
    COUNT(*) FILTER (WHERE col IS NULL) * 100.0 / COUNT(*) AS null_pct
FROM t;

Как тренироваться

Работу с NULL учат через ошибки. Напишите запрос без IS NULL, потом посмотрите, почему фильтр не сработал — и запомните на всю жизнь.

Тренажёр Карьерник содержит блок задач на NULL: COALESCE, NULLIF, NOT IN ловушки, LEFT JOIN + IS NULL для ANTI JOIN. Каждая с разбором.

Совет: на собесе, видя любое сравнение с возможным NULL, сразу проговаривайте вслух: «если тут может быть NULL, результат может быть NULL». Это заметно — и ценится.

Читайте также

FAQ

Чем NULL отличается от пустой строки?

NULL — отсутствие значения, пустая строка '' — это значение с длиной 0. NULL != ''. LENGTH('') = 0, LENGTH(NULL) = NULL. В реальных данных часто встречаются обе и нужно явно их различать.

Почему WHERE col != 'x' не включает строки с NULL?

Потому что NULL != 'x' даёт UNKNOWN, а WHERE оставляет только TRUE. Решение: WHERE col != 'x' OR col IS NULL.

Как работает SUM с NULL?

Игнорирует — суммирует только не-NULL значения. Если все значения NULL, SUM вернёт NULL (не 0!). COALESCE(SUM(col), 0) — защита от этого.

IS NULL или = NULL?

IS NULL. = NULL всегда даёт UNKNOWN. На собесе это первый вопрос на владение SQL, если человек говорит = NULL — минус балл.