EXISTS в SQL: шпаргалка

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

Что делает EXISTS

Возвращает TRUE, если подзапрос возвращает хотя бы одну строку. Используется как фильтр «существует/не существует».

SELECT *
FROM users u
WHERE EXISTS (
    SELECT 1 FROM orders o WHERE o.user_id = u.user_id
);

Читается как: «пользователи, для которых существует хотя бы один заказ».

Синтаксис

WHERE EXISTS (subquery)
WHERE NOT EXISTS (subquery)

Подзапрос обычно коррелированный (ссылается на внешнюю таблицу).

1. EXISTS — пользователи с заказами

SELECT *
FROM users u
WHERE EXISTS (
    SELECT 1 FROM orders o WHERE o.user_id = u.user_id
);

2. NOT EXISTS — пользователи без заказов

SELECT *
FROM users u
WHERE NOT EXISTS (
    SELECT 1 FROM orders o WHERE o.user_id = u.user_id
);

3. EXISTS с дополнительным условием

Пользователи с заказом после 2026 года:

SELECT *
FROM users u
WHERE EXISTS (
    SELECT 1
    FROM orders o
    WHERE o.user_id = u.user_id
      AND o.created_at > '2026-01-01'
);

4. EXISTS vs IN

Оба решают похожие задачи:

-- EXISTS
SELECT * FROM users u
WHERE EXISTS (SELECT 1 FROM orders o WHERE o.user_id = u.user_id);

-- IN
SELECT * FROM users
WHERE user_id IN (SELECT user_id FROM orders);

Разница:

EXISTS IN
Коррелированный да нет (обычно)
NULL-безопасность да NOT IN ломается на NULL
Читаемость обычная для простых случаев — лучше
Производительность часто лучше на больших таблицах хуже при миллионах записей

На собесе упомяните: NOT IN ломается на NULL — используйте NOT EXISTS.

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

-- работает корректно
SELECT * FROM users u WHERE NOT EXISTS (
    SELECT 1 FROM orders o WHERE o.user_id = u.user_id
);

5. EXISTS vs JOIN

JOIN выводит колонки из двух таблиц. EXISTS — только фильтрует:

-- JOIN: может задублировать users, если у одного user несколько заказов
SELECT DISTINCT u.*
FROM users u
JOIN orders o ON o.user_id = u.user_id;

-- EXISTS: не дублирует
SELECT *
FROM users u
WHERE EXISTS (SELECT 1 FROM orders o WHERE o.user_id = u.user_id);

Правило: если нужны только столбцы из «основной» таблицы — EXISTS чище и быстрее.

6. NOT EXISTS vs LEFT JOIN ... IS NULL

Тоже эквивалентные, выбор — по стилю:

-- 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;

-- NOT EXISTS (обычно читабельнее)
SELECT *
FROM users u
WHERE NOT EXISTS (
    SELECT 1 FROM orders o WHERE o.user_id = u.user_id
);

В Postgres планировщик обычно строит одинаковый план для обоих. В MySQL NOT EXISTS часто быстрее.

7. EXISTS с несколькими условиями

Пользователи с хотя бы одним дорогим (>10000) и недавним (в 2026) заказом:

SELECT *
FROM users u
WHERE EXISTS (
    SELECT 1
    FROM orders o
    WHERE o.user_id = u.user_id
      AND o.total > 10000
      AND o.created_at > '2026-01-01'
);

8. EXISTS и EXCEPT (альтернатива)

Для «users без заказов» есть ещё EXCEPT:

SELECT user_id FROM users
EXCEPT
SELECT user_id FROM orders;

Но EXCEPT возвращает только ключ, EXISTS — можно выбрать любые колонки.

9. EXISTS в CASE

Проверка «есть ли заказ» как флаг в SELECT:

SELECT
    u.user_id,
    CASE
        WHEN EXISTS (SELECT 1 FROM orders o WHERE o.user_id = u.user_id)
        THEN 1 ELSE 0
    END AS has_order
FROM users u;

10. Двойное EXISTS

Пользователи, у которых есть И заказ, И отзыв:

SELECT *
FROM users u
WHERE EXISTS (SELECT 1 FROM orders o WHERE o.user_id = u.user_id)
  AND EXISTS (SELECT 1 FROM reviews r WHERE r.user_id = u.user_id);

Или один EXISTS с JOIN внутри:

SELECT *
FROM users u
WHERE EXISTS (
    SELECT 1 FROM orders o
    JOIN reviews r ON r.user_id = o.user_id
    WHERE o.user_id = u.user_id
);

Частые ошибки

Ошибка 1. Использовать SELECT * вместо SELECT 1

-- работает, но SELECT * здесь бессмысленный
WHERE EXISTS (SELECT * FROM orders WHERE ...)

-- конвенция
WHERE EXISTS (SELECT 1 FROM orders WHERE ...)

Разницы в производительности нет (планировщик оптимизирует), но SELECT 1 — стандарт.

Ошибка 2. Забыть корреляцию

-- EXISTS возвращает TRUE всегда, если в orders есть хоть одна строка
SELECT * FROM users u
WHERE EXISTS (SELECT 1 FROM orders);  -- вернёт ВСЕ users

-- правильно: коррелированный подзапрос
SELECT * FROM users u
WHERE EXISTS (SELECT 1 FROM orders o WHERE o.user_id = u.user_id);

Ошибка 3. NOT EXISTS vs != (на NULL)

-- если есть NULL, != тоже не работает
WHERE user_id != (SELECT MAX(user_id) FROM orders WHERE ... IS NULL)

-- безопаснее NOT EXISTS

Ошибка 4. Использовать EXISTS, когда JOIN + DISTINCT короче

Иногда JOIN короче и понятнее. EXISTS — не догма.

Производительность

  • EXISTS останавливается на первой найденной строке (short-circuit)
  • На больших таблицах обычно быстрее IN
  • С правильным индексом (user_id в orders) — очень быстро
  • NOT EXISTS часто быстрее LEFT JOIN ... IS NULL в MySQL

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

FAQ

EXISTS или IN — что быстрее?

На небольших подзапросах — одинаково. На больших — EXISTS обычно быстрее, особенно в Postgres и SQL Server.

Почему SELECT 1, а не SELECT *?

Конвенция читаемости. Планировщик оптимизирует оба одинаково — фактической разницы в скорости нет.

EXISTS возвращает TRUE/FALSE?

Да, это boolean-оператор. Используется только в WHERE/HAVING/CASE, не в SELECT-листе (кроме как через CASE).

Можно ли EXISTS без WHERE в подзапросе?

Можно, но вернёт TRUE, если таблица непустая. Обычно нужен коррелированный подзапрос с условием привязки к внешней таблице.


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