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+ вопросами для собесов аналитиков.