これは、名前付きテーブル式に関するシリーズの第9部です。パート1では、派生テーブル、共通テーブル式(CTE)、ビュー、およびインラインテーブル値関数(iTVF)を含む名前付きテーブル式の背景を説明しました。パート2、パート3、パート4では、派生テーブルに焦点を当てました。パート5、パート6、パート7、パート8では、CTEに焦点を当てました。説明したように、派生テーブルとCTEは、ステートメントスコープの名前付きテーブル式です。それらを定義するステートメントが終了すると、それらはなくなります。
これで、再利用可能な名前付きテーブル式の説明に進む準備ができました。つまり、データベース内のオブジェクトとして作成され、削除されない限り永続的にそこにとどまるものです。そのため、適切な権限を持つすべての人がアクセスおよび再利用できます。ビューとiTVFはこのカテゴリに分類されます。 2つの違いは、主に前者は入力パラメータをサポートしておらず、後者はサポートしていることです。
この記事では、ビューのカバレッジを開始します。以前と同じように、最初に論理的または概念的な側面に焦点を当て、後で最適化の側面に進みます。ビューに関する最初の記事では、ビューとは何かに焦点を当て、正しい用語を使用して、ビューの設計上の考慮事項を前述の派生テーブルおよびCTEの設計上の考慮事項と比較したいと思います。
私の例では、TSQLV5というサンプルデータベースを使用します。これを作成してデータを設定するスクリプトはここにあり、そのER図はここにあります。
ビューとは何ですか?
関係理論について議論するときはいつものように、私たちSQLの実践者は、私たちが使用している用語が間違っているとよく言われます。したがって、この精神で、すぐに、テーブルとビューという用語を使用するときに言うことから始めます。 、 それは間違っています。これはクリスデイトから学びました。
テーブルはSQLのリレーションに対応するものであることを思い出してください(値と変数に関する説明を少し単純化しすぎています)。テーブルは、データベース内のオブジェクトとして定義されたベーステーブルの場合もあれば、式(より具体的にはテーブル式)によって返されるテーブルの場合もあります。これは、リレーションがリレーショナル式から返されるリレーションである可能性があるという事実に似ています。テーブル式はクエリである可能性があります。
さて、ビューとは何ですか?これは、CTEが名前付きテーブル式であるのと同じように、名前付きテーブル式です。私が言ったように、ビューはデータベース内のオブジェクトとして作成され、適切な権限を持つユーザーがアクセスできる、再利用可能な名前付きテーブル式です。つまり、ビューはテーブルです。これはベーステーブルではありませんが、それでもテーブルです。したがって、「長方形と正方形」または「ウイスキーとラガヴーリン」と言うのと同じように(ラガヴーリンが多すぎない限り)、「テーブルとビュー」を使用するのは不適切です。
構文
CREATEVIEWステートメントのT-SQL構文は次のとおりです。
CREATE [OR ALTER]VIEW[<スキーマ名>。 ]<テーブル名>[(<ターゲット列>)][WITH<スキーマバインドを含む属性の表示>]
AS
<テーブル式>
[WITH CHECK OPTION]
[; ]
CREATE VIEWステートメントは、バッチ内の最初で唯一のステートメントである必要があります。
CREATEORALTERの部分はSQLServer2016 SP1で導入されたため、以前のバージョンを使用している場合は、オブジェクトが既に存在するかどうかに応じて、CREATEVIEWステートメントとALTERVIEWステートメントを別々に操作する必要があります。ご存知かもしれませんが、既存のオブジェクトを変更しても、割り当てられた権限は保持されます。これが、通常、オブジェクトを削除して再作成するのではなく、既存のオブジェクトを変更することが賢明である理由の1つです。一部の人々を驚かせるのは、ビューを変更しても既存のビュー属性が保持されないことです。それらを保持したい場合は、それらを再指定する必要があります。
米国の顧客を表す単純なビュー定義の例を次に示します。
USE TSQLV5; GO CREATE OR ALTER VIEW Sales.USACustomers AS SELECT custid, companyname FROM Sales.Customers WHERE country = N'USA'; GO
そして、ビューをクエリするステートメントは次のとおりです。
SELECT custid, companyname FROM Sales.USACustomers;
ビューを作成するステートメントとそれを照会するステートメントの間に、派生テーブルまたはCTEに対するステートメントに含まれるまったく同じ3つの要素があります。
- 内部テーブル式(ビューの内部クエリ)
- 割り当てられたテーブル名(ビュー名)
- ビューに対する外部クエリを含むステートメント
鋭い目を持っている人は、実際には2つのテーブル式がここに含まれていることに気付くでしょう。内側のもの(ビューの内側のクエリ)と外側のもの(ビューに対するステートメントのクエリ)があります。ビューに対するクエリを含むステートメントでは、クエリ自体がテーブル式であり、ターミネータを追加すると、ステートメントになります。これは厄介に聞こえるかもしれませんが、これを取得して正しい名前で呼び出すと、知識に反映されます。そして、あなたが知っていることを知っているとき、それは素晴らしいことではありませんか?
また、シリーズの前半で説明した派生テーブルおよびCTEのテーブル式のすべての要件は、ビューの基になるテーブル式に適用されます。注意点として、要件は次のとおりです。
- テーブル式のすべての列に名前を付ける必要があります
- テーブル式の列名はすべて一意である必要があります
- テーブル式の行には順序がありません
これらの要件の背後にあるものについての理解を新たにする必要がある場合は、シリーズのパート2の「テーブル式はテーブルです」のセクションを参照してください。特に「注文なし」の部分を理解していることを確認してください。簡単に言うと、テーブル式はテーブルであるため、順序はありません。そのため、この句がTOPまたはOFFSET-FETCHフィルタをサポートするために存在しない限り、ORDERBY句を使用したクエリに基づいてビューを作成することはできません。また、内部クエリにORDER BY句を含めることができるこの例外があっても、ビューに対する外部クエリに独自のORDER BY句がない場合、クエリが返されるという保証はありません。行は特定の順序であり、観察された動作を気にする必要はありません。これを理解することは非常に重要です!
ネストと複数の参照
派生テーブルとCTEの設計上の考慮事項について説明するときに、ネストと複数の参照の両方の観点から2つを比較しました。それでは、これらの部門でビューがどのように機能するかを見てみましょう。ネスティングから始めましょう。この目的のために、70を超える顧客が派生テーブル、CTE、およびビューを使用して注文した年を返すコードを比較します。シリーズの前半で、派生テーブルとCTEを含むコードをすでに見ました。派生テーブルを使用してタスクを処理するコードは次のとおりです。
SELECT orderyear, numcusts FROM ( SELECT orderyear, COUNT(DISTINCT custid) AS numcusts FROM ( SELECT YEAR(orderdate) AS orderyear, custid FROM Sales.Orders ) AS D1 GROUP BY orderyear ) AS D2 WHERE numcusts > 70;
ここで派生テーブルに見られる主な欠点は、派生テーブル定義をネストするという事実であり、これにより、そのようなコードの理解、保守、およびトラブルシューティングが複雑になる可能性があることを指摘しました。
CTEを使用して同じタスクを処理するコードは次のとおりです。
WITH C1 AS ( SELECT YEAR(orderdate) AS orderyear, custid FROM Sales.Orders ), C2 AS ( SELECT orderyear, COUNT(DISTINCT custid) AS numcusts FROM C1 GROUP BY orderyear ) SELECT orderyear, numcusts FROM C2 WHERE numcusts > 70;
ネストがないため、これははるかに明確なコードのように感じることを指摘しました。ソリューションの各ステップは、ソリューションのロジックが上から下に明確に流れるように、独自のユニットで最初から最後まで個別に確認できます。したがって、CTEオプションは、この点で派生テーブルよりも改善されていると思います。
今度はビューに。ビューの主な利点の1つは、再利用性であることを忘れないでください。アクセス許可を制御することもできます。関連するユニットの開発は、最初から最後まで一度に1つのユニットに注意を向けることができるという意味で、CTEに少し似ています。さらに、ソリューション内のユニットごとに個別のビューを作成するか、ステートメントスコープの名前付きテーブル式を含むクエリに基づいて1つのビューのみを作成するかを柔軟に決定できます。
各ユニットを再利用可能にする必要がある場合は、前者を使用します。このような場合に使用するコードは次のとおりで、3つのビューを作成します。
-- Sales.OrderYears CREATE OR ALTER VIEW Sales.OrderYears AS SELECT YEAR(orderdate) AS orderyear, custid FROM Sales.Orders; GO -- Sales.YearlyCustCounts CREATE OR ALTER VIEW Sales.YearlyCustCounts AS SELECT orderyear, COUNT(DISTINCT custid) AS numcusts FROM Sales.OrderYears GROUP BY orderyear; GO -- Sales.YearlyCustCountsMin70 CREATE OR ALTER VIEW Sales.YearlyCustCountsAbove70 AS SELECT orderyear, numcusts FROM Sales.YearlyCustCounts WHERE numcusts > 70; GO
各ビューを個別にクエリできますが、元のタスクが何を求めていたかを返すために使用するコードは次のとおりです。
SELECT orderyear, numcusts FROM Sales.YearlyCustCountsAbove70;
最も外側の部分(元のタスクで必要だったもの)にのみ再利用性の要件がある場合、3つの異なるビューを開発する必要はありません。 CTEまたは派生テーブルを含むクエリに基づいて1つのビューを作成できます。 CTEを含むクエリでこれを行う方法は次のとおりです。
CREATE OR ALTER VIEW Sales.YearlyCustCountsAbove70 AS WITH C1 AS ( SELECT YEAR(orderdate) AS orderyear, custid FROM Sales.Orders ), C2 AS ( SELECT orderyear, COUNT(DISTINCT custid) AS numcusts FROM C1 GROUP BY orderyear ) SELECT orderyear, numcusts FROM C2 WHERE numcusts > 70; GO
ちなみに、それが明らかでない場合は、ビューの内部クエリが基づいているCTEは再帰的である可能性があります。
外部クエリから同じテーブル式への複数の参照が必要な場合に進みましょう。この例のタスクは、1年あたりの年間注文数を計算し、各年の数を前年と比較することです。これを実現する最も簡単な方法は、実際にはLAGウィンドウ関数を使用することですが、3つのツール間で複数参照の場合を比較するためだけに、年間の注文数を表すテーブル式の2つのインスタンス間の結合を使用します。
これは、派生テーブルでタスクを処理するためにシリーズの前半で使用したコードです。
SELECT CUR.orderyear, CUR.numorders, CUR.numorders - PRV.numorders AS diff FROM ( SELECT YEAR(orderdate) AS orderyear, COUNT(*) AS numorders FROM Sales.Orders GROUP BY YEAR(orderdate) ) AS CUR LEFT OUTER JOIN ( SELECT YEAR(orderdate) AS orderyear, COUNT(*) AS numorders FROM Sales.Orders GROUP BY YEAR(orderdate) ) AS PRV ON CUR.orderyear = PRV.orderyear + 1;
ここには非常に明らかな欠点があります。テーブル式の定義を2回繰り返す必要があります。基本的に、同じクエリコードに基づいて2つの名前付きテーブル式を定義しています。
CTEを使用して同じタスクを処理するコードは次のとおりです。
WITH OrdCount AS ( SELECT YEAR(orderdate) AS orderyear, COUNT(*) AS numorders FROM Sales.Orders GROUP BY YEAR(orderdate) ) SELECT CUR.orderyear, CUR.numorders, CUR.numorders - PRV.numorders AS diff FROM OrdCount AS CUR LEFT OUTER JOIN OrdCount AS PRV ON CUR.orderyear = PRV.orderyear + 1;
ここには明らかな利点があります。内部クエリの単一のインスタンスに基づいて名前付きテーブル式を1つだけ定義し、それを外部クエリから2回参照します。
この意味で、ビューはCTEに似ています。次のように、クエリの1つのコピーのみに基づいて1つのビューのみを定義します。
CREATE OR ALTER VIEW Sales.YearlyOrderCounts AS SELECT YEAR(orderdate) AS orderyear, COUNT(*) AS numorders FROM Sales.Orders GROUP BY YEAR(orderdate); GO
ただし、CTEを使用する場合よりも優れているのは、名前付きテーブル式を外部ステートメントでのみ再利用することに限定されないことです。適切な権限があれば、関係のないクエリをいくつでも使用して、ビュー名を何度でも再利用できます。ビューへの複数の参照を使用してタスクを実行するためのコードは次のとおりです。
SELECT CUR.orderyear, CUR.numorders, CUR.numorders - PRV.numorders AS diff FROM Sales.YearlyOrderCounts AS CUR LEFT OUTER JOIN Sales.YearlyOrderCounts AS PRV ON CUR.orderyear = PRV.orderyear + 1;
ビューは派生テーブルよりもCTEに似ているようですが、より再利用可能なツールであるという追加機能と、アクセス許可を制御する機能があります。または、それを好転させるには、CTEをステートメントスコープのビューと考えるのがおそらく適切です。ここで本当に素晴らしいのは、CTEのスコープよりも広いスコープ、ビューのスコープよりも狭いスコープを持つ名前付きテーブル式もある場合です。たとえば、セッションレベルのスコープ付き名前付きテーブル式があれば素晴らしいと思いませんか?
概要
私はこのトピックが大好きです。テーブル式には、関係理論に根ざしたものがたくさんあり、それは数学に根ざしています。私は物事の正しい用語が何であるかを知るのが大好きです、そして一般的に私が基礎を注意深く理解していることを確認するのが好きです。長年にわたる私の学習プロセスを振り返ると、基礎をしっかりと理解することを主張し、正しい用語を使用することと、後ではるかに高度で複雑なものに到達したときにあなたのことを本当に知ることとの間の非常に明確な道筋を見ることができます。
では、ビューに関して重要な部分は何ですか?
- ビューはテーブルです。
- クエリから派生したテーブル(テーブル式)です。
- テーブル名であるため、ユーザーにはテーブル名のように見える名前が付けられています。
- データベースに永続オブジェクトとして作成されます。
- ビューに対するアクセス許可を制御できます。
ビューは、多くの点でCTEに似ています。モジュール方式でソリューションを開発するという意味で、最初から最後まで一度に1つのユニットに焦点を合わせます。また、外部クエリからビュー名への複数の参照を持つことができるという意味で。ただし、CTEよりも優れているのは、ビューが外部ステートメントのスコープだけに限定されるのではなく、データベースから削除されるまで再利用できることです。
ビューについてはまだまだ多くのことが言えるので、来月も話し合いを続けます。その間、私はあなたに考えを残したいと思います。派生テーブルとCTEを使用すると、内部クエリでSELECT*を優先するケースを作成できます。詳細については、シリーズのパート3で作成したケースを参照してください。ビューで同様のケースを作成できますか、それともビューで悪い考えですか?