Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Что такое подзапрос в SQL
Подзапрос (subquery) — это SQL запрос, который вложен внутри другого запроса. Подзапрос может использоваться как источник данных, условие фильтрации или даже как вычисляемый столбец.
Основная структура
SELECT column1, column2
FROM table1
WHERE column1 IN (
SELECT column1 -- это подзапрос (внутренний запрос)
FROM table2
WHERE condition
);
Подзапрос выполняется первым, потом его результат используется во внешнем запросе.
Типы подзапросов
1. Подзапрос в WHERE (условие фильтрации):
-- Найти всех пользователей, которые сделали хотя бы один заказ
SELECT id, name
FROM users
WHERE id IN (
SELECT user_id
FROM orders
);
-- Эквивалентно (но подзапрос работает в 99% случаев быстрее)
SELECT DISTINCT u.id, u.name
FROM users u
JOIN orders o ON u.id = o.user_id;
2. Подзапрос в FROM (как источник данных):
-- Найти средний доход по категориям
SELECT category, AVG(total_amount) as avg_income
FROM (
SELECT category, SUM(amount) as total_amount
FROM products
GROUP BY category
) as category_totals
GROUP BY category;
3. Подзапрос в SELECT (как вычисляемый столбец):
SELECT
id,
name,
(SELECT COUNT(*) FROM orders WHERE orders.user_id = users.id) as total_orders
FROM users;
4. Коррелированный подзапрос (correlated subquery):
Подзапрос ссылается на столбцы внешнего запроса:
-- Найти работников, зарплата которых выше среднего в своём отделе
SELECT id, name, salary, department
FROM employees e1
WHERE salary > (
SELECT AVG(salary)
FROM employees e2
WHERE e2.department = e1.department -- Корреляция!
);
5. Подзапрос с EXIST:
-- EXISTS проверяет наличие хотя бы одной строки
SELECT id, name
FROM users u
WHERE EXISTS (
SELECT 1
FROM orders o
WHERE o.user_id = u.id
);
-- Эквивалентно
SELECT id, name
FROM users u
WHERE id IN (SELECT user_id FROM orders);
Операторы с подзапросами
IN — проверка наличия в списке:
SELECT * FROM users WHERE id IN (SELECT user_id FROM orders);
NOT IN — исключение:
-- Пользователи, которые НЕ сделали заказ
SELECT * FROM users WHERE id NOT IN (SELECT user_id FROM orders);
ANY / ALL — сравнение с элементами подзапроса:
-- Заказы с суммой больше, чем любой заказ от user_id=1
SELECT * FROM orders WHERE amount > ANY (
SELECT amount FROM orders WHERE user_id = 1
);
-- Заказы с суммой больше, чем ВСЕ заказы от user_id=1
SELECT * FROM orders WHERE amount > ALL (
SELECT amount FROM orders WHERE user_id = 1
);
Примеры сложных подзапросов
Найти топ 3 продавца по количеству продаж:
SELECT name, total_sales
FROM (
SELECT seller_id, COUNT(*) as total_sales
FROM orders
GROUP BY seller_id
ORDER BY total_sales DESC
LIMIT 3
) as top_sellers
JOIN users ON top_sellers.seller_id = users.id;
Найти продукты, которые дороже среднего и входят в топ 5 категорий по выручке:
SELECT p.id, p.name, p.price, p.category
FROM products p
WHERE p.price > (SELECT AVG(price) FROM products)
AND p.category IN (
SELECT category
FROM (
SELECT category, SUM(price * quantity) as revenue
FROM products
GROUP BY category
ORDER BY revenue DESC
LIMIT 5
) as top_categories
);
Производительность подзапросов
Проблема: N+1 queries — подзапрос выполняется для каждой строки:
-- МЕДЛЕННО: подзапрос выполняется 1000 раз если в users 1000 строк
SELECT
id, name,
(SELECT COUNT(*) FROM orders WHERE user_id = users.id) as total_orders
FROM users;
-- БЫСТРО: используем JOIN + GROUP BY
SELECT u.id, u.name, COUNT(o.id) as total_orders
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
GROUP BY u.id, u.name;
Оптимизация — когда подзапрос работает медленно:
-- Плохо: подзапрос в WHERE для большой таблицы
SELECT * FROM orders
WHERE user_id IN (
SELECT id FROM users WHERE status = 'premium'
);
-- Хорошо: используем JOIN
SELECT o.*
FROM orders o
JOIN users u ON o.user_id = u.id
WHERE u.status = 'premium';
EXPLAIN для анализа подзапросов
-- Посмотрим, как БД выполняет запрос
EXPLAIN ANALYZE
SELECT * FROM users
WHERE id IN (SELECT user_id FROM orders);
-- Output показывает:
-- - Hash Semi Join (быстро)
-- - Nested Loop (медленно)
-- - Seq Scan vs Index Scan
Когда использовать подзапросы
Используй подзапрос когда:
- Логика становится сложнее с JOIN'ами
- Нужна промежуточная агрегация
- EXISTS/NOT EXISTS более читаема
- Количество результатов небольшое
Избегай подзапросов когда:
- Можно использовать JOIN (часто быстрее)
- Подзапрос выполняется для каждой строки (N+1)
- Большой объём данных в подзапросе
CTE вместо подзапросов (modern SQL)
Common Table Expression (WITH clause) — альтернатива, которая часто более читаемая:
-- Вместо
SELECT * FROM users
WHERE id IN (
SELECT user_id FROM orders WHERE amount > 1000
);
-- Используй CTE
WITH high_value_orders AS (
SELECT user_id FROM orders WHERE amount > 1000
)
SELECT * FROM users
WHERE id IN (SELECT user_id FROM high_value_orders);
Преимущества CTE:
- Более читаемо
- Переиспользуется несколько раз
- Рекурсивные запросы поддерживает
Заключение
Подзапросы — мощный инструмент в SQL, но:
- Используй их правильно для читаемости
- Профилируй с EXPLAIN
- Предпочитай JOIN'ы когда возможно
- Не создавай N+1 queries