← Назад к вопросам

Что такое подзапросы в SQL и чем они отличаются от JOIN?

1.6 Junior🔥 271 комментариев
#SQL и базы данных

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Подзапросы 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 лучше, но это вариант с подзапросом) нужен потому что нужно ранжировать внутри категорий, а потом фильтровать.

Когда использовать подзапросы

  1. Когда нужна агрегация перед фильтрацией: коррелированные подзапросы с COUNT, MAX, MIN
  2. EXISTS/NOT EXISTS: проверка наличия строк
  3. IN подзапрос: фильтрация по списку значений
  4. Derived tables: сложные вычисления, которые нужны как промежуточный результат

Когда использовать JOIN

  1. Объединение данных из разных таблиц: когда нужны колонки из обеих таблиц
  2. Производительность: JOIN оптимизирован, быстрее подзапросов
  3. Читаемость: обычно понятнее для больших запросов
  4. 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.

Главное: нет одного правильного способа, выбирай в зависимости от задачи, данных и читаемости кода.