Что такое подзапросы в SQL и чем они отличаются от JOIN?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Подзапросы vs JOIN в SQL
Это базовые техники в SQL, и многие аналитики недостаточно глубоко их понимают. Расскажу об основных различиях и когда что использовать.
Что такое подзапрос
Подзапрос (subquery) — это SELECT запрос внутри другого SELECT запроса. Подзапрос выполняется первым, его результат используется во внешнем запросе.
SELECT user_id, name, revenue
FROM users
WHERE revenue > (SELECT AVG(revenue) FROM users)
Здесь подзапрос SELECT AVG(revenue) FROM users вычисляет среднее, а затем внешний запрос фильтрует пользователей, у которых revenue выше среднего.
Что такое JOIN
JOIN — это объединение двух таблиц по общему ключу, результат содержит колонки из обеих таблиц.
SELECT u.user_id, u.name, o.order_id, o.amount
FROM users u
INNER JOIN orders o ON u.user_id = o.user_id
Результат содержит все колонки из обеих таблиц, где user_id совпадает.
Основные различия
| Аспект | Подзапрос | JOIN |
|---|---|---|
| Структура | Запрос внутри запроса | Объединение таблиц |
| Результат | Обычно одно значение или набор | Новая таблица с колонками из обеих таблиц |
| Производительность | Медленнее (выполняется для каждой строки) | Быстрее (оптимизирован база данных) |
| Читаемость | Сложнее для больших запросов | Часто понятнее |
| Используется для | Фильтрация, агрегация, условие EXISTS | Объединение данных из разных таблиц |
Виды подзапросов
1. Коррелированный подзапрос (Correlated Subquery)
Подзапрос обращается к колонкам из внешнего запроса.
SELECT u.user_id, u.name
FROM users u
WHERE (SELECT COUNT(*) FROM orders o WHERE o.user_id = u.user_id) > 5
Этот подзапрос выполняется для каждой строки users, поэтому медленный.
2. Некоррелированный подзапрос (Non-correlated)
Подзапрос независим от внешнего запроса, выполняется один раз.
SELECT user_id, name
FROM users
WHERE country_id IN (SELECT id FROM countries WHERE continent = 'Europe')
Быстрее, так как подзапрос выполняется один раз.
3. Подзапрос в FROM (Derived Table)
SELECT avg_revenue, COUNT(*) as user_count
FROM (
SELECT user_id, AVG(amount) as avg_revenue
FROM orders
GROUP BY user_id
) subquery
WHERE avg_revenue > 1000
Примеры из продуктовой аналитики
Кейс 1: Найти пользователей с выше среднего revenue
С подзапросом:
SELECT user_id, username, total_spent
FROM users
WHERE total_spent > (SELECT AVG(total_spent) FROM users)
С JOIN (если нужно больше информации):
SELECT u.user_id, u.username, u.total_spent, c.country
FROM users u
INNER JOIN countries c ON u.country_id = c.id
WHERE u.total_spent > (SELECT AVG(total_spent) FROM users)
Кейс 2: Получить пользователей, которые совершали покупки в последний месяц
С подзапросом (коррелированный):
SELECT user_id, name
FROM users u
WHERE EXISTS (
SELECT 1 FROM orders o
WHERE o.user_id = u.user_id
AND o.created_at > NOW() - INTERVAL 1 MONTH
)
С JOIN:
SELECT DISTINCT u.user_id, u.name
FROM users u
INNER JOIN orders o ON u.user_id = o.user_id
WHERE o.created_at > NOW() - INTERVAL 1 MONTH
JOIN быстрее, так как база данных оптимизирует объединение.
Кейс 3: Найти top 5 продуктов в каждой категории
SELECT category, product_id, revenue
FROM (
SELECT category, product_id, revenue,
ROW_NUMBER() OVER (PARTITION BY category ORDER BY revenue DESC) as rn
FROM products
) ranked
WHERE rn <= 5
Здесь подзапрос (CTE лучше, но это вариант с подзапросом) нужен потому что нужно ранжировать внутри категорий, а потом фильтровать.
Когда использовать подзапросы
- Когда нужна агрегация перед фильтрацией: коррелированные подзапросы с COUNT, MAX, MIN
- EXISTS/NOT EXISTS: проверка наличия строк
- IN подзапрос: фильтрация по списку значений
- Derived tables: сложные вычисления, которые нужны как промежуточный результат
Когда использовать JOIN
- Объединение данных из разных таблиц: когда нужны колонки из обеих таблиц
- Производительность: JOIN оптимизирован, быстрее подзапросов
- Читаемость: обычно понятнее для больших запросов
- LEFT/RIGHT JOIN: когда нужны строки, которых может не быть в другой таблице
Лучшая практика: CTE (Common Table Expression)
В современном SQL, вместо подзапросов часто используют CTE (WITH), это более читаемо:
WITH user_avg_revenue AS (
SELECT AVG(total_spent) as avg_spent
FROM users
)
SELECT u.user_id, u.username, u.total_spent
FROM users u, user_avg_revenue
WHERE u.total_spent > user_avg_revenue.avg_spent
Оптимизация
Совет 1: Избегай коррелированных подзапросов Они выполняются для каждой строки внешнего запроса, что медленно на больших данных.
Совет 2: Используй EXPLAIN ANALYZE
EXPLAIN ANALYZE
SELECT user_id, name
FROM users
WHERE total_spent > (SELECT AVG(total_spent) FROM users)
Это показывает, как база данных выполняет запрос и сколько времени это занимает.
Совет 3: Предпочитай JOIN Если можешь переписать подзапрос как JOIN, делай это. БД лучше оптимизирует JOIN.
Главное: нет одного правильного способа, выбирай в зависимости от задачи, данных и читаемости кода.