IN (підзапит)- перевіряє, чи значення є серед результатів підзапиту.EXISTS (підзапит)- перевіряє, чи підзапит повернув хоч один рядок. Значення не важливі, тож пишутьSELECT 1.
SELECT * FROM users u WHERE u.id IN (SELECT user_id FROM orders);
SELECT * FROM users u WHERE EXISTS (SELECT 1 FROM orders o WHERE o.user_id = u.id);
Для позитивних перевірок сучасні оптимізатори PostgreSQL і MySQL зазвичай перетворюють обидва варіанти на однаковий план (semi-join), тож різниця частіше в читабельності.
Головна пастка - NOT IN і NULL. Якщо підзапит повертає хоч один NULL, NOT IN не поверне жодного рядка:
SELECT * FROM users WHERE id NOT IN (SELECT manager_id FROM teams);
-- якщо в teams є рядок з manager_id = NULL - порожній результат
Причина в тризначній логіці: 5 NOT IN (1, NULL) - це 5 <> 1 AND 5 <> NULL, а 5 <> NULL дає NULL, не true. Рядок не проходить фільтр.
Тому для «немає відповідності» використовують NOT EXISTS або LEFT JOIN ... WHERE x.id IS NULL. Вони поводяться передбачувано з NULL і добре оптимізуються.