ビューの解決
SQL Server クエリ プロセッサでは、インデックス付きビューとインデックスなしのビューの処理方法が異なります。
インデックス付きビューの行は、テーブルと同じ形式でデータベースに格納されます。クエリ オプティマイザでクエリ プランにインデックス付きビューを使用することが決定されると、インデックス付きビューはベース テーブルと同じ方法で処理されます。
インデックスなしのビューでは、ビューの行ではなく、定義のみが格納されます。クエリ オプティマイザにより、ロジックがビューの定義から、インデックスなしのビューを参照する SQL ステートメントに対して作成された実行プランに組み込まれます。
SQL Server のクエリ オプティマイザでは、いつインデックス付きビューを使用するかを決定するときに、テーブルのインデックスをいつ使用するかを決定するのとよく似たロジックが使用されます。インデックス付きビューに含まれるデータが SQL ステートメントの一部またはすべてを対象としており、クエリ オプティマイザによってビュー上のインデックスが低コストのアクセス パスであると判断されると、クエリ内でビューが名前で参照されるかどうかにかかわらず、クエリ オプティマイザではインデックスが選択されます。詳細については、「ビューのインデックスの解決」を参照してください。
SQL ステートメントからインデックスなしのビューが参照されるときは、パーサーとクエリ オプティマイザで SQL ステートメントとビューのソースが両方分析され、それらが 1 つの実行プランに解決されます。SQL ステートメントの実行プランとビューの実行プランに別々に解決されるわけではありません。
たとえば、次のビューがあるとします。
USE AdventureWorks;
GO
CREATE VIEW EmployeeName AS
SELECT h.EmployeeID, c.LastName, c.FirstName
FROM HumanResources.Employee AS h
JOIN Person.Contact AS c
ON h.ContactID = c.ContactID;
GO
次の 2 つの SQL ステートメントはどちらも、このビューに基づいてベース テーブルに同じ操作を行い、同じ結果を生成します。
/* SELECT referencing the EmployeeName view. */
SELECT LastName AS EmployeeLastName, SalesOrderID, OrderDate
FROM AdventureWorks.Sales.SalesOrderHeader AS soh
JOIN AdventureWorks.dbo.EmployeeName AS EmpN
ON (soh.ContactID = EmpN.EmployeeID)
WHERE OrderDate > '20020531';
/* SELECT referencing the Contact and Employee tables directly. */
SELECT LastName AS EmployeeLastName, SalesOrderID, OrderDate
FROM AdventureWorks.HumanResources.Employee AS e
JOIN AdventureWorks.Sales.SalesOrderHeader AS soh
ON soh.SalesPersonID = e.EmployeeID
JOIN AdventureWorks.Person.Contact AS c
ON e.ContactID =c.ContactID
WHERE OrderDate > '20020531';
SQL Server Management Studio のプラン表示機能では、リレーショナル エンジンがこの 2 つの SELECT ステートメントのどちらに対しても同じ実行プランを構築することが示されます。
ビューでのヒントの使用
クエリのビューに設定されるヒントは、ベース テーブルにアクセスするためにビューを展開するときに検出される他のヒントと競合することがあります。このような競合が発生すると、クエリはエラーを返します。たとえば、定義にテーブル ヒントが含まれている、次のビューについて考えてみます。
USE AdventureWorks;
GO
CREATE VIEW Person.AddrState WITH SCHEMABINDING AS
SELECT a.AddressID, a.AddressLine1,
s.StateProvinceCode, s.CountryRegionCode
FROM Person.Address a WITH (NOLOCK), Person.StateProvince s
WHERE a.StateProvinceID = s.StateProvinceID;
ここで次のクエリを入力したとします。
SELECT AddressID, AddressLine1, StateProvinceCode, CountryRegionCode
FROM Person.AddrState WITH (SERIALIZABLE)
WHERE StateProvinceCode = 'WA';
このクエリは失敗します。ビューが展開されるときに、クエリ内の Person.AddrState ビューに適用される SERIALIZABLE ヒントが、ビューの Person.Address テーブルと Person.StateProvince テーブルの両方に反映されたためです。ただし、ビューを展開することで、Person.Address の NOLOCK ヒントも公開されます。その結果、SERIALIZABLE ヒントと NOLOCK ヒントが競合するので、クエリ結果は正しくありません。
PAGLOCK、NOLOCK、ROWLOCK、TABLOCK、または TABLOCKX の各テーブルのヒントは互いに競合します。HOLDLOCK、NOLOCK、READCOMMITTED、REPEATABLEREAD、SERIALIZABLE の各テーブルのヒントも互いに競合します。
ヒントは、入れ子になったビュー全体に反映されます。たとえば、クエリでビュー v1 に HOLDLOCK ヒントが適用されるとします。v1 を展開すると、ビュー v2 がその定義の一部であることがわかります。v2 の定義では、ベース テーブルの 1 つに NOLOCK ヒントが含まれています。しかし、このテーブルはビュー v1 のクエリから HOLDLOCK ヒントも継承します。その結果、NOLOCK ヒントと HOLDLOCK ヒントが競合するので、クエリが失敗します。
ビューが含まれるクエリで FORCE ORDER ヒントを使用すると、ビュー内のテーブルの結合順序は、順序付けられた構造内のビューの位置によって決まります。たとえば、次のクエリは 3 つのテーブルとビューから選択を行います。
SELECT * FROM Table1, Table2, View1, Table3
WHERE Table1.Col1 = Table2.Col1
AND Table2.Col1 = View1.Col1
AND View1.Col2 = Table3.Col2;
OPTION (FORCE ORDER)
さらに、次に示すように View1 が定義されています。
CREATE VIEW View1 AS
SELECT Colx, Coly FROM TableA, TableB
WHERE TableA.ColZ = TableB.Colz;
クエリ プランでの結合順序は、Table1、Table2、TableA、TableB、Table3 の順になります。