Вложенные запросы (SQL Server)
Вложенный запрос — это запрос, который используется внутри инструкции SELECT , INSERT , UPDATE или DELETE или внутри другого вложенного запроса.
В этой статье требуется AdventureWorks2022 пример базы данных, которую можно скачать на домашней странице примеров и проектов сообщества 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. Вложенный запрос в предложении WHERE ссылается на таблицу Purchasing.ProductVendor для ограничения количества обновляемых строк таблицы Product только теми, у которых идентификатор BusinessEntity равен 1540.
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 отображает сообщение об ошибке.
Чтобы использовать подзапрос, начинающийся с немодифицированного оператора сравнения, необходимо достаточно хорошо знать свои данные и природу проблемы, чтобы быть уверенным, что вложенный запрос возвратит точно одно значение.
Например, если предполагается, что каждый менеджер по продажам отвечает только за одну территорию продаж, и нужно найти клиентов, расположенных на территории, за которую отвечает Линда Митчелл, можно написать инструкцию с вложенным запросом, начинающимся с простого оператора сравнения = .
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)
- IS [NOT] DISTINCT FROM (Transact-SQL)
- Соединения
- Операторы сравнения (Transact-SQL)
Основные понятия производительности запросов
- Руководство по архитектуре обработки запросов
- Рекомендации по хранилищу запросов
- Интеллектуальная обработка запросов в базах данных SQL
- Оценка количества элементов (SQL Server)
Базовый курс SQL. Комбинированные запросы. Соединения. UNION
![]()
В обычном SQL-запросе мы используем ключевое слово SELECT один раз, извлекая данные из одной или нескольких таблиц. Но возможности SQL позволяют также скомбинировать несколько таких запросов в один с помощью оператора UNION , чтобы вернуть результат в едином наборе данных. Эти запросы называют комбинированными запросами, или соединениями.
Когда используются комбинированные запросы:
- когда нужно получить данные, структурированные одинаковым способом, но из разных таблиц
- когда нужно сделать несколько запросов к одной и той же таблице и получить данные в едином наборе
Многократные условия WHERE
В основном получается так, что результат комбинированного запроса к одной таблице аналогичен запросу к ней с множественными условиями WHERE . Поэтому любой запрос с несколькими условиями WHERE можно также считать комбинированным запросом.
Создание соединений
Синтаксис соединений очень простой: достаточно составить все запросы SELECT , которые вам требуются и добавить между ними UNION .
Рассмотрим соединение на примере. Предположим нам необходимо получить список иностранных студентов и добавить к нему тех, кто обучается в группе '117F'. Можно составить сложный запрос с условиями WHERE , но в данном случае удобнее использовать соединение.
Для начала рассмотрим отдельно части запроса:
SELECT student_surname FROM Students WHERE student_country != 'РФ';
student_surname ------------------------ Сейдинай Адамченко Легран
SELECT student_surname FROM Students WHERE group_id = (SELECT group_id FROM Groups WHERE group_name = '177F');
Получим второй список:
student_surname ------------------------ Легран Егорова Грошев
Теперь сделаем то же, но с помощью соединения UNION :
SELECT student_surname FROM Students WHERE student_country != 'РФ' UNION SELECT student_surname FROM Students WHERE group_id = (SELECT group_id FROM Groups WHERE group_name = '117F');
Получим необходимый нам результат в одном наборе данных:
student_surname ------------------------ Сейдинай Легран Егорова Грошев Адамченко
И для сравнения рассмотрим тот же запрос с множественным условием WHERE :
SELECT student_surname FROM Students WHERE student_country != 'РФ' OR group_id = (SELECT group_id FROM Groups WHERE group_name = '117F');
Запрос с помощью соединения может показаться громоздким по сравнению с множественным WHERE , но зато он наглядно демонстрирует смысл того, что необходимо получить. С точки зрения поддержки кода часто это оправданный шаг, особенно если условия более сложные.
Сколько запросов можно соединять?
В стандарте SQL нет ограничений на число запросов SELECT , соединяемых с помощью UNION . Однако всегда лучше уточнять такие нюансы в документации конкретной СУБД.
Производительность соединений
В большинстве СУБД есть встроенный оптимизатор запросов, который комбинирует все SELECT в соединении. Это означает, что разницы между несколькими условиями в одном запросе и несколькими целыми запросами нет. Но это только теоретически. На практике всегда лучше тестировать оба варианта, чтобы опытным путём убедиться в том, что выбран оптимальный метод.
Правила применения UNION
Определим некоторые правила, которые следует соблюдать, используя UNION :
- Оператор UNION должен разделять все запросы SELECT , сколько бы их не было
- Соединения можно использовать только для получения одних и тех же полей таблиц. Т.е. имена столбцов и применяемые итоговые функции должны быть одинаковыми во всех запросах. В некоторых СУБД значение имеет даже их порядок.
- Типы данных запрашиваемых столбцов должны быть по крайней мере совместимы. Т.е. тип данных должен быть либо тот же, либо такой, который СУБД сможет преобразовать автоматически (например, различные форматы числа или даты)
Включить повторяющиеся строки
Рассмотрим внимательнее результаты отдельных запросов из предыдущего примера. Первый запрос возвращает 3 строки, второй - тоже 3. Но при их соединении, мы получаем 5 строк, а не 6. Так происходит потому, что UNION по умолчанию исключает из выборки одинаковые результаты (так же сработал бы и запрос с несколькими условиями WHERE ). В нашем случае повторяющаяся строка - фамилия студента 'Легран'.
Если нам по каким-то причинам нужно получить все строки таблиц без исключения, применим оператор UNION ALL :
SELECT student_surname FROM Students WHERE student_country != 'РФ' UNION ALL SELECT student_surname FROM Students WHERE group_id = (SELECT group_id FROM Groups WHERE group_name = '117F');
В результате 6 строк:
student_surname ----------------------- Сейдинай Адамченко Легран Егорова Легран Грошев
UNION ALL или WHERE
В отличие от предыдущих примеров, WHERE никак не сможет заменить UNION ALL . Так что, если вы хотите получить полный список строк, вместе с дубликатами, используйте соединения.
Сортировка в соединениях
Т.к. результаты всех запросов соединения имеют одинаковую структуру, их с лёгкостью можно отсортировать. Оператор ORDER BY при этом должен быть один для всех запросов и находиться после последнего оператора SELECT .
Если вы примените сортировку несколько раз, СУБД выдаст ошибку.
Давайте дополним наше соединение:
SELECT student_surname FROM Students WHERE student_country != 'РФ' UNION SELECT student_surname FROM Students WHERE group_id = (SELECT group_id FROM Groups WHERE group_name = '117F') ORDER BY student_surname;
Фамилии будут отсортированы по алфавиту:
student_surname ----------------------- Адамченко Грошев Егорова Легран Сейдинай
Несмотря на то, что фактически ORDER BY принадлежит только последнему запросу SELECT , СУБД применит его ко всей полученной выборке.
Какие ещё бывают комбинированные запросы?
В некоторых СУБД есть и другие типы комбинированных соединений. Они создаются с помощью операторов EXCEPT , или MINUS , (позволяет извлечь строки, которые есть в первой таблице, но отсутствуют во второй) и INTERSECT (позволяет извлечь только строки, имеющиеся в обоих таблицах). Но на практике для этих целей чаще используют объединения.
Соединения разных таблиц
На этом уроке мы рассмотрели соединения запросов к одной и той же таблице, но это не предел. Также UNION можно использовать для разных таблиц - необходимо только следить за совместимостью типов данных. В таких случаях, если имена столбцов различаются, для их соединения придётся использовать псевдонимы.
Итак, сегодня мы узнали, как можно упростить сложные запросы с множественными условиями WHERE . И как с помощью оператора UNION получить результаты запросов из одной или нескольких таблиц в одной выборке.
Key Words for FKN + antitotal forum (CS VSU):
- sql базовый курс
- sql с чего начать
- как начать учить программирование
- как стать программистом
- sql union
- sql комбинированные запросы
- sql два запроса в одном
- sql соединения
- производительность соединений
- множественный условия WHERE
- sql подзапрос
Как выполнит одним запросом несколько действий MYSQL?

Вот ты и подрос, наконец, чтобы узнать главное правило программирования:
Преждевременная оптимизация - корень всех зол.
Никакой нагрузки тут нет, снижать нечего. Поэтому расслабьтся, забудь эти фантазии и займись чем-нибудь полезным.
Например, открой для себя подготовленные выражения и не никогда не пихай переменные прямо в запрос. В противном случае будет очень, очень больно когда сайт будет выложен в открытый доступ.
Ответ написан более трёх лет назад
Нравится 8 2 комментария

сделай русскоязычную версию, это немного странно - ориентироваться на забугорную публику и игнорировать русскоязычную

Lander скорее всего не будет работать. У него 100% mysqli. У таких оптимизаторов всегда mysqli
Ответы на вопрос 3

ThunderCat @ThunderCat Куратор тега PHP
Как выполнит одним запросом несколько действий MYSQL?
Никак, это разные запросы с разными таблицами и разными задачами.
для снижения нагрузки на базу данных.
Неужели у вас уже более 1000 запросов в секунду и необходима срочная оптимизация?
$db->query("UPDATE score SET score = score + $klik push = push + 1 WHERE vk_id = $ids LIMIT 1"); $db->query("UPDATE score SET score = score + $klik push = push + 1 WHERE vk_id = $ids LIMIT 1");
во первых - не хватает запятых между присваиваниями, во вторых SET score = score + $klik*2, push = push + 2 вот вам минус 1 запрос. В третьих - вроде используете pdo, а переменные вставляете без prepared statements.
Все это можно:
1) обернуть в транзакцию - профит от этого - целостность выполнения операций(на скорости особо не отразится).
2) Запустить одним скриптом, разделив запросы семиколоном ";" (профита практически никакого)
Вывод нескольких SELECT-ов в одну таблицу
можно воспользоваться объединением запросов (union):
select "запрос 1", count(*) from . where . union select "запрос 2", count(*) from . where . union .
Отслеживать
ответ дан 29 ноя 2016 в 6:39
aleksandr barakin aleksandr barakin
68k 221 221 золотой знак 79 79 серебряных знаков 221 221 бронзовый знак
Предложу такой вариант: Написать процедуру, в которой: 1) Будет создаваться таблица, определяющая Вашу результирующую таблицу 2) Делать выборки исходной таблицы 3) Проходится по каждой выборки и вставлять данные в таблицу, созданную в 1 пункте
Отслеживать
ответ дан 29 ноя 2016 в 0:44
2,015 4 4 золотых знака 27 27 серебряных знаков 54 54 бронзовых знака
Вот как решил проблему
SELECT "request_1" AS "Запрос 1", "request_2" AS "Запрос 2", "request_3" AS "Запрос 3", "request_4" AS "Запрос 4" FROM ( -- Исполняем запрос 1 SELECT count("customer"."id") FROM "customer" WHERE "customer"."unsubscribed_types" = 'daily_weekdays' OR "customer"."unsubscribed_types" = 'daily_weekends' OR "customer"."unsubscribed_types" = 'weekly_free' OR "customer"."unsubscribed_types" = 'weekly_best_weekly' OR "customer"."unsubscribed_types" = 'special_partner' OR "customer"."unsubscribed_types" = 'bonus_activation' ) AS "request_1", ( -- Исполняем запрос 2 SELECT count("customer"."id") FROM "customer" WHERE "customer"."unsubscribed_types" ISNULL OR "customer"."unsubscribed_types" = '<>' ) AS "request_2", ( -- Исполняем запрос 3 SELECT count("customer"."id") FROM "customer" WHERE "customer"."unsubscribed_types" = 'daily_weekdays' AND "customer"."unsubscribed_types" = 'daily_weekends' AND "customer"."unsubscribed_types" = 'weekly_free' AND "customer"."unsubscribed_types" = 'weekly_best_weekly' AND "customer"."unsubscribed_types" = 'special_partner' AND "customer"."unsubscribed_types" = 'bonus_activation' ) AS "request_3", ( -- Исполняем запрос 4 SELECT count("customer"."id") FROM "customer" WHERE "unsubscribed_types" = 'daily_weekdays' ) AS "request_4", ;