SQL Server サブクエリ
1. サブクエリの基礎知識
サブクエリは、SELECT、INSERT、UPDATE、DELETE ステートメント内、または別のサブクエリ内にネストされたクエリです。
サブクエリは、式が許可されている場所ならどこでも使用できます。
例:
USE AdventureWorks2016;
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 では、通常、サブクエリを含むステートメントと、サブクエリを含まない意味的に同等のバージョンとの間にパフォーマンスの違いはありません。ただし、存在を確認する必要がある場合は、結合した方がパフォーマンスが向上します。それ以外の場合は、外側のクエリの結果ごとにネストされたクエリを処理して、重複を確実に排除する必要があります。この場合、join メソッドの方が良い結果が得られます。
次の例は、同じ結果セットと実行計画を返すサブクエリと結合を示しています。
USE AdventureWorks2016;
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 ステートメント内にネストされたサブクエリには、次のコンポーネントがあります。
- 通常の選択リスト コンポーネントを含む通常のクエリ。
- 1 つ以上のテーブル名またはビュー名を含む通常の句。
- オプション: WHERE、GROUP BY、HAVING。
サブクエリの SELECT クエリは、常に括弧で囲みます。or 句を含めることはできず、TOP 句も指定されている場合にのみ句を含めることができます。
サブクエリは、外側の WHERE 、 HAVING 、 SELECT 、 INSERT 、 UPDATE 、 DELETE 、またはステートメントの句内、または別のサブクエリ内にネストできます。最大 32 レベルのネストが可能ですが、制限は、使用可能なメモリと、クエリ内の他の式の複雑さによって異なります。1 つのクエリでは、最大 32 レベルのネストをサポートできない場合があります。サブクエリが単一の値を返す場合、サブクエリは式を使用できる場所ならどこにでも表示できます。
テーブルがサブクエリにのみ表示され、外側のクエリには表示されない場合、そのテーブルの列を出力 (外側のクエリの選択リスト) に含めることはできません。
サブクエリを含むステートメントは、通常、次のいずれかの形式を取ります。
- WHERE 式 [NOT] IN (サブクエリ)
- WHERE 式の比較演算子 [ANY | ALL] (サブクエリ)
- WHERE [NOT] EXISTS (サブクエリ)
一部の Transact-SQL ステートメントでは、サブクエリを独立したクエリとして評価できます。概念的には、サブクエリの結果は外側のクエリに代入されます (ただし、これは、SQL Server が実際にサブクエリを含む Transact-SQL ステートメントを処理する方法とは限りません)。
サブクエリには、次の 3 つの基本的なタイプがあります。
- 導入されたリスト、または比較演算子 INANY または ALL によって変更されたリストで動作します。
- 変更されていない比較演算子で導入され、単一の値を返す必要があります。
- EXISTS を使用した存在テストを導入するかどうか。
2. サブクエリのルール
- 比較演算子を使用して導入されたサブクエリの選択リストには、式または列名を 1 つだけ含めることができます。
- 外部クエリの句に列名が含まれる場合、その句は、サブクエリの選択リスト内の列との結合互換性がなければなりません。
- ntext、text、および image データ型は、サブクエリの選択リストでは使用できません。
- サブクエリは単一の値を返す必要があるため、変更されていない比較演算子 (キーワード or が続かない) によって導入されたサブクエリには、and 句を含めることはできません。
- 含まれるサブクエリではキーワードを使用できません。
- and句は指定できません。
- ORDER BY は、TOP も指定されている場合にのみ指定できます。
- サブクエリで作成されたビューは更新できません。
3. サブクエリの列名を制限する
例: 外部クエリ句の BusinessEntityID 列は、外部クエリ句 (Sales.Store) のテーブル名によって暗黙的に修飾されます。サブクエリの選択リスト内の CustomerID への参照は、サブクエリ句 (つまり、Sales.Customer テーブル) によって修飾されます。
USE AdventureWorks2016;
GO
SELECT [Name]
FROM Sales.Store
WHERE BusinessEntityID NOT IN
(SELECT CustomerID
FROM Sales.Customer
WHERE TerritoryID = 5);
GO
一般に、ステートメント内の列名は、同じレベルの句で参照されるテーブルによって暗黙的に修飾されます。サブクエリ句で参照されているテーブルに列が存在しない場合、外側のクエリ句で参照されているテーブルによって列が暗黙的に修飾されます。
これらの暗黙の仮定を指定するクエリは次のようになります。
USE AdventureWorks2016;
GO
SELECT [Name]
FROM Sales.Store
WHERE Sales.Store.BusinessEntityID NOT IN
(SELECT Sales.Customer.CustomerID
FROM Sales.Customer
WHERE TerritoryID = 5);
GO
テーブル名を明示的に記述することは決して間違いではありません。また、テーブル名に関する暗黙の仮定をオーバーライドするために、明示的な修飾を常に使用できます。
4. サブクエリの多層ネスト
サブクエリ自体に 1 つ以上のサブクエリを含めることができます。サブクエリは、ステートメント内にいくつでもネストできます。
例: クエリは、販売員でもある従業員の名前を検索します。
USE AdventureWorks2016;
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)
最も内側のクエリは、営業担当者 ID を返します。次に高いレベルのクエリは、これらの営業担当者 ID を使用して評価し、従業員の連絡先 ID 番号を返します。最後に、外部クエリは連絡先 ID を使用して従業員の名前を検索します。
このクエリは、結合として表現することもできます。
USE AdventureWorks2016;
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
5. 相関サブクエリ
多くのクエリは、サブクエリを 1 回実行し、結果の値を外側のクエリの句に代入することで評価できます。相関サブクエリ (繰り返しサブクエリとも呼ばれます) を含むクエリでは、サブクエリはその値を外部クエリに依存します。これは、外側のクエリが選択する可能性のある行ごとに、サブクエリが繰り返し実行されることを意味します。
例:
USE AdventureWorks2016;
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 が最初に行を調べる場合、変数 Employee.BusinessEntityID は値 285 を取り、SQL Server は内部クエリでこれを置き換えます。これらの 2 つのクエリの例は、相関サブクエリを使用した前の例の分解を表しています。
USE AdventureWorks2016;
GO
SELECT Bonus
FROM Sales.SalesPerson
WHERE BusinessEntityID = 285;
GO
結果は 0.00 (営業担当者ではなかったため、ボーナスを受け取っていません) であるため、外側のクエリは次のように評価されます。
USE AdventureWorks2016;
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 であるため、相関サブクエリを使用した前の例のクエリの結果には、 の行は含まれません。右側の行についても同じプロセスを実行します。結果が含まれているため、この行が結果に含まれていることがわかります。
概要: 相関サブクエリでは、外部クエリのテーブル値関数への引数としてテーブル内の列を参照することにより、句にテーブル値関数を含めることもできます。この場合、外側のクエリの行ごとに、テーブル値関数がサブクエリに対して評価されます。
6. サブクエリの種類
- 別名で。
- INまたはNOT IN付き。
- UPDATE、DELETE、および INSERT ステートメント内。
- 比較演算子を使用します。
- ANY、SOME、または ALL を使用します。
- 跟は [NOT] DISTINCT FROM。
- EXISTS または NOT EXISTS で。
- 式の代わりに。
要約する
サブクエリで参照される列がサブクエリには存在せず、外部クエリの句によって参照されるテーブルに存在する場合、クエリはエラーなしで実行されます。SQL Server は、外部クエリのテーブル名を使用して、サブクエリの列を暗黙的に修飾します。