Вложенные запросы (SQL Server)

Относится к:SQL ServerБаза данных SQL AzureУправляемый экземпляр SQL AzureAzure Synapse AnalyticsСистема платформы аналитики (PDW)База данных SQL в Microsoft Fabric

Вложенный запрос — это запрос, который используется внутри инструкции SELECT, INSERT, UPDATE или DELETE или внутри другого вложенного запроса.

Примеры кода в этой статье используют базу данных образца AdventureWorks2025 или AdventureWorksDW2025, которую можно скачать с домашней страницы образцов и проектов сообщества 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". Однако в некоторых случаях, когда проверяется существование, соединения показывают лучшую производительность. В противном случае вложенный запрос должен выполняться для каждого результата внешнего запроса, чтобы обеспечить устранение дубликатов. В таких случаях подход с использованием JOIN даст лучшие результаты.

В следующем примере показаны как вложенный запрос 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.

Вложенный запрос может быть включен в предложение 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, text и image нельзя использовать в списке SELECT подзапросов.
  • Поскольку они должны возвращать одно значение, вложенные запросы, вводимые немодифицированным оператором сравнения (то есть оператором, после которого не следует ключевое слово 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

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

Important

Если столбец ссылается на вложенный запрос, который не существует в таблице, на которую ссылается предложение вложенного запроса 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)

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

Этот запрос также можно выразить с помощью операции JOIN:

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

Поскольку это условие ложно, строка для Syed Abbas не включена в результаты предыдущего примера запроса с коррелированным подзапросом. Повторите ту же процедуру для строки Pamela Ansman-Wolfe. Вы видите, что эта строка включена в результаты, так как WHERE 5000 IN (5000) включает результаты.

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

Типы вложенных запросов

Подзапросы можно указывать во многих местах:

Подзапросы с псевдонимами таблиц

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

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

Единственная разница в использовании соединения и вложенного запроса для этой и аналогичных задач заключается в том, что объединение позволяет включить в результат столбцы, содержащиеся в нескольких таблицах. Например, если вы хотите включить в результат название подкатегории продукта, необходимо использовать вариант с JOIN.

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

Соединение всегда можно выразить в виде вложенного запроса. Подзапрос часто, но не всегда, может быть представлен в виде соединения. Это связано с симметричными соединениями: вы можете объединить таблицу AB в любом порядке и получить один и тот же ответ. То же самое не верно, если используется вложенный запрос.

Вложенные запросы с 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

Подзапросы могут быть вложены в операторы манипулирования данными (DML) UPDATE, DELETE, INSERT и SELECT.

В следующем примере значение в ListPrice столбце Production.Product в таблице удвоится. Подзапрос в предложении WHERE ссылается на таблицу Purchasing.ProductVendor, чтобы ограничить строки, обновляемые в таблице Product, только теми, которые предоставляются через BusinessEntity1540.

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 возвращает одно значение. Например, следующий запрос находит продукты по цене выше, чем самый низкий ценовый продукт, который находится в ProductSubcategoryID14.

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, UPDATE, INSERT и 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