Почему SQL возвращает пустой результат

Карьерник — квиз-тренажёр в Telegram с 1500+ вопросами для собесов аналитика. SQL, Python, A/B, метрики. Бесплатно.

Основные причины

  1. Условие в WHERE слишком строгое (опечатка, неверное значение)
  2. NULL в сравненииcolumn = NULL всегда FALSE
  3. INNER JOIN убрал строки без пары
  4. Регистр'Alice' ≠ 'alice'
  5. Пробелы / trim'Alice' ≠ 'Alice '
  6. Неверная таймзона или границы даты
  7. NOT IN с NULL — возвращает пусто
  8. HAVING после WHERE фильтрует ещё раз

1. Опечатки в значениях

-- пусто, потому что в базе 'Moscow', а не 'Moskow'
SELECT * FROM users WHERE city = 'Moskow';

-- проверка
SELECT DISTINCT city FROM users ORDER BY city LIMIT 20;

2. NULL в сравнении

-- всегда FALSE, даже если есть строки с NULL
SELECT * FROM users WHERE phone = NULL;

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

Почему? Потому что NULL = NULL в SQL даёт UNKNOWN, а не TRUE.

3. INNER JOIN срезает строки

-- пользователи, у которых НЕТ заказов, выпадут
SELECT u.name, o.total
FROM users u
INNER JOIN orders o ON o.user_id = u.id;

Если ожидали увидеть всех пользователей → LEFT JOIN:

SELECT u.name, o.total
FROM users u
LEFT JOIN orders o ON o.user_id = u.id;

4. Условие в WHERE ломает LEFT JOIN

-- превращает LEFT в INNER
SELECT u.*, o.status
FROM users u
LEFT JOIN orders o ON o.user_id = u.id
WHERE o.status = 'paid';

-- правильно: условие в ON
SELECT u.*, o.status
FROM users u
LEFT JOIN orders o ON o.user_id = u.id AND o.status = 'paid';

Разница: в WHERE — фильтр после JOIN (убирает NULL-строки). В ON — фильтр во время JOIN.

5. Регистр

-- возможно, в БД 'Alice', не 'alice'
SELECT * FROM users WHERE name = 'alice';

-- регистронезависимо
SELECT * FROM users WHERE LOWER(name) = LOWER('alice');

-- Postgres
SELECT * FROM users WHERE name ILIKE 'alice';

6. Пробелы

-- в БД "Alice " с пробелом на конце
SELECT * FROM users WHERE name = 'Alice';  -- пусто

-- с trim
SELECT * FROM users WHERE TRIM(name) = 'Alice';

Лучше почистить данные в ETL, а не каждый раз TRIM.

7. Таймзоны

-- сервер UTC, а вы думаете, что Москва
SELECT * FROM events
WHERE created_at >= '2026-04-21';
-- захватывает события с 21 апреля 00:00 UTC = 03:00 МСК

-- нужна явная таймзона
SELECT * FROM events
WHERE created_at >= '2026-04-21 00:00:00 Europe/Moscow';

8. Границы даты

-- не включает 2026-04-30 23:59
SELECT * FROM orders
WHERE created_at BETWEEN '2026-04-01' AND '2026-04-30';

-- правильно: полуоткрытый интервал
SELECT * FROM orders
WHERE created_at >= '2026-04-01'
  AND created_at <  '2026-05-01';

BETWEEN даёт >= AND <=, но для времени лучше < following_day.

9. NOT IN с NULL

-- если есть хоть один NULL в подзапросе — результат пустой
SELECT * FROM users
WHERE user_id NOT IN (SELECT user_id FROM orders);

-- правильно: отфильтровать NULL
SELECT * FROM users
WHERE user_id NOT IN (
    SELECT user_id FROM orders WHERE user_id IS NOT NULL
);

-- или NOT EXISTS (безопасно)
SELECT u.* FROM users u
WHERE NOT EXISTS (
    SELECT 1 FROM orders o WHERE o.user_id = u.user_id
);

Почему? x NOT IN (a, b, NULL) = x != a AND x != b AND x != NULL = x != a AND x != b AND NULL = NULL (не TRUE).

10. HAVING после WHERE

-- WHERE убрал все активные, HAVING ничего не найдёт
SELECT user_id, COUNT(*)
FROM orders
WHERE status = 'cancelled'
GROUP BY user_id
HAVING COUNT(*) > 100;

Уточните логику: WHERE — до агрегации, HAVING — после.

11. AND вместо OR в WHERE

-- ни один пользователь не из двух городов сразу
SELECT * FROM users
WHERE city = 'Moscow' AND city = 'SPb';

-- правильно
SELECT * FROM users
WHERE city = 'Moscow' OR city = 'SPb';

-- или
SELECT * FROM users WHERE city IN ('Moscow', 'SPb');

12. Join на неправильном ключе

-- orders.customer_id vs users.id
SELECT * FROM users u
JOIN orders o ON o.user_id = u.id;
-- может быть, в orders это 'customer_id'?

Проверьте имена колонок.

13. Тип данных несовпадает

-- user_id — строка, а сравниваете с числом
WHERE user_id = 42        -- если user_id VARCHAR → ok, если INT → ok
WHERE user_id = '42'      -- подстраховывает CAST, но может не использовать индекс

Как дебажить

Пошагово убирать фильтры

-- исходный запрос возвращает пусто
SELECT * FROM users WHERE city = 'Moscow' AND age > 30 AND is_premium;

-- уберём один
SELECT * FROM users WHERE city = 'Moscow' AND age > 30;  -- ещё пусто?

-- убираем ещё
SELECT * FROM users WHERE city = 'Moscow';  -- есть результаты?

-- найдите, какой фильтр лишает данных

Проверить distinct values

-- какие city на самом деле в таблице
SELECT DISTINCT city FROM users ORDER BY 1;

Посмотреть пример строк

SELECT * FROM users LIMIT 5;

COUNT до и после фильтра

SELECT COUNT(*) FROM users;                         -- всего
SELECT COUNT(*) FROM users WHERE city = 'Moscow';   -- после фильтра

На собесе

Вопрос: «Запрос возвращает пусто, что делать?»

Хороший ответ:

  1. Проверить опечатки и регистр значений
  2. Проверить NULL в сравнениях
  3. Проверить INNER vs LEFT JOIN
  4. Проверить границы дат / таймзоны
  5. Пошагово убирать условия

Связанные темы

FAQ

Почему col = NULL не работает?

В SQL NULL = NULL это UNKNOWN. Используйте IS NULL.

Почему LEFT JOIN возвращает только совпадения?

Потому что в WHERE есть условие на правую таблицу. Перенесите в ON.

NOT IN или NOT EXISTS?

NOT EXISTS безопаснее с NULL.

BETWEEN или >= ... <?

Для времени — >= ... <. BETWEEN теряет последний день с временем.


Тренируйте SQL — откройте тренажёр с 1500+ вопросами для собесов.