Подзапросы

Знакомьтесь: подзапросы!
Подзапрос — это запрос в запросе. Например, ассистент по актёрам собирает портфолио с фото и пробами артистов, подходящих на роль Гамлета в новом фильме. А затем режиссёр, изучив портфолио, приглашает на кастинг пятерых, с которыми готов работать. Деятельность ассистента по актёрам — это подзапрос, или внутренний запрос. А выбор лучшего Гамлета из пяти — внешний запрос.
image
Подзапросы могут выполняться в разных частях запроса.
Если подзапрос записать в блоке FROM, то SELECT выберет данные из таблицы, полученной в результате работы подзапроса. Имя этой таблицы указывают во внутреннем запросе, к её столбцам обращаются во внешнем. Подзапрос записывают в круглых скобках:
Скопировать кодSQL
SELECT ПОДЗАПРОС_1.название_столбца, ПОДЗАПРОС_1.название_столбца_2 FROM -- Для лучшей читабельности кода, переносите подзапрос на новую строку -- отделяйте подзапросы отступами (SELECT название_столбца, название_столбца_2 FROM название_таблицы WHERE название_столбца = значение) AS ПОДЗАПРОС_1; -- не забывайте давать имя подзапросу в блоке FROM
idNamegenreauthordate_pubpagespricerating
1Капитанская дочка5Александр Пушкин1836-01-011301504.7
2Отцы и дети1Иван Тургенев1861-01-012402074.7
3Вишнёвый сад7Антон Чехов1903-01-01601384.4
4Война и мир1Лев Толстой1869-01-011274
5Анна Каренина1Лев Толстой1878-01-0110001264.6
6Цветы для Элджернона4Дэниел Киз1959-01-012502804.7
7Похороните меня за плинтусом...5Павел Санаев1994-01-011841984.1
8Зелёная миля9Стивен Кинг1996-01-014201904.8
9Унесённые ветром1Маргарет Митчел1936-01-01624358
10Три товарища1Эрих Мария Ремарк1929-01-014701964.7
11На Западном фронте без перемен4Эрих Мария Ремарк1945-01-012002734.6
12Триумфальная арка1Эрих Мария Ремарк1956-01-015202204.7
13Чёрный обелиск1Эрих Мария Ремарк1961-01-014804204.6
14Ночь в Лиссабоне1Эрих Мария Ремарк1959-01-012501874.7
15Жизнь взаймы1Эрих Мария Ремарк1927-01-012402404.6
16Станция на горизонте1Эрих Мария Ремарк1830-01-012104.1
17Евгений Онегин1Александр Пушкин1820-01-017363304.7
Напишем запрос, который подсчитает среднее количество проставленных оценок по жанрам книг. Нужно вызвать две агрегирующие функции: COUNT() найдёт количество, AVG() подсчитает среднее. Однако написать SELECT AVG(COUNT(rating)) не выйдет — получим сообщение об ошибке: «агрегирующие функции не могут быть вложенными» (англ. ERROR: aggregate function calls cannot be nested).
Сначала нужно вызвать актёров на пробы, а потом просмотреть пробы и выбрать лучшего. Найдём количество оценок по жанрам в подзапросе, а во внешнем запросе рассчитаем среднее полученных значений:
Скопировать кодSQL
SELECT AVG(Sub.count_rating) AS avg_count_rating FROM (SELECT COUNT(rating) AS count_rating FROM books GROUP BY genre) AS Sub;
Разберём результат работы внутреннего запроса:
Column 1Column 2
52
19
71
42
91
В внутреннем запросе подсчитали число оценок книг каждого жанра и сохранили его в поле count_rating. К таблице-результату работы подзапроса теперь можно обратиться во внешнем запросе.
Вызовем столбец count_rating таблицы Sub (от англ. subquery, «подзапрос») и найдём среднее функцией AVG():
Скопировать кодSQL
3
Что это значит? Таблица содержит в среднем по 3 оценки, проставленные каждому жанру.
Внутренние запросы могут понадобиться в разных блоках внешнего запроса. Например, устроим подзапрос в блоке WHERE. Тогда выберутся данные из столбца со значениями, сгенерированными в результате работы подзапроса:
Скопировать кодSQL
SELECT название_столбца, название_столбца_1 FROM название_таблицы WHERE название_столбца = (SELECT столбец_1 FROM название_таблицы_2 WHERE столбец_1 = значение);
Дополним шаблон конструкцией IN, чтобы собирать данные из нескольких столбцов:
Скопировать кодSQL
SELECT название_столбца, название_столбца_1 FROM название_таблицы WHERE название_столбца IN (SELECT столбец_1 FROM название_таблицы_2 WHERE столбец_1 = значение_1 OR столбец_1 = значение_2);
Пример: добавим к таблице books таблицу genre с кодами жанров и их названиями:
idname
1Роман
2Басня
3Комедия
4Рассказ
5Повесть
6Поэма
7Пьеса
8Научно-образовательная
9Драма
10Сказка
11Трагедия
Выберем книги жанра «Роман» из таблицы books:
Скопировать кодSQL
SELECT name, genre FROM books WHERE genre = (SELECT id FROM genre WHERE name ='Роман');
Column 1Column 2
1Роман
Подзапрос указывает, что нужно выбрать из таблицы genre те id, которым соответствует жанр — «Роман». Результат подзапроса: 1. Лишь одно значение из таблицы genre означает романы.
Перейдём к внешнему запросу. Он выбирает названия и жанры из таблицы books. Но только те, чей жанр равен результату внутреннего запроса, то есть единице.
Результат работы внешнего запроса:
namegenre
Отцы и дети1
Война и мир1
Анна Каренина1
Унесенные ветром1
Три товарища1
Триумфальная арка1
Черный обелиск1
Ночь в Лиссабоне1
Жизнь взаймы1
Станция на горизонте1
Евгений Онегин1
Напишем похожий запрос. Выберем не только романы, но и повести с драмами. Вот где пригодится IN:
Скопировать кодSQL
SELECT name, genre FROM books WHERE genre IN (SELECT id FROM genre WHERE name IN ( 'Роман', 'Драма', 'Повесть' ));
Конструкция IN во внутреннем запросе выбирает id из таблицы жанров с названиями 'Роман', 'Драма' или 'Повесть'. В результате получим три id: 1, 9, 5. Эти значения передаются во внешний запрос.
Внешний запрос сообщает, что нужно выбрать названия и жанры из таблицы books, где жанры равны значениям из результирующего списка внутреннего запроса. То есть, 1, 9 или 5.
namegenre
Капитанская дочка5
Отцы и дети1
Война и мир1
Анна Каренина1
Похороните меня за плинтусом...5
Зеленая миля9
Унесенные ветром1
Три товарища1
Триумфальная арка1
Черный обелиск1
Ночь в Лиссабоне1
Жизнь взаймы1
Станция на горизонте1
Евгений Онегин1