ビューの解決

SQL Server クエリ プロセッサでは、インデックス付きビューとインデックスなしのビューの処理方法が異なります。

  • インデックス付きビューの行は、テーブルと同じ形式でデータベースに格納されます。クエリ オプティマイザーでクエリ プランにインデックス付きビューを使用することが決定されると、インデックス付きビューはベース テーブルと同じ方法で処理されます。

  • インデックスなしのビューでは、ビューの行ではなく、定義のみが格納されます。クエリ オプティマイザーにより、ロジックがビューの定義から、インデックスなしのビューを参照する SQL ステートメントに対して作成された実行プランに組み込まれます。

SQL Server のクエリ オプティマイザーでは、いつインデックス付きビューを使用するかを決定するときに、テーブルのインデックスをいつ使用するかを決定するのとよく似たロジックが使用されます。インデックス付きビューに含まれるデータが SQL ステートメントの一部またはすべてを対象としており、クエリ オプティマイザーによってビュー上のインデックスが低コストのアクセス パスであると判断されると、クエリ内でビューが名前で参照されるかどうかにかかわらず、クエリ オプティマイザーではインデックスが選択されます。詳細については、「ビューのインデックスの解決」を参照してください。

SQL ステートメントからインデックスなしのビューが参照されるときは、パーサーとクエリ オプティマイザーで SQL ステートメントとビューのソースが両方分析され、それらが 1 つの実行プランに解決されます。SQL ステートメントの実行プランとビューの実行プランに別々に解決されるわけではありません。

たとえば、次のビューがあるとします。

USE AdventureWorks2008R2;
GO
CREATE VIEW EmployeeName AS
SELECT h.BusinessEntityID, p.LastName, p.FirstName
FROM HumanResources.Employee AS h 
JOIN Person.Person AS p
ON h.BusinessEntityID = p.BusinessEntityID;
GO

次の 2 つの SQL ステートメントはどちらも、このビューに基づいてベース テーブルに同じ操作を行い、同じ結果を生成します。

/* SELECT referencing the EmployeeName view. */
SELECT LastName AS EmployeeLastName, SalesOrderID, OrderDate
FROM AdventureWorks2008R2.Sales.SalesOrderHeader AS soh
JOIN AdventureWorks2008R2.dbo.EmployeeName AS EmpN
ON (soh.SalesPersonID = EmpN.BusinessEntityID)
WHERE OrderDate > '20020531';

/* SELECT referencing the Person and Employee tables directly. */
SELECT LastName AS EmployeeLastName, SalesOrderID, OrderDate
FROM AdventureWorks2008R2.HumanResources.Employee AS e 
JOIN AdventureWorks2008R2.Sales.SalesOrderHeader AS soh
ON soh.SalesPersonID = e.BusinessEntityID
JOIN AdventureWorks2008R2.Person.Person AS p
ON e.BusinessEntityID =p.BusinessEntityID
WHERE OrderDate > '20020531';

SQL Server Management Studio のプラン表示機能では、リレーショナル エンジンがこの 2 つの SELECT ステートメントのどちらに対しても同じ実行プランを構築することが示されます。

ビューでのヒントの使用

クエリのビューに設定されるヒントは、ベース テーブルにアクセスするためにビューを展開するときに検出される他のヒントと競合することがあります。このような競合が発生すると、クエリはエラーを返します。たとえば、定義にテーブル ヒントが含まれている、次のビューについて考えてみます。

USE AdventureWorks2008R2;
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 の順になります。