В прошлом уроке мы считали агрегаты по группам через GROUP BY. Иногда для проверки нужен результат ОДНОГО запроса, чтобы использовать его как условие в ДРУГОМ запросе — например, «сначала найди всех, кто записан на курс, а потом среди ОСТАЛЬНЫХ найди тех, кого там нет». Для таких задач в SQL есть подзапросы.
Чему научишься за этот урок:
— объяснять, что такое подзапрос (subquery) и как он устроен;
— читать запрос, где один SELECT вложен внутрь другого;
— сравнивать подзапрос с JOIN и понимать, когда они взаимозаменяемы;
— оценивать, когда подзапрос оправдан для разовой ручной проверки тестировщика.
Что такое подзапрос
Подзапрос (subquery) — это SQL-запрос ВНУТРИ другого запроса. Он используется как условие фильтрации или как источник данных для внешнего запроса. Подзапрос обычно заключён в скобки и стоит внутри WHERE, IN или другой части внешнего запроса.
Пример на знакомых таблицах users(id, name) и enrollments(id, user_id, course_id):
SELECT name FROM users WHERE id IN ( SELECT user_id FROM enrollments WHERE course_id = 5 );
Здесь внутренний запрос SELECT user_id FROM enrollments WHERE course_id = 5 находит все user_id, записанные на курс 5. Внешний запрос берёт этот список id и по нему находит в таблице users соответствующие имена. Результат — имена всех, кто записан на курс 5.
Подзапрос против JOIN
Тот же самый результат можно получить через JOIN (из прошлой темы). Подзапрос и JOIN часто взаимозаменяемы для одной и той же задачи, но подзапрос иногда проще ЧИТАТЬ: логика более пошаговая — «сначала найди X, потом по X найди Y», а не «соедини сразу две таблицы и отфильтруй».
Когда подзапрос оправдан
Для разовой, читаемой проверки тестировщику подзапрос часто удобнее JOIN — не нужно продумывать, как именно соединять таблицы, логика читается «изнутри наружу». Для запросов, которые выполняются ЧАСТО в реальном продукте на больших объёмах данных, обычно предпочитают JOIN — он эффективнее для базы данных. Но тестировщик пишет разовые проверочные запросы, а не продакшен-код, поэтому эта эффективность для его задач обычно не критична.
Итог:
— подзапрос (subquery) — это SQL-запрос внутри другого запроса, используется как условие или источник данных;
— внутренний запрос выполняется первым, внешний использует его результат;
— подзапрос и JOIN часто дают один и тот же результат для одной задачи;
— для разовой читаемой ручной проверки тестировщику подзапрос часто удобнее JOIN, для частых продакшен-запросов на больших данных обычно предпочитают JOIN.
Контрольный вопрос. Что такое подзапрос (subquery)?
Подсказка: В названии уже есть подсказка — это запрос ВНУТРИ запроса.
Проверить ответ →
Реальная проверка — на нашей платформе, бесплатно и без регистрации. Открыть задачу и ввести ответ →
Контрольный вопрос. В запросе SELECT name FROM users WHERE id IN (SELECT user_id FROM enrollments WHERE course_id = 5); какой запрос выполняется первым по логике?
Подсказка: Внешнему запросу нужен результат внутреннего, чтобы понять, какие id искать — значит, порядок не одновременный.
Проверить ответ →
Реальная проверка — на нашей платформе, бесплатно и без регистрации. Открыть задачу и ввести ответ →
Контрольный вопрос. Чем подзапрос обычно проще для чтения, чем эквивалентный JOIN?
Подсказка: В уроке прямо сравнивается «пошаговое чтение» подзапроса с соединением таблиц через JOIN.
Проверить ответ →
Реальная проверка — на нашей платформе, бесплатно и без регистрации. Открыть задачу и ввести ответ →
Контрольный вопрос. Для каких запросов в реальном продукте обычно предпочитают JOIN, а не подзапрос?
Подсказка: В уроке прямо сказано, для какого сценария эффективность JOIN становится важна.
Проверить ответ →
Реальная проверка — на нашей платформе, бесплатно и без регистрации. Открыть задачу и ввести ответ →
Контрольный вопрос. Про соотношение подзапроса и JOIN в уроке сказаны следующие вещи. Какие из утверждений верны (выбери все подходящие)?
Выберите все верные варианты.
Подсказка: Сверь каждое утверждение с разделом «Подзапрос против JOIN» и «Когда подзапрос оправдан» — одно из четырёх прямо противоречит примеру урока.
Проверить ответ →
Реальная проверка — на нашей платформе, бесплатно и без регистрации. Открыть задачу и ввести ответ →
Контрольный вопрос. Какое ключевое слово SQL используется в примере урока, чтобы проверить, входит ли id пользователя в список результатов подзапроса? Ответь одним словом заглавными буквами.
Подсказка: Посмотри на пример запроса в уроке — оно стоит сразу перед открывающей скобкой подзапроса.
Проверить ответ →
Реальная проверка — на нашей платформе, бесплатно и без регистрации. Открыть задачу и ввести ответ →
Задание. Дан подзапрос: SELECT name FROM users WHERE id IN (SELECT user_id FROM enrollments WHERE course_id = 7); Что именно найдёт этот запрос — имена кого он вернёт? Ответь одним предложением.
Подсказка: Внутренний запрос находит id всех, кто записан на курс 7 — а внешний по этим id находит что?
✅ Готово, если: ты ввёл(а) верный ответ, и онлайн-проверка его приняла.
Проверить ответ →
Реальная проверка — на нашей платформе, бесплатно и без регистрации. Открыть задачу и ввести ответ →
Задание. Нужно найти всех пользователей, которые ЗАПИСАНЫ хотя бы на один курс (используя таблицы users(id, name) и enrollments(id, user_id, course_id)). Напиши SQL-запрос с подзапросом, который вернёт их имена.
Подсказка: Подзапрос должен найти все user_id из enrollments без фильтра по course_id — а внешний запрос по ним найти имена в users.
✅ Готово, если: ты ввёл(а) верный ответ, и онлайн-проверка его приняла.
Проверить ответ →
Реальная проверка — на нашей платформе, бесплатно и без регистрации. Открыть задачу и ввести ответ →
Задание. Нужно найти всех пользователей, которые НЕ записаны ни на один курс. Опиши на словах (не обязательно точным синтаксисом SQL), как решить эту задачу через подзапрос: что должен находить внутренний запрос и как внешний запрос должен использовать его результат, чтобы получить именно «не записанных».
Критерий приёмки: описано, что внутренний подзапрос находит все user_id, которые ЕСТЬ в enrollments (SELECT user_id FROM enrollments); внешний запрос выбирает из users тех, чей id НЕ входит в этот список — то есть используется отрицание (NOT IN или аналогичная логика «id не в списке результатов подзапроса»), а не то же самое условие IN, что для «записанных».
Подсказка: Задача — противоположность примеру из урока: там искали ЗАПИСАННЫХ через IN, здесь нужны те, кого в списке подзапроса НЕТ.
✅ Готово, если: ответ раскрывает то, что просит критерий приёмки в задании выше.
Онлайн-проверка ответа появится позже
Задание. Тестировщик получил от разработчика два варианта одного и того же проверочного запроса: (1) с подзапросом — вариант из этого урока для поиска записанных на курс; (2) эквивалентный запрос через JOIN между users и enrollments с условием course_id = 5. Оба дают одинаковый результат. Оцени: какой вариант тестировщику стоит выбрать для РАЗОВОЙ ручной проверки при подготовке к тестированию, а какой — если этот же запрос нужно встроить в автотест, который будет выполняться сотни раз в день на реальной базе продукта с миллионами строк? Обоснуй оба выбора.
Критерий приёмки: для разовой ручной проверки рекомендован подзапрос как более читаемый пошагово («сначала найди X, потом Y») без необходимости продумывать соединение таблиц; для частого автотеста на больших объёмах данных рекомендован JOIN как более эффективный для базы данных при частом выполнении на больших объёмах — оба вывода явно связаны с критерием «частота выполнения + объём данных», а не выбраны произвольно.
Подсказка: Вернись к разделу «Когда подзапрос оправдан» — там прямо разведены два сценария по критерию частоты выполнения и объёма данных.
✅ Готово, если: ответ раскрывает то, что просит критерий приёмки в задании выше.
Онлайн-проверка ответа появится позже
Что дальше
Теперь вы умеете решать задачи подзапросом и оценивать, когда это оправдано. В следующем, последнем уроке главы разберём команды, которые ИЗМЕНЯЮТ данные и структуру базы — INSERT, UPDATE, DELETE и DDL — и главное: почему их можно выполнять только в песочнице.
