Подзапросы в SQL

Что такое подзапрос в SQL и зачем он вам нужен?
Представьте, что вы работаете с блогом на WordPress, где хранятся тысячи комментариев, постов и пользователей. Вам нужно вывести только тех авторов, чьи статьи набрали больше 100 лайков за последнюю неделю. Без подзапроса вы потратите часы на ручной перебор или напишете пачку бессвязных запросов. Подзапрос — это вложенный запрос SELECT, который выполняется внутри основного. Он даёт вам один точный ответ, который можно использовать в WHERE, FROM, HAVING или даже в SELECT. И это не теория — каждую задачу из этой статьи вы сможете сразу применить к реальным данным: к своему интернет-магазину, CRM или лендингу.
Как подзапросы отличают вас от обычного разработчика?
Большинство новичков останавливаются на JOIN’ах и думают, что этих инструментов достаточно. На практике реальные проекты перегружены данными: миллионы строк, десятки таблиц, вложенные зависимости. Грамотный подзапрос сокращает количество ошибок в 3–4 раза по сравнению с последовательными запросами в коде. Когда вы пишете один подзапрос вместо трёх отдельных SELECT’ов, нагрузка на базу снижается на 30–70% — зависит от объёма данных. А ещё подзапросы читаются легче: вместо того чтобы разбирать цепочку объединений, вы видите чёткую логику — «сначала найди это, потом используй как условие».
Когда вы обязаны использовать подзапрос, а не JOIN?
В 2026 году устаревшие версии MySQL всё ещё встречаются, и на них подзапросы в FROM работают быстрее JOIN’ов для агрегированных расчётов. Но главный критерий — когда вам нужно сравнить запись с результатом агрегатной функции, например, вывести продукты, цена которых выше средней по их категории. Вы не сможете сделать это простым JOIN без промежуточного расчёта. Или когда вы проверяете «хотя бы одно совпадение» через EXISTS — это экономит память, потому что СУБД останавливается при первом совпадении, а не тащит все строки.
Какие типы подзапросов вы будете использовать каждый день?
- Подзапрос в WHERE с оператором IN — идеален для фильтрации по набору значений. Пример: найти клиентов, которые оформили заказы за последние 30 дней. Скорость выше, чем при объединении двух таблиц.
- Подзапрос в FROM (производные таблицы) — когда нужно выполнить предварительную агрегацию. Например, вычислить средний чек по каждому месяцу, а потом уже отфильтровать месяцы с высоким чеком.
- СКАЛЯРНЫЙ подзапрос в SELECT — возвращает одно значение. Удобен для добавления столбца «средний рейтинг товара» в выборку, чтобы не тащить отдельную таблицу.
- Подзапрос с EXISTS — проверка существования. Многие начинающие путают его с IN, но для больших таблиц с дубликатами
EXISTSв 2–3 раза быстрее. - Коррелированный подзапрос — зависит от внешнего запроса. Требует аккуратности: если написать неэффективно, будете ждать часами. Но для поиска «самой дешёвой ссылки у каждого поставщика» это лучшее решение.
Пример из жизни: как вы накажете нерадивого менеджера с помощью подзапроса?
Допустим, в вашей таблице заказов нужно найти менеджеров, у которых хотя бы один заказ был оформлен с датой раньше даты регистрации самого менеджера. Согласитесь, это подозрительно. Такой запрос проверит целостность данных: выводит только тех, у кого есть хотя бы одна аномалия. Без подзапроса вам пришлось бы создавать временную таблицу или писать сложный JOIN с группировкой. Конкретный код: SELECT manager_id FROM managers d WHERE EXISTS (SELECT 1 FROM orders o WHERE o.manager_id = d.manager_id AND o.order_date < d.reg_date). Выполняется за миллисекунды.
Что вы должны знать про производительность подзапросов?
Самый частый миф — что подзапросы всегда медленные. На самом деле, если вы сравниваете IN (SELECT ...) и JOIN с SELECT DISTINCT, подзапрос на MySQL 8.0 с оптимизацией может быть быстрее в 10 раз. Внимание: используйте EXISTS вместо IN, когда подзапрос может вернуть дубликаты, — разница в 2–3 раза. Ещё один секрет: в PostgreSQL подзапросы с LIMIT 1 внутри коррелированного запроса — это способ мгновенно получить «первую» запись из группы, без оконных функций.
Топ-5 ошибок новичков, которые стоят вам времени и нервов
- Игнорирование корреляции — когда вы пишете подзапрос, не связывая его с внешней таблицей, получаете бессмысленный набор всех значений. Результат — медленный запрос и вылет ошибки.
- Путаница между WHERE и HAVING с подзапросами —
WHEREвыполняется до агрегации, аHAVINGпосле. Если нужно найти «заказы клиентов, сделавших более 5 покупок», используйтеHAVING COUNT(*) > 5с подзапросом на клиентов. - Подзапрос возвращает NULL — если в списке есть NULL, то
INне сработает, как вы ожидаете. Всегда используйтеCOALESCEилиIN (SELECT ... WHERE ... IS NOT NULL). - Вложение подзапросов глубиной более 3 уровней — код становится нечитаемым, и база данных зависает. Лучше использовать CTE (WITH) или временную таблицу.
- Отсутствие индексов на связанные столбцы — подзапросы без индексов на столбцах сравнения выполняются в 100 раз дольше. Проверяйте планы выполнения.
Как подзапросы меняются в 2026 году с новыми версиями SQL?
Современные базы данных, такие как PostgreSQL 16 и MySQL 8.4, научились автоматически декорировать подзапросы в JOIN’ы, если это выгодно. Но полагаться на оптимизатор — риск. Практика показывает, что для сложных отчётов явное написание подзапроса в FROM (как производная таблица) даёт выигрыш в 20–30% по сравнению с тем же запросом через JOIN с агрегацией. Поэтому в курсах по веб-разработке 2026 года делают упор именно на ручной подбор оптимальной структуры, а не на «магическую кнопку». Вы должны уметь прикидывать количество строк на каждом этапе.
Часто задаваемые вопросы по подзапросам в SQL
1. Можно ли использовать несколько подзапросов в одном SELECT?
Да, и это распространённая практика. Вы можете в одном SELECT указать подзапрос для расчёта текущей суммы, для средней зарплаты в отделе и для общего числа сотрудников. Главное — следить, чтобы каждый подзапрос возвращал ровно одну строку, иначе ошибка. Пример: SELECT salary, (SELECT AVG(salary) FROM employees WHERE dept_id = e.dept_id) AS avg_salary FROM employees e.
2. Что быстрее: подзапрос или JOIN в WHERE?
Однозначного ответа нет. Подзапрос с EXISTS обычно быстрее при проверке на существование строки, так как он прекращает поиск после нахождения первого совпадения. А JOIN быстрее, когда нужно получить данные из нескольких таблиц без проверки на существование. Тестируйте на своих данных — как правило, разница становится заметной при объёме более 50 тысяч строк.
3. Как правильно использовать подзапрос с IN и избежать проблем с большими наборами?
Избегайте передачи списка значений из IN подзапроса, который возвращает сотни тысяч строк — лучше переключиться на INNER JOIN с той же второй таблицей. Если список маленький (меньше ~100 элементов), подзапрос с IN отлично работает. Оптимальный совет: всегда ставьте DISTINCT внутри подзапроса для IN — это снижает риск дублирования и ускоряет запрос.
4. Можно ли внутри подзапроса обращаться к полям внешнего запроса?
Да, это называется коррелированный подзапрос. Например, SELECT * FROM products p WHERE price > (SELECT AVG(price) FROM products WHERE category_id = p.category_id). Такие подзапросы могут выполняться медленно, если не проиндексированы столбцы из условия. Индексируйте столбцы, которые упоминаются в корреляционном условии — например, category_id в обоих подзапросах.
5. Почему подзапрос в FROM требует псевдонима?
Потому что производная таблица (subquery в FROM) должна иметь имя, чтобы к ней можно было обращаться из основного запроса. Это требование стандарта SQL. Даже если вы не используете имя в других местах, СУБД требует его. Пример: SELECT * FROM (SELECT id, name FROM users WHERE status = 'active') AS active_users — псевдоним active_users обязателен.
6. Как отладить подзапрос, если он не возвращает данные?
Первым делом выполните вложенный запрос отдельно, подставив в него конкретное значение внешнего ключа. Часто ошибка в том, что подзапрос написан с неправильной логикой сравнения или возвращает NULL, а вы используете = вместо IS NULL. Также проверьте, что в условии FROM вы не пропустили фильтр по дате или статусу. Простой SELECT * из подзапроса даст вам весь массив данных и выявит проблему.
7. Что делать, если подзапрос выполняет полное сканирование таблицы?
Это классическая проблема производительности. Создайте композитный индекс на столбцах, которые используются в WHERE подзапроса и в SELECT основного запроса. Для подзапроса с IN хорошо работает индекс на столбце, по которому делается выборка для внешнего запроса. Также проверьте, можно ли заменить подзапрос на EXISTS — иногда он триггерит правильное использование индексов.
8. Бывает ли, что подзапрос не нужен вовсе?
Конечно. Если задача решается простым JOIN без агрегации — всегда лучше использовать JOIN. Он читается легче и выполняется стабильно. Но когда в дело вступает группировка, цены выше среднего или проверка нахождения значения, подзапрос становится вашей рабочей лошадкой. Не переусложняйте — иногда достаточно простого LEFT JOIN с NULL проверкой.
9. Как оформить подзапрос, чтобы не запутаться?
Стандарт оформления: всегда выделяйте подзапрос в отдельные строки, пишите ключевые слова SELECT, FROM, WHERE с отступами. Давайте подзапросам осмысленные псевдонимы: avg_price или top_users. Если подзапрос в FROM, оборачивайте его в круглые скобки вместе с AS .... Этот код должны понимать не только базы данных, но и ваши коллеги.
10. Где взять практические задания по подзапросам с реальными данными?
На нашей платформе вы найдёте тестовую базу данных с примерами из интернет-магазина, библиотеки и системы комментариев. После прохождения базового курса по SQL вы сможете перейти к модулю подзапросов, где разбираются все пять типов — от простого IN до коррелированных с EXISTS. Практические задачи включают создание отчётов по продажам, выявление аномалий и оптимизацию существующих запросов. Обновите свой профиль до Premium, чтобы получить доступ к 20+ упражнениям с автоматической проверкой.
Добавлено: 23.04.2026
