Подзапросы
Подзапросы представляют выражения SELECT
, которые встроены в другие запросы SQL. Рассмотрим простейший пример применения подзапросов.
Базы данных:
CREATE DATABASE IF NOT EXISTS test;
USE test;
CREATE TABLE Products
(
Id INT AUTO_INCREMENT PRIMARY KEY,
ProductName VARCHAR(30) NOT NULL,
Manufacturer VARCHAR(20) NOT NULL,
ProductCount INT DEFAULT 0,
Price DECIMAL NOT NULL
);
CREATE TABLE Orders
(
Id INT AUTO_INCREMENT PRIMARY KEY,
ProductId INT NOT NULL,
ProductCount INT DEFAULT 1,
CreatedAt DATE NOT NULL,
Price DECIMAL NOT NULL,
FOREIGN KEY (ProductId) REFERENCES Products(Id) ON DELETE CASCADE
);
Заполнение баз данных:
USE test;
INSERT INTO Products (ProductName, Manufacturer, ProductCount, Price)
VALUES ('iPhone X', 'Apple', 2, 76000),
('iPhone 8', 'Apple', 2, 51000),
('iPhone 7', 'Apple', 5, 42000),
('Galaxy S9', 'Samsung', 2, 56000),
('Galaxy S8', 'Samsung', 1, 46000),
('Honor 10', 'Huawei', 2, 26000),
('Nokia 8', 'HMD Global', 6, 38000);
INSERT INTO Orders (ProductId, CreatedAt, ProductCount, Price)
VALUES
(
(SELECT Id FROM Products WHERE ProductName='Galaxy S8'),
'2018-05-21',
2,
(SELECT Price FROM Products WHERE ProductName='Galaxy S8')
),
(
(SELECT Id FROM Products WHERE ProductName='iPhone X'),
'2018-05-23',
1,
(SELECT Price FROM Products WHERE ProductName='iPhone X')
),
(
(SELECT Id FROM Products WHERE ProductName='iPhone 8'),
'2018-05-21',
1,
(SELECT Price FROM Products WHERE ProductName='iPhone 8')
);
При добавлении данных в таблицу Orders
как раз используются подзапросы. Например, первый заказ был сделан на товар
Galaxy S8. Соответственно в
таблицу Orders
нам надо сохранить информацию о заказе, где поле ProductId
указывает на Id
товара Galaxy S8, поле
Price
- на его цену. Но на момент написания запроса нам может быть неизвестен ни Id
покупателя, ни Id
товара, ни цена товара. В этом
случае можно выполнить подзапрос в виде:
(SELECT Price FROM Products WHERE ProductName='iPhone 8')
Подзапрос выполняет команду SELECT
и заключается в скобки. В данном же случае при добавлении одного товара
выполняется два подзапроса. Каждый подзапрос возвращает одного скалярное значение, например, числовой идентификатор.
В примере выше подзапросы выполнялись к другой таблице, но могут выполняться и к той же, для которой вызывается
основной запрос. Например, найдем товары из таблицы Products
, которые имеют минимальную цену:
SELECT * FROM Products
WHERE Price = (SELECT MIN(Price) FROM Products);
Или найдем товары, цена которых выше средней:
SELECT * FROM Products
WHERE Price > (SELECT AVG(Price) FROM Products);
Коррелирующие и некоррелирующие подзапросы
Подзапросы бывают коррелирующими и некоррелирующими. В примерах выше команды SELECT
фактически выполняли один
подзапрос для всех строк, извлекаемых
командой. Например, подзапрос возвращает минимальную или среднюю цену, которая не изменится, сколько бы мы строк не
выбирали в основном запросе.
То есть результат подзапроса не зависел от строк, которые выбираются в основном запросе. И такой подзапрос
выполняется один раз для всего внешнего
запроса.
Но также можно использовать и коррелирующие подзапросы (correlated subquery)
, результаты
которых зависят от строк, которые выбираются в основном запросе.
Например, выберем все заказы из таблицы Orders
, добавив к ним информацию о товаре:
SELECT CreatedAt, Price,
(SELECT ProductName FROM Products
WHERE Products.Id = Orders.ProductId) AS Product
FROM Orders;
В данном случае для каждой строки из таблицы Orders
будет выполняться подзапрос, результат которого зависит от
столбца ProductId
. И каждый подзапрос может возвращать различные данные.
Коррелирующий подзапрос может выполняться и для той же таблицы, к которой выполняется основной запрос.
Например, выберем из таблицы Products
те товары, стоимость которых выше средней цены товаров для данного производителя:
SELECT ProductName,
Manufacturer,
Price,
(SELECT AVG(Price) FROM Products AS SubProds
WHERE SubProds.Manufacturer=Prods.Manufacturer) AS AvgPrice
FROM Products AS Prods
WHERE Price >
(SELECT AVG(Price) FROM Products AS SubProds
WHERE SubProds.Manufacturer=Prods.Manufacturer);
Здесь определено два коррелирующих подзапроса. Первый подзапрос определяет спецификацию столбца AvgPrice
.
Он будет выполняться для каждой строки, извлекаемой из таблицы Products
. В подзапрос передается производитель товара
и на его основе выбирается средняя цена для товаров именно этого производителя. И так как производитель у товаров может отличаться, то и
результат подзапроса в каждом случае также может отличаться.
Второй подзапрос аналогичен, только он используется для фильтрации извлекаемых из таблицы Products
. И также он будет
выполняться для каждой строки.
Чтобы избежать двойственности при фильтрации в подзапросе при сравнении производителей
(SubProds.Manufacturer=Prods.Manufacturer)
для внешней выборки установлен псевдоним Prods
, а для
выборки из подзапросов определен псевдоним SubProds
.
Следует учитывать, что коррелирующие подзапросы выполняются для каждой отдельной строки выборки, то выполнение таких подзапросов может замедлять выполнение всего запроса в целом.