В блог
Подзапросы в SQL: что это и зачем они нужны - IT Resume

Подзапросы в SQL: что это и зачем они нужны

Дата последнего обновления: 10.11.2025
Дата размещения: 10.11.2025
Евгений Буторин
Автор, редактор

Сегодня мы разберём одну из фундаментальных, но при этом вызывающую вопросы тему — подзапросы в SQL. Понимание этой конструкции — это переход от простого умения писать запросы к способности решать по-настоящему сложные аналитические задачи.

Если вы хотите не просто извлекать данные, а гибко и мощно их трансформировать, эта статья для вас.

Что такое подзапрос?

Начнём с простой аналогии. Представьте, что ваш руководитель просит вас сначала узнать, кто из клиентов совершил покупку на сумму больше 1000 рублей в этом месяце, а потом принести их полные контактные данные. Это и называется подзапрос.

Таким образом, подзапрос или внутренний запрос — это запрос, который вложен внутрь другого запроса (внешнего или основного запроса). Его результат используется основным запросом для дальнейших операций. Подзапрос всегда заключён в круглые скобки и выполняется первым, до внешнего запроса.

Подзапрос имеет следующий синтаксис:

SELECT client_uk, client_name
FROM customers
WHERE client_uk = (SELECT client_uk FROM sales where amt_sales >= 1000);

Здесь выражение в скобках (SELECT client_uk FROM sales where amt_sales >= 1000) – это и есть подзапрос.

Зачем нужны подзапросы? Основные сценарии применения

Многие новички спрашивают: «А нельзя ли обойтись JOIN'ами?». Часто можно. Но есть целый класс задач, где подзапросы делают код гораздо более читаемым, логичным и лаконичным. Давайте разберём основные случаи их использования.

Подзапросы в условии WHERE (с операторами сравнения)

Это, пожалуй, самый частый сценарий. Когда вам нужно отфильтровать записи по условию, которое само требует отдельного запроса к базе.

Например, нам нужно найти всех клиентов, операционная прибыль которых выше средней по клиентской базе.

  • Без подзапроса пришлось бы делать два запроса: сначала узнать среднюю операционную прибыль (ОП) по клиентской базе, запомнить число, а затем сделать второй запрос WHERE op > полученного значения.
  • С подзапросом мы делаем это одним изящным запросом:
SELECT client_uk, client_name, op
FROM cutomer_op
WHERE op > (SELECT AVG(op) FROM cutomer_op);

Здесь подзапрос (SELECT AVG(op) FROM cutomer_op) вычисляет единственное значение — среднюю ОП. Внешний запрос затем использует это значение для фильтрации всех клиентов. Это наглядно и не требует ручного вмешательства.

Важный нюанс: когда мы используем операторы сравнения (=, >, <, >=, <=, <>), подзапрос должен возвращать только одно значение (одну строку и одну колонку). Такой подзапрос называется скалярным.

Подзапросы с операторами IN и NOT IN

А что, если подзапрос целый список значений? Например, несколько ID. Здесь на помощь приходит оператор IN.
Например, нам нужно найти всех менеджеров, которые курируют отделы, расположенные в Лондоне.

Для этого нам нужно:

  • Сначала найти ID всех отделов в Лондоне.
  • Потом найти всех менеджеров, чей department_id находится в полученном списке.
SELECT employee_id, first_name, last_name
FROM employees
WHERE department_id IN (
    SELECT department_id
    FROM departments
    WHERE location_id IN (
        SELECT DISTINCT location_id FROM locations WHERE UPPER(city) = 'LONDON'
    )
);

Обратите внимание, что здесь мы видим вложенный подзапрос — подзапрос внутри подзапроса. Сначала находится location_id для Лондона, затем по этому ID находятся все department_id, и, наконец, по списку этих ID находятся сотрудники.

Оператор NOT IN работает аналогично, но исключает записи, попадающие в список.

Подзапросы в качестве вычисляемого столбца (в SELECT)

Это мощный инструмент для обогащения основной выборки дополнительными, вычисляемыми на лету данными.

Например, мы хотим получить список всех сотрудников, и для каждого увидеть его зарплату и среднюю зарплату по его отделу.

SELECT
    employee_ccode,
    employee_name,
    salary,
    department_name,
    (SELECT AVG(salary) FROM employees e2 WHERE e2.department_ccode = e1.department_ccode) AS avg_department_salary
FROM employee e1;

Здесь для каждой строки из внешнего запроса (e1) выполняется коррелированный подзапрос. Он вычисляет среднюю зарплату, но не по всей компании, а именно по тому отделу, к которому принадлежит текущий сотрудник.
Обратите внимание на условие e2.department_ccode = e1.department_ccode — оно связывает внешний и внутренний запросы.

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

Подзапросы в FROM (представления на лету)

Иногда нам нужно использовать результат сложного запроса как таблицу. Это идеальная работа для подзапроса в FROM.
Например, нам нужно найти среднюю зарплату по отделам, а затем выбрать из этого списка только те отделы, где средняя зарплата превышает 80000.

SELECT *
FROM (
    SELECT
        department_ccode,
        AVG(salary) AS avg_sal
    FROM employees
    GROUP BY department_ccode
) AS department_avg
WHERE avg_sal > 80000;

В этом примере подзапрос создает временную «виртуальную» таблицу с псевдонимом department_avg, которая содержит два столбца: department_id и avg_sal. Внешний запрос работает уже с этой временной таблицей как с обычной. Это отличный способ разбивать сложные задачи на простые этапы.

Также этот тип подзапросов помогает при валидации данных, позволяя быстро группировать необходимые данные для сравнения с источником.

Коррелированные vs Некоррелированные подзапросы

Это ключевая классификация, которую должен понимать каждый аналитик.

  • Некоррелированный подзапрос — независимый. Он выполняется один раз, и его результат передаётся во внешний запрос. Все примеры выше, кроме подзапроса в SELECT, были некоррелированными. Они, как правило, более эффективны.
  • Коррелированный подзапрос зависит от данных внешнего запроса. Он выполняется не один раз, а для каждой строки результата внешнего запроса — как в примере с avg_department_salary. Внутри подзапроса есть ссылка на столбец из внешней таблицы (e1.department_ccode). Такие запросы могут быть медленными на больших объёмах данных, так как являются аналогом цикла for each.

EXISTS и NOT EXISTS: особая магия

Операторы EXISTS и NOT EXISTS используются исключительно с подзапросами и проверяют не сам результат, а его наличие.

Например, нам нужно найти покрытие баннерами клиентов, которые делали хотя бы один раз видели баннер.

SELECT banner_name, coverage
FROM banners c
WHERE EXISTS (
    SELECT 1
    FROM banner_views o
    WHERE o. banner_name = c. banner_name
);

Здесь подзапрос не возвращает данные во внешний запрос. Он лишь возвращает TRUE, если для текущего клиента найдётся хотя бы одна строка в таблице banner_views, и FALSE в противном случае. Обратите внимание, что в SELECT подзапроса часто пишут 1 или * — значение не важно, важна только проверка на существование.

NOT EXISTS работает с точностью до наоборот и часто используется для поиска «сирот» — записей, для которых нет связей в другой таблице.

Рекомендации и предостережения

  1. Производительность. Подзапросы, особенно коррелированные, могут быть медленными. Всегда смотрите на EXPLAIN PLAN (план выполнения запроса). Часто запрос с JOIN может быть эффективнее.
  2. Читаемость. Грамотно используемые подзапросы делают код логичным и легко сопровождаемым. Но глубоко вложенные «спагетти-подзапросы» могут стать кошмаром. Не злоупотребляйте вложенностью. Лучше используйте временные таблицы или CTE. CTE — это современный и очень читаемый способ заменить сложный подзапрос. Он позволяет дать имя подзапросу и использовать его несколько раз в основном запросе, что улучшает структуру кода.

Подзапросы — это не просто синтаксическая конструкция. Это способ мышления, позволяющий декомпозировать сложную бизнес-задачу на последовательные логические шаги. Они открывают путь к написанию мощных, гибких и выразительных SQL-запросов.

Не бойтесь их использовать. Практикуйтесь, сравнивайте с альтернативами, анализируйте планы запросов, и вы быстро почувствуете, в какой ситуации какой инструмент — подзапрос, JOIN или CTE — будет наиболее уместным и эффективным.

Подпишитесь на нашу рассылку
Имя*
Email*
Номер телефона*
Заполняя данную форму, Вы соглашаетесь с политикой конфиденциальности
Никакого спама. Только точечные рассылки с лучшими материалами.
  • Курсы-симуляторы
  • Авторские тренинги
  • Бесплатные курсы
  • Высшее образование