Вложенные запросы (SQL Server)
Применимо: SQL Server База данных SQL Azure Управляемый экземпляр SQL Azure azure Synapse Analytics Analytics Platform System (PDW)
Вложенный запрос — это запрос, который используется внутри инструкции SELECT
, INSERT
, UPDATE
или DELETE
или внутри другого вложенного запроса.
Примеры кода Transact-SQL в этой статье используют AdventureWorks2022
базу данных или AdventureWorksDW2022
пример базы данных, которую можно скачать с домашней страницы примеров и проектов сообщества Microsoft SQL Server.
Подзапрос может быть использован везде, где разрешены выражения. В этом примере вложенный запрос используется в качестве выражения столбца с именем MaxUnitPrice в инструкции SELECT
.
USE AdventureWorks2022;
GO
SELECT Ord.SalesOrderID, Ord.OrderDate,
(SELECT MAX(OrdDet.UnitPrice)
FROM Sales.SalesOrderDetail AS OrdDet
WHERE Ord.SalesOrderID = OrdDet.SalesOrderID) AS MaxUnitPrice
FROM Sales.SalesOrderHeader AS Ord;
GO
Основы вложенных запросов
Вложенный запрос по-другому называют внутренним запросом или внутренней операцией выбора, в то время как инструкцию, содержащую вложенный запрос, называют внешним запросом или внешней операцией выбора.
Многие инструкции Transact-SQL, включающие вложенные запросы, можно также сформулировать как соединения. Другие запросы могут быть осуществлены только с помощью подзапросов. В Transact-SQL обычно нет разницы в производительности между инструкцией, которая включает в себя вложенный запрос и семантически эквивалентную версию, которая не имеет значения. Сведения об архитектуре о том, как SQL Server обрабатывает запросы, см. в разделе "Обработка инструкций SQL". Однако в некоторых случаях, когда проверяется существование, соединения показывают лучшую производительность. В противном случае для устранения дубликатов вложенный запрос должен обрабатываться для получения каждого результата внешнего запроса. В таких случаях метод работы соединений дает лучшие результаты.
В следующем примере показаны вложенные запросы SELECT
и соединение SELECT
, возвращающие один и тот же результирующий набор и план выполнения:
USE AdventureWorks2022;
GO
/* SELECT statement built using a subquery. */
SELECT [Name]
FROM Production.Product
WHERE ListPrice =
(SELECT ListPrice
FROM Production.Product
WHERE [Name] = 'Chainring Bolts' );
GO
/* SELECT statement built using a join that returns
the same result set. */
SELECT Prd1.[Name]
FROM Production.Product AS Prd1
JOIN Production.Product AS Prd2
ON (Prd1.ListPrice = Prd2.ListPrice)
WHERE Prd2.[Name] = 'Chainring Bolts';
GO
Вложенный во внешнюю инструкцию SELECT запрос, имеет следующие компоненты:
- обычный запрос
SELECT
, включающий обычные компоненты списка выборки; - обычное предложение
FROM
, включающее одно или несколько имен таблиц или представлений. - Необязательное предложение
WHERE
. - Необязательное предложение
GROUP BY
. - Необязательное предложение
HAVING
.
Запрос SELECT вложенного запроса всегда заключен в скобки. Он не может включать предложение COMPUTE
или FOR BROWSE
предложение, и может включать ORDER BY
только предложение, если также указано предложение TOP.
Вложенный запрос может быть включен в предложение WHERE
или HAVING
внешней инструкции SELECT
, INSERT
, UPDATE
или DELETE
или в другой вложенный запрос. Возможно создавать вложенность до 32-го уровня, хотя ограничения меняются в зависимости от объема доступной памяти и сложности других выражений в запросе. Отдельные запросы не поддерживают вложение до 32 уровней. Подзапрос может появляться везде, где может использоваться выражение, если он возвращает одно значение.
Если таблица отображается только во вложенном запросе, а не во внешнем запросе, столбцы из этой таблицы не могут быть включены в выходные данные (список выбора внешнего запроса).
Инструкции, включающие вложенные запросы, обычно имеют один из следующих форматов:
WHERE expression [NOT] IN (subquery)
WHERE expression comparison_operator [ANY | ALL] (subquery)
WHERE [NOT] EXISTS (subquery)
В некоторых инструкциях Transact-SQL вложенный запрос можно оценить как если бы это был независимый запрос. Концептуально результаты вложенных запросов заменяются внешним запросом (хотя это не обязательно, как SQL Server фактически обрабатывает инструкции Transact-SQL с вложенными запросами).
Существуют три основных типа подзапросов, которые:
- работают в списках, указанных с помощью ключевого слова
IN
, или тех, которые оператор сравнения изменил с помощью ключевого словаANY
илиALL
; - вставлены оператором немодифицированных сравнений и должны возвращать одно значение;
- являются проверками на существование, начинающимися с ключевого слова
EXISTS
.
Правила вложенных запросов
На вложенный запрос распространяются следующие ограничения:
- Список выбора вложенного запроса, начинающийся с оператора сравнения, может включать только одно выражение или имя столбца (за исключением операторов
EXISTS
иIN
в инструкцииSELECT *
или в списке соответственно). - Если предложение
WHERE
внешнего запроса включает имя столбца, оно должно быть совместимо для соединения со столбцом в списке выбора вложенного запроса. - Типы данных ntext, текста и изображения нельзя использовать в списке вложенных запросов.
- Так как они должны возвращать одно значение, вложенные запросы, представленные оператором немодифицированного сравнения (один не следует ключевому слову
ANY
илиALL
) не могут включатьGROUP BY
иHAVING
предложения. - Ключевое
DISTINCT
слово нельзя использовать с вложенными запросами, включающимиGROUP BY
. - Не
COMPUTE
удается указать предложения иINTO
предложения. - Предложение
ORDER BY
может быть указано только вместе с предложениемTOP
. - Представление, созданное с помощью вложенных запросов, не может быть обновлено.
- Список выбора вложенного запроса, начинающегося с предложения
EXISTS
, по соглашению содержит звездочку (*
) вместо отдельного имени столбца. Правила для вложенного запроса, начинающегося с предложенияEXISTS
, являются такими же, как для стандартного списка выбора, поскольку вложенный запрос, начинающийся с предложенияEXISTS
, проводит проверку на существование и возвращает TRUE или FALSE вместо данных.
Определение имен столбцов в вложенных запросах
В следующем примере BusinessEntityID
столбец в WHERE
предложении внешнего запроса неявно определяется именем таблицы в предложении внешнего запроса FROM
(Sales.Store
). Ссылка на CustomerID
список выбора вложенных запросов квалифицируется предложением вложенных запросов FROM
, то есть таблицей Sales.Customer
.
USE AdventureWorks2022;
GO
SELECT [Name]
FROM Sales.Store
WHERE BusinessEntityID NOT IN
(SELECT CustomerID
FROM Sales.Customer
WHERE TerritoryID = 5);
GO
Общее правило состоит в том, что имена столбцов в инструкции неявно уточняются именем таблицы, указанной в предложении FROM
того же уровня вложенности. Если столбец не существует в таблице, на которую FROM
ссылается предложение вложенного запроса, он неявно определяется таблицей, на которую FROM
ссылается предложение внешнего запроса.
Вот как выглядит запрос с указанными неявными предположениями:
USE AdventureWorks2022;
GO
SELECT [Name]
FROM Sales.Store
WHERE Sales.Store.BusinessEntityID NOT IN
(SELECT Sales.Customer.CustomerID
FROM Sales.Customer
WHERE TerritoryID = 5);
GO
Никогда не неправильно указать имя таблицы явным образом, и всегда можно переопределить неявные предположения о именах таблиц с явной квалификацией.
Внимание
Если столбец, на который есть ссылка во вложенном запросе, не существует в таблице, указанной в предложении FROM
вложенного запроса, но существует в таблице, на которую ссылается предложение FROM
внешнего запроса, запрос будет выполнен без ошибок. SQL Server неявно квалифифиирует столбец в вложенный запрос с именем таблицы во внешнем запросе.
Несколько уровней вложения
Каждый вложенный запрос, в свою очередь, может содержать один или более вложенных запросов. В инструкцию можно вложить любое количество вложенных запросов.
Следующий запрос осуществляет поиск сотрудников, занимающих должность менеджера по продажам.
USE AdventureWorks2022;
GO
SELECT LastName, FirstName
FROM Person.Person
WHERE BusinessEntityID IN
(SELECT BusinessEntityID
FROM HumanResources.Employee
WHERE BusinessEntityID IN
(SELECT BusinessEntityID
FROM Sales.SalesPerson)
);
GO
Вот результирующий набор.
LastName FirstName
-------------------------------------------------- -----------------------
Jiang Stephen
Abbas Syed
Alberts Amy
Ansman-Wolfe Pamela
Campbell David
Carson Jillian
Ito Shu
Mitchell Linda
Reiter Tsvi
Saraiva Jos
Vargas Garrett
Varkey Chudukatil Ranjit
Valdez Rachel
Tsoflias Lynn
Pak Jae
Blythe Michael
Mensa-Annan Tete
(17 row(s) affected)
Самый глубоко вложенный запрос возвращает идентификаторы указанных сотрудников. Запрос уровнем выше оперирует с полученными идентификаторами и возвращает контактные идентификаторы сотрудников. Наконец, во внешнем запросе по полученным контактным идентификаторам извлекаются имена сотрудников.
Этот запрос также можно выразить с помощью соединения:
USE AdventureWorks2022;
GO
SELECT LastName, FirstName
FROM Person.Person c
INNER JOIN HumanResources.Employee e
ON c.BusinessEntityID = e.BusinessEntityID
JOIN Sales.SalesPerson s
ON e.BusinessEntityID = s.BusinessEntityID;
GO
Коррелированные вложенные запросы
Результат для нескольких запросов может быть получен путем выполнения одного вложенного запроса и подстановки полученного результата или результатов в предложение WHERE
внешнего запроса. В запросах, содержащих коррелированные вложенные запросы (также называемые повторяющимися вложенными запросами), вложенный запрос зависит по значению от внешнего запроса. Это означает, что выполнение вложенного запроса повторяется по одному разу для каждой строки, которая может быть выбрана внешним запросом.
Этот запрос получает один экземпляр первого и фамилии каждого сотрудника, для которого бонус в SalesPerson
таблице равен 5000, и для которого идентификационные номера сотрудников совпадают в Employee
таблицах и SalesPerson
таблицах.
USE AdventureWorks2022;
GO
SELECT DISTINCT c.LastName, c.FirstName, e.BusinessEntityID
FROM Person.Person AS c JOIN HumanResources.Employee AS e
ON e.BusinessEntityID = c.BusinessEntityID
WHERE 5000.00 IN
(SELECT Bonus
FROM Sales.SalesPerson sp
WHERE e.BusinessEntityID = sp.BusinessEntityID) ;
GO
Вот результирующий набор.
LastName FirstName BusinessEntityID
-------------------------- ---------- ------------
Ansman-Wolfe Pamela 280
Saraiva José 282
(2 row(s) affected)
Предыдущий вложенный запрос в этом операторе нельзя оценивать независимо от внешнего запроса. Он должен иметь значение Employee.BusinessEntityID
, но это значение изменяется, так как SQL Server проверяет различные строки в Employee
.
Именно так вычисляется этот запрос: SQL Server рассматривает каждую строку Employee
таблицы для включения в результаты, заменив значение в каждой строке внутренним запросом.
Например, если SQL Server сначала проверяет строку для Syed Abbas
, переменная Employee.BusinessEntityID
принимает значение 285
, которое SQL Server заменяет внутренним запросом. Эти два примера запроса представляют собой декомпозицию предыдущего примера со связанным вложенным запросом.
USE AdventureWorks2022;
GO
SELECT Bonus
FROM Sales.SalesPerson
WHERE BusinessEntityID = 285;
GO
Результатом является 0,00 (Syed Abbas
не получил бонус, потому что они не являются продавцом), поэтому внешний запрос оценивается следующим образом:
USE AdventureWorks2022;
GO
SELECT LastName, FirstName
FROM Person.Person AS c JOIN HumanResources.Employee AS e
ON e.BusinessEntityID = c.BusinessEntityID
WHERE 5000 IN (0.00);
GO
Так как это значение равно false, строка для Syed Abbas
не включается в результаты предыдущего примера запроса с соответствующим вложенным запросом. То же самое действие выполняется со строкой для сотрудника Pamela Ansman-Wolfe
. Вы видите, что эта строка включена в результаты, так как WHERE 5000 IN (5000)
включает результаты.
Коррелированные вложенные запросы могут также включать в предложение FROM
функции с табличным значением, указывая для них в качестве аргументов столбцы таблиц из внешнего запроса. В этом случае для каждой строки внешнего запроса выполняется функция с табличным значением, как и в случае с вложенным запросом.
Типы вложенных запросов
Вложенные запросы могут быть указаны во многих местах:
- С псевдонимами. Дополнительные сведения см . в подразделах с псевдонимами таблиц.
- С
IN
илиNOT IN
. Дополнительные сведения см. в разделах Вложенные запросы с ключевым словом IN и Вложенные запросы с ключевым словом NOT IN. - В инструкциях
UPDATE
,DELETE
иINSERT
. Дополнительные сведения см. в разделе Вложенные запросы в инструкциях UPDATE, DELETE и INSERT. - С операторами сравнения. Дополнительные сведения см . в подразделах с операторами сравнения.
- С
ANY
,SOME
илиALL
. Дополнительные сведения см. в разделе "Операторы сравнения", измененные ANY, SOME или ALL. - С
IS [NOT] DISTINCT FROM
. Дополнительные сведения см. в статье IS [NOT] DISTINCT FROM (Transact-SQL). - С
EXISTS
илиNOT EXISTS
. Дополнительные сведения см. в разделах Вложенные запросы с оператором EXISTS и Вложенные запросы с оператором NOT EXISTS. - Вместо выражения. Дополнительные сведения см . в подразделах, используемых вместо выражения.
Вложенные запросы с псевдонимами таблиц
Многие инструкции, где вложенный и внешний запросы ссылаются на одну и ту же таблицу, могут быть переформулированы как самосоединения (соединения таблицы с самой собой). Например, можно найти адреса сотрудников из конкретного региона с помощью вложенного запроса:
USE AdventureWorks2022;
GO
SELECT StateProvinceID, AddressID
FROM Person.Address
WHERE AddressID IN
(SELECT AddressID
FROM Person.Address
WHERE StateProvinceID = 39);
GO
Вот результирующий набор.
StateProvinceID AddressID
----------- -----------
39 942
39 955
39 972
39 22660
(4 row(s) affected)
Можно также использовать самосоединение:
USE AdventureWorks2022;
GO
SELECT e1.StateProvinceID, e1.AddressID
FROM Person.Address AS e1
INNER JOIN Person.Address AS e2
ON e1.AddressID = e2.AddressID
AND e2.StateProvinceID = 39;
GO
Псевдонимы e1
таблиц и e2
необходимы, так как таблица, присоединенная к себе, отображается в двух разных ролях. Псевдонимы можно также использовать во вложенных запросах, где и внешний, и внутренний запросы ссылаются на одну и ту же таблицу.
USE AdventureWorks2022;
GO
SELECT e1.StateProvinceID, e1.AddressID
FROM Person.Address AS e1
WHERE e1.AddressID IN
(SELECT e2.AddressID
FROM Person.Address AS e2
WHERE e2.StateProvinceID = 39);
GO
Явные псевдонимы таблицы позволяют ясно, что ссылка на Person.Address
вложенную запросу не означает то же самое, что и ссылка во внешнем запросе.
Вложенные запросы с in
Результат вложенного запроса, в котором присутствует ключевое слово IN
(или NOT IN
) — это список из нуля или более значений. После того как вложенный запрос вернул результат, он используется внешним запросом.
Следующий запрос ищет названия всех колес, произведенных компанией Adventure Works Cycles.
USE AdventureWorks2022;
GO
SELECT [Name]
FROM Production.Product
WHERE ProductSubcategoryID IN
(SELECT ProductSubcategoryID
FROM Production.ProductSubcategory
WHERE [Name] = 'Wheels');
GO
Вот результирующий набор.
Name
----------------------------
LL Mountain Front Wheel
ML Mountain Front Wheel
HL Mountain Front Wheel
LL Road Front Wheel
ML Road Front Wheel
HL Road Front Wheel
Touring Front Wheel
LL Mountain Rear Wheel
ML Mountain Rear Wheel
HL Mountain Rear Wheel
LL Road Rear Wheel
ML Road Rear Wheel
HL Road Rear Wheel
Touring Rear Wheel
(14 row(s) affected)
Эта инструкция выполняется в два шага. Во-первых, внутренний запрос возвращает идентификационный номер подкатегории, соответствующий имени Wheel
(17
). Во-вторых, это значение заменяется на внешний запрос, который находит имена продуктов, которые идут с идентификаторами подкатегории.Production.Product
USE AdventureWorks2022;
GO
SELECT [Name]
FROM Production.Product
WHERE ProductSubcategoryID IN ('17');
GO
Единственная разница в использовании соединения и вложенного запроса для этой и аналогичных задач заключается в том, что объединение позволяет включить в результат столбцы, содержащиеся в нескольких таблицах. Например: если нужно включить в результат название подкатегории, следует пользоваться соединением:
USE AdventureWorks2022;
GO
SELECT p.[Name], s.[Name]
FROM Production.Product p
INNER JOIN Production.ProductSubcategory s
ON p.ProductSubcategoryID = s.ProductSubcategoryID
AND s.[Name] = 'Wheels';
GO
Вот результирующий набор.
Name
LL Mountain Front Wheel Wheels
ML Mountain Front Wheel Wheels
HL Mountain Front Wheel Wheels
LL Road Front Wheel Wheels
ML Road Front Wheel Wheels
HL Road Front Wheel Wheels
Touring Front Wheel Wheels
LL Mountain Rear Wheel Wheels
ML Mountain Rear Wheel Wheels
HL Mountain Rear Wheel Wheels
LL Road Rear Wheel Wheels
ML Road Rear Wheel Wheels
HL Road Rear Wheel Wheels
Touring Rear Wheel Wheels
(14 row(s) affected)
Следующий запрос ищет названия всех поставщиков, имеющих высокий кредитный рейтинг, у которых компания Adventure Works Cycles заказала как минимум 20 позиций, и средний срок поставки у которых не превышает 16 дней.
USE AdventureWorks2022;
GO
SELECT [Name]
FROM Purchasing.Vendor
WHERE CreditRating = 1
AND BusinessEntityID IN
(SELECT BusinessEntityID
FROM Purchasing.ProductVendor
WHERE MinOrderQty >= 20
AND AverageLeadTime < 16);
GO
Вот результирующий набор.
Name
--------------------------------------------------
Compete Enterprises, Inc
International Trek Center
First National Sport Co.
Comfort Road Bicycles
Circuit Cycles
First Rate Bicycles
Jeff's Sporting Goods
Competition Bike Training Systems
Electronic Bike Repair & Supplies
Crowley Sport
Expert Bike Co
Team Athletic Co.
Compete, Inc.
(13 row(s) affected)
Выполняется внутренний запрос и возвращаются номера идентификаторов поставщиков, которые соответствуют определениям вложенного запроса. Затем выполняется внешний запрос. В предложение внутреннего и внешнего запроса можно включить несколько условий WHERE
.
При использовании соединения тот же запрос будет выражен так:
USE AdventureWorks2022;
GO
SELECT DISTINCT [Name]
FROM Purchasing.Vendor v
INNER JOIN Purchasing.ProductVendor p
ON v.BusinessEntityID = p.BusinessEntityID
WHERE CreditRating = 1
AND MinOrderQty >= 20
AND AverageLeadTime < 16;
GO
Соединение всегда может быть выражено в виде вложенного запроса. Вложенный запрос часто, но не всегда может быть выражен в виде соединения. Это связано с симметричными соединениями: вы можете объединить таблицу A
B
в любом порядке и получить один и тот же ответ. То же самое не верно, если используется вложенный запрос.
Вложенные запросы с NOT IN
Вложенные запросы с ключевым словом NOT IN
также возвращают список из нуля или более значений.
Следующий запрос находит имена продуктов, которые не готовы к велосипедам.
USE AdventureWorks2022;
GO
SELECT [Name]
FROM Production.Product
WHERE ProductSubcategoryID NOT IN
(SELECT ProductSubcategoryID
FROM Production.ProductSubcategory
WHERE [Name] = 'Mountain Bikes'
OR [Name] = 'Road Bikes'
OR [Name] = 'Touring Bikes');
GO
Эта инструкция не может быть преобразована в соединение. Аналогичное не равное соединение имеет другое значение: он находит имена продуктов, которые находятся в некоторых подкатегориях, которые не являются готовым велосипедом.
Вложенные запросы в инструкциях UPDATE, DELETE и INSERT
Вложенные запросы могут быть вложены в UPDATE
операторы , DELETE
INSERT
и SELECT
манипуляции данными (DML).
В следующем примере значение в ListPrice
столбце Production.Product
в таблице удвоится. Вложенный запрос в предложении ссылается на таблицу, чтобы ограничить строки, обновленные в таблице Product, только теми, которые предоставлены BusinessEntity
1540
.Purchasing.ProductVendor
WHERE
USE AdventureWorks2022;
GO
UPDATE Production.Product
SET ListPrice = ListPrice * 2
WHERE ProductID IN
(SELECT ProductID
FROM Purchasing.ProductVendor
WHERE BusinessEntityID = 1540);
GO
Ниже приведен эквивалентный UPDATE
оператор с помощью соединения:
USE AdventureWorks2022;
GO
UPDATE Production.Product
SET ListPrice = ListPrice * 2
FROM Production.Product AS p
INNER JOIN Purchasing.ProductVendor AS pv
ON p.ProductID = pv.ProductID AND BusinessEntityID = 1540;
GO
Для ясности, если на одну и ту же таблицу ссылаются в других вложенных запросах, используйте псевдонимы целевой таблицы:
USE AdventureWorks2022;
GO
UPDATE p
SET ListPrice = ListPrice * 2
FROM Production.Product AS p
INNER JOIN Purchasing.ProductVendor AS pv
ON p.ProductID = pv.ProductID AND BusinessEntityID = 1540;
GO
Вложенные запросы с операторами сравнения
Вложенные запросы могут быть введены с помощью одного из операторов сравнения (=
, < >
, >
, > =
, <
, ! >
, ! <
или < =
).
Вложенный запрос, указанный с помощью неизмененного оператора сравнения (оператора сравнения, за которым не следуют ключевые слова ANY
или ALL
), должен возвратить одиночное значение, а не список значений как вложенные запросы, указанные с помощью IN
. Если такой вложенный запрос возвращает более одного значения, SQL Server отображает сообщение об ошибке.
Чтобы использовать подзапрос, начинающийся с немодифицированного оператора сравнения, необходимо достаточно хорошо знать свои данные и природу проблемы, чтобы быть уверенным, что вложенный запрос возвратит точно одно значение.
Например, если предполагается, что каждый сотрудник по продажам охватывает только одну территорию продаж, и вы хотите найти клиентов, расположенных на территории, на которую распространяется Linda Mitchell
, можно написать заявление с вложенным запросом, представленным с простым =
оператором сравнения.
USE AdventureWorks2022;
GO
SELECT CustomerID
FROM Sales.Customer
WHERE TerritoryID =
(SELECT TerritoryID
FROM Sales.SalesPerson
WHERE BusinessEntityID = 276);
GO
Однако если сотрудник Linda Mitchell
работал более чем с одной территорией продаж, вы получите сообщение об ошибке. Вместо оператора сравнения =
может использоваться формулировка IN
(также может использоваться =ANY
).
Вложенные запросы, начинающиеся с немодифицированных операторов сравнения, часто включают агрегатные функции, потому что они возвращают одиночное значение. Например, следующая инструкция находит названия всех продуктов, у которых цена по прейскуранту больше, чем средняя цена по прейскуранту.
USE AdventureWorks2022;
GO
SELECT [Name]
FROM Production.Product
WHERE ListPrice >
(SELECT AVG (ListPrice)
FROM Production.Product);
GO
Так как вложенные запросы, введенные с немодифицированными операторами сравнения, должны возвращать одно значение, они не могут включать GROUP BY
или HAVING
предложения, если вы не знаете GROUP BY
HAVING
, что само предложение возвращает одно значение. Например, следующий запрос находит продукты по цене выше, чем самый низкий ценовый продукт, который находится в ProductSubcategoryID
14
.
USE AdventureWorks2022;
GO
SELECT [Name]
FROM Production.Product
WHERE ListPrice >
(SELECT MIN (ListPrice)
FROM Production.Product
GROUP BY ProductSubcategoryID
HAVING ProductSubcategoryID = 14);
GO
Операторы сравнения, измененные ANY
, SOME
или ALL
Операторы сравнения со вложенными запросами могут быть уточнены с помощью ключевых слов ALL
или ANY
. Модификатор SOME
является эквивалентом модификатора ANY
в стандарте ISO. Дополнительные сведения об этих операторах сравнения SOME и ANY см. в этой статье.
Вложенные запросы с измененными операторами сравнения возвращают список из нуля или более значений и могут включать предложения GROUP BY
или HAVING
. Эти вложенные запросы могут быть переформулированы с использованием ключевого слова EXISTS
.
> Использование оператора сравнения в качестве примера > ALL
означает больше каждого значения. Другими словами, это сравнение с максимальным значением. Например, > ALL (1, 2, 3)
означает "больше 3". > ANY
означает "больше по крайней мере одного значения", т. е. "больше минимума". Поэтому > ANY (1, 2, 3)
означает "больше 1".
Чтобы строка результата вложенного запроса с > ALL
удовлетворяла условию, заданному внешним запросом, значение в столбце, для которого вводится вложенный запрос, должно быть больше каждого значения из списка, возвращаемого вложенным запросом.
Аналогичным образом, чтобы строка результата вложенного запроса с > ANY
удовлетворяла условию, заданному внешним запросом, значение в столбце, для которого вводится вложенный запрос, должно быть больше хотя бы одного значения из списка, возвращаемого вложенным запросом.
Следующий запрос сдержит пример вложенного запроса, используемого оператором сравнения с модификатором ANY
. Он вернет все продукты, цены на которые больше или равны максимальной цене в любой подкатегории продуктов.
USE AdventureWorks2022;
GO
SELECT [Name]
FROM Production.Product
WHERE ListPrice >= ANY
(SELECT MAX (ListPrice)
FROM Production.Product
GROUP BY ProductSubcategoryID);
GO
Для каждой подкатегории продуктов внутренний запрос найдет максимальную цену. Внешний запрос получит эти значения и определит, какие цены отдельных продуктов больше или равны максимальной цене в любой из подкатегорий продуктов. Если ANY
значение изменено ALL
, запрос возвращает только те продукты, цены на которые больше или равны всем ценам списка, возвращаемым во внутреннем запросе.
Если вложенный запрос не возвращает никаких значений, весь запрос не возвращает какие-либо значения.
Оператор = ANY
эквивалентен оператору IN
. Например, чтобы найти названия всех колес, производимых компанией Adventure Works Cycle, можно использовать IN
или = ANY
.
--Using = ANY
USE AdventureWorks2022;
GO
SELECT [Name]
FROM Production.Product
WHERE ProductSubcategoryID = ANY
(SELECT ProductSubcategoryID
FROM Production.ProductSubcategory
WHERE Name = 'Wheels');
GO
--Using IN
USE AdventureWorks2022;
GO
SELECT [Name]
FROM Production.Product
WHERE ProductSubcategoryID IN
(SELECT ProductSubcategoryID
FROM Production.ProductSubcategory
WHERE Name = 'Wheels');
GO
Ниже приведен результирующий набор для любого запроса:
Name
--------------------------------------------------
LL Mountain Front Wheel
ML Mountain Front Wheel
HL Mountain Front Wheel
LL Road Front Wheel
ML Road Front Wheel
HL Road Front Wheel
Touring Front Wheel
LL Mountain Rear Wheel
ML Mountain Rear Wheel
HL Mountain Rear Wheel
LL Road Rear Wheel
ML Road Rear Wheel
HL Road Rear Wheel
Touring Rear Wheel
(14 row(s) affected)
Однако оператор <> ANY
отличается от NOT IN
:
<> ANY
означает "не равно a или не равно b, или не равно c".NOT IN
означает "не равно a и не равно b, и не равно c".<> ALL
означает то же, что иNOT IN
.
Например, следующий запрос отобразит заказчиков, находящихся на территории, где не работает ни один менеджер по продажам.
USE AdventureWorks2022;
GO
SELECT CustomerID
FROM Sales.Customer
WHERE TerritoryID <> ANY
(SELECT TerritoryID
FROM Sales.SalesPerson);
GO
В результат включены все заказчики, кроме тех, чьим территориям продаж соответствует NULL, так как любая территория, назначенная заказчику, обслуживается менеджером по продажам. Внутренний запрос находит все территории продаж, охваченные лицами продаж, а затем для каждой территории внешний запрос находит клиентов, которые не находятся в одном.
По этой же причине, если использовать NOT IN
в этом запросе, в результат не войдет ни один из заказчиков.
Те же результаты можно получить с помощью оператора <> ALL
, который эквивалентен NOT IN
.
Вложенные запросы с EXISTS
Если вложенный запрос указывается с помощью ключевого слова EXISTS
, он выступает в роли проверки на существование. В предложении WHERE
внешнего запроса проверяется факт существования строк, возвращенных вложенным запросом. Вложенный запрос фактически не создает никаких данных; возвращает значение TRUE
или FALSE
.
Вложенный запрос, представленный с помощью EXISTS, имеет следующий синтаксис: WHERE [NOT] EXISTS (subquery)
Следующий запрос ищет названия всех продуктов, которые находятся в подкатегории Wheels:
USE AdventureWorks2022;
GO
SELECT [Name]
FROM Production.Product
WHERE EXISTS
(SELECT *
FROM Production.ProductSubcategory
WHERE ProductSubcategoryID =
Production.Product.ProductSubcategoryID
AND [Name] = 'Wheels');
GO
Вот результирующий набор.
Name
--------------------------------------------------
LL Mountain Front Wheel
ML Mountain Front Wheel
HL Mountain Front Wheel
LL Road Front Wheel
ML Road Front Wheel
HL Road Front Wheel
Touring Front Wheel
LL Mountain Rear Wheel
ML Mountain Rear Wheel
HL Mountain Rear Wheel
LL Road Rear Wheel
ML Road Rear Wheel
HL Road Rear Wheel
Touring Rear Wheel
(14 row(s) affected)
Чтобы понять результаты данного запроса, необходимо рассмотреть наименование каждого продукта. Это позволит узнать, возвращается ли в результате выполнения данного вложенного запроса хотя бы одна строка. Иными словами, выдается ли в результате проверки на существование значение TRUE
.
Вложенные запросы, представленные с ПОМОЩЬЮ EXISTS, немного отличаются от других вложенных запросов следующими способами:
- Ключевое слово не предшествует имени столбца, константы
EXISTS
или другого выражения. - Список выборки для вложенного запроса, указанного с помощью ключевого слова
EXISTS
, практически всегда состоит из знака "звездочка" (*). Нет никаких причин для перечисления имен столбцов, так как вы просто проверяете, существуют ли строки, соответствующие условиям, указанным в подзапросе.
Ключевое EXISTS
слово важно, так как часто не существует альтернативной формулировки без вложенных запросов. Хотя некоторые запросы, созданные с EXISTS
помощью не могут быть выражены другим способом, многие запросы могут использовать IN
или оператор сравнения, измененный ANY
или ALL
для достижения аналогичных результатов.
Например, предыдущий запрос может быть задан с использованием оператора IN
.
USE AdventureWorks2022;
GO
SELECT [Name]
FROM Production.Product
WHERE ProductSubcategoryID IN
(SELECT ProductSubcategoryID
FROM Production.ProductSubcategory
WHERE [Name] = 'Wheels');
GO
Вложенные запросы с NOT EXISTS
NOT EXISTS
работает так EXISTS
, за исключением WHERE
того, что предложение удовлетворяется, если строки не возвращаются вложенным запросом.
Например, чтобы найти имена продуктов, которые не находятся в подкатегории колес:
USE AdventureWorks2022;
GO
SELECT [Name]
FROM Production.Product
WHERE NOT EXISTS
(SELECT *
FROM Production.ProductSubcategory
WHERE ProductSubcategoryID =
Production.Product.ProductSubcategoryID
AND [Name] = 'Wheels');
GO
Вложенные запросы, используемые вместо выражения
В Transact-SQL вложенный запрос можно заменить в любом месте, где выражение может использоваться в SELECT
, INSERT
UPDATE
и DELETE
инструкциях, за исключением ORDER BY
списка.
Следующий пример показывает, как можно использовать это улучшение. Запрос находит цены на все горные велосипеды, их среднюю цену и разницу между средней ценой и ценой каждого горного велосипеда.
USE AdventureWorks2022;
GO
SELECT [Name], ListPrice,
(SELECT AVG(ListPrice) FROM Production.Product) AS Average,
ListPrice - (SELECT AVG(ListPrice) FROM Production.Product)
AS Difference
FROM Production.Product
WHERE ProductSubcategoryID = 1;
GO
Связанный контент
- IN (Transact-SQL)
- EXISTS (Transact-SQL)
- ALL (Transact-SQL)
- SOME | ANY (Transact-SQL)
- Соединения
- Операторы сравнения (Transact-SQL)
- Руководство по архитектуре обработки запросов
- Рекомендации по хранилищу запросов
- Интеллектуальная обработка запросов в базах данных SQL
- Оценка количества элементов (SQL Server)