intermediate
Joins
Выбирайте inner, outer, semi и anti join shapes по cardinality, null behavior и query plan cost.
Join объединяет строки связанных таблиц по предикату — обычно равенству ключей. Тип join определяет, какие строки останутся при совпадениях или null с любой стороны.
| Join | Когда строки сохраняются | |------|--------------------------| | `INNER JOIN` | Есть совпадение с обеих сторон | | `LEFT JOIN` | Все строки слева; справа null, если нет пары | | `RIGHT JOIN` | Зеркало left — на практике редок | | `FULL OUTER` | Все с обеих сторон; null там, где нет пары | | Semi (`EXISTS`, `IN`) | Строки слева с хотя бы одним совпадением — без раздувания дубликатами | | Anti (`NOT EXISTS`) | Строки слева без совпадений |
-- Пользователи с хотя бы одним заказом (semi-join)
SELECT u.id, u.email
FROM users u
WHERE EXISTS (SELECT 1 FROM orders o WHERE o.user_id = u.id);
-- Пользователи без заказов (anti-join)
SELECT u.id FROM users u
WHERE NOT EXISTS (SELECT 1 FROM orders o WHERE o.user_id = u.id);
Кардинальность важна: join один-ко-многим без агрегации дублирует факты родителя — оберните в подзапрос или осознанно используйте `DISTINCT ON` / window functions. Фильтруйте рано; переносите предикаты, сужающие join, до самого join, когда planner это позволяет.
На интервью: по требованию отчёта выберите inner vs left vs exists и объясните риск дубликатов и обработку null.
Типовые ошибки: неявные join через запятую без ясного предиката; `LEFT JOIN`, затем `WHERE right.col = X` (превращается в inner); условия join через `OR`, блокирующие индекс; join широких таблиц до фильтрации.
Компромисс — между читаемостью SQL и стоимостью плана: semi/anti join часто выигрывают у `DISTINCT` после inner join, но «читаемая» форма зависит от движка и статистики.
Чеклист:
- Укажите кардинальность (1:1, 1:N, N:M) для join.
- Выберите тип join из требований к null и дубликатам.
- Предпочитайте `EXISTS` вместо `IN` для correlated semi-join на масштабе.
- Фильтруйте ведущую таблицу до join, когда возможно.
- Упомяните индексы на ключах join.