テーブル式に関するシリーズの一環として、先月、ビューの取材を開始しました。具体的には、ビューの論理的な側面について説明し、それらの設計を派生テーブルやCTEの設計と比較しました。今月は、SELECT *とDDLの変更に注目して、ビューの論理的な側面について引き続き取り上げます。
この記事で使用するコードはどのデータベースでも実行できますが、デモでは、以前の記事で使用したものと同じサンプルデータベースであるTSQLV5を使用します。 TSQLV5を作成してデータを取り込むスクリプトはここにあり、そのER図はここにあります。
ビューの内部クエリでSELECT*を使用することはお勧めできません
先月の記事の結論のセクションで、私は思考の糧として質問を投げかけました。シリーズの前半で、派生テーブルとCTEで使用される内部テーブル式でSELECT*を使用することを支持するケースを作成したことを説明しました。記憶をリフレッシュする必要がある場合の詳細については、シリーズのパート3を参照してください。次に、ビューの定義に使用される内部テーブル式に対して同じ推奨事項が引き続き有効かどうかを検討するように依頼しました。おそらく、このセクションのタイトルはすでにネタバレでしたが、ビューを見ると、実際には非常に悪い考えだと言っておきます。
SCHEMABINDING属性で定義されていないビューから始めます。これにより、依存オブジェクトへの関連するDDLの変更が防止され、次に、この属性を使用すると状況がどのように変化するかを説明します。
これが私の議論を提示する最も簡単な方法になるので、私は例に直接ジャンプします。
次のコードを使用して、テーブルに対するSELECT *を使用したクエリに基づいて、dbo.T1というテーブルとdbo.V1というビューを作成します。
USETSQLV5; DROP VIEW IF EXISTS dbo.V1; DROP TABLE IF EXISTS dbo.T1; GO CREATE TABLE dbo.T1(keycol INT NOT NULL IDENTITY CONSTRAINT PK_T1 PRIMARY KEY、intcol INT NOT NULL、charcol VARCHAR(10)NOT NULL); INSERT INTO dbo.T1(intcol、charcol)VALUES(10、'A')、(20、'B'); GO CREATE OR ALTER VIEW dbo.V1AS SELECT * FROM dbo.T1; GO
テーブルに現在keycol、intcol、charcolの列があることを確認してください。
次のコードを使用して、ビューをクエリします。
SELECT * FROM dbo.V1;
次の出力が得られます:
keycol intcol charcol ----------- ----------- ---------- 1 10 A2 20 B
ここでは特別なことは何もありません。
ビューを作成すると、SQLServerはメタデータ情報をいくつかのカタログオブジェクトに記録します。これは、sys.viewsを介して照会できるいくつかの一般情報、sys.sql_modulesを介して照会できるビュー定義、sys.columnsを介して照会できる列情報を記録し、その他の情報は他のオブジェクトから入手できます。また、SQL Serverを使用すると、ビューに対するアクセス許可を制御できることも、この説明に関連しています。ビューの内部テーブル式でSELECT*を使用するときに警告したいのは、DDLの変更が基になる依存オブジェクトに適用されたときに何が起こる可能性があるかです。
次のコードを使用して、user1というユーザーを作成し、ビューからkeycol列とintcol列を選択する権限をユーザーに付与しますが、charcolは選択しません。
DROP USER IF EXISTS user1;ログインなしでユーザーuser1を作成します。 GRANT SELECT ON dbo.V1(keycol、intcol)TO user1;
この時点で、ビューに関連する記録されたメタデータのいくつかを調べてみましょう。次のコードを使用して、sys.viewsからビューを表すエントリを返します。
SELECT SCHEMA_NAME(schema_id)AS schemaname、name、object_id、type_descFROM sys.viewsWHERE object_id =OBJECT_ID(N'dbo.V1');
このコードは次の出力を生成します:
schemaname name object_id type_desc ----------- ----- ----------- ---------- dboV1130099504表示
次のコードを使用して、sys.modulesからビュー定義を取得します。
SELECT定義FROMsys.sql_modulesWHEREobject_id =OBJECT_ID(N'dbo.V1');
別のオプションは、次のようにOBJECT_DEFINITION関数を使用することです:
SELECT OBJECT_DEFINITION(OBJECT_ID(N'dbo.V1'));
次の出力が得られます:
CREATE VIEW dbo.V1AS SELECT * FROM dbo.T1;
次のコードを使用して、sys.columnsからビューの列定義をクエリします。
SELECT name AS column_name、column_id、TYPE_NAME(system_type_id)AS data_typeFROM sys.columnsWHERE object_id =OBJECT_ID(N'dbo.V1');
予想どおり、ビューの3つの列keycol、intcol、charcolに関する情報を取得します。
column_name column_id data_type ------------ ----------- ---------- keycol 1 intintcol 2 intcharcol 3 varchar
列に関連付けられている列ID(順序位置)を確認します。
次のように、標準の情報スキーマビューINFORMATION_SCHEMA.COLUMNSにクエリを実行すると、同様の情報を取得できます。
SELECT COLUMN_NAME、ORDINAL_POSITION、DATA_TYPEFROM INFORMATION_SCHEMA.COLUMNSWHERE TABLE_SCHEMA =N'dbo'AND TABLE_NAME =N'V1';
ビューの依存関係情報(ビューが参照するオブジェクト)を取得するには、次のようにsys.dm_sql_referenced_entitiesにクエリを実行できます。
SELECT OBJECT_NAME(referenced_id)AS reference_object、referenced_minor_id、COL_NAME(referenced_id、referenced_minor_id)AS column_nameFROM sys.dm_sql_referenced_entities(N'dbo.V1'、N'OBJECT');
テーブルT1とその3つの列への依存関係が見つかります:
reference_object reference_minor_id column_name ------------------ ------------------- ------- ---- T1 0 NULLT1 1 keycolT1 2 intcolT1 3 charcol
ご想像のとおり、列のreference_minor_id値は、前に見た列IDです。
V1に対するuser1の権限を取得する場合は、次のようにsys.database_permissionsにクエリを実行できます。
SELECT OBJECT_NAME(major_id)AS reference_object、minor_id、COL_NAME(major_id、minor_id)AS column_name、permission_nameFROM sys.database_permissionsWHERE major_id =OBJECT_ID(N'dbo.V1')AND grantee_principal_id =USER_ID(N'user1); pre>このコードは次の出力を生成し、実際にuser1がkeycolとintcolに対してのみ選択権限を持ち、charcolに対しては権限を持たないことを確認します。
reference_object minor_id column_name permit_name ------------------ ----------- ------------ - -------------- V1 1 keycol SELECTV1 2 intcol SELECT繰り返しますが、minor_id値は、前に見た列IDです。ユーザーuser1には、IDが1と2の列に対する権限があります。
次に、次のコードを実行してuser1になりすまし、V1のすべての列にクエリを実行します。
EXECUTE AS USER =N'user1'; SELECT * FROM dbo.V1;ご想像のとおり、charcolをクエリする権限がないため、権限エラーが発生します:
メッセージ230、レベル14、状態1、行141
SELECT権限は、オブジェクト「V1」、データベース「TSQLV5」、スキーマ「dbo」の列「charcol」で拒否されました。keycolとintcolのみをクエリしてみてください:
SELECT keycol、intcol FROM dbo.V1;今回はクエリが正常に実行され、次の出力が生成されます。
keycol intcol ----------- -----------1 102 20これまでのところ驚きはありません。
次のコードを実行して、元のユーザーに戻します。
REVERT;次に、基になるテーブルdbo.T1にいくつかの構造上の変更を適用しましょう。次のコードを実行して、最初にdatecolとbinarycolという2つの列を追加してから、列intcolを削除します。
ALTER TABLE dbo.T1 ADD datecol DATE NOT NULL DEFAULT( '99991231')、binarycol VARBINARY(3)NOT NULL DEFAULT(0x112233); ALTER TABLE dbo.T1 DROP COLUMN intcol;ビューがSCHEMABINDING属性で作成されていないため、SQLServerはビューによって参照される列の構造変更を拒否しませんでした。さて、キャッチのために。この時点では、SQLServerはさまざまなカタログオブジェクトのビューのメタデータ情報をまだ更新していません。
次のコードを使用して、元のユーザー(user1ではない)を使用してビューをクエリします。
SELECT * FROM dbo.V1;次の出力が得られます:
keycol intcol charcol ----------- ---------- ---------- 1 A 9999-12-312 B 9999-12-31intcolは実際にはcharcolのコンテンツを返し、charcolはdatecolのコンテンツを返すことに注意してください。テーブルにはもうintcolはありませんが、datecolはあります。また、新しい列のbinarycolは返されません。
何が起こっているのかを理解するために、次のコードを使用してビューの列メタデータをクエリします。
SELECT name AS column_name、column_id、TYPE_NAME(system_type_id)AS data_typeFROM sys.columnsWHERE object_id =OBJECT_ID(N'dbo.V1');このコードは次の出力を生成します:
column_name column_id data_type ------------ ----------- ---------- keycol 1 intintcol 2 intcharcol 3 varcharご覧のとおり、ビューのメタデータはまだ更新されていません。 intcolは列ID2として、charcolは列ID 3として表示されます。実際には、intcolはもう存在せず、charcolは列2であり、datecolは列3であると想定されています。
許可情報に変更があるかどうかを確認しましょう:
SELECT OBJECT_NAME(major_id)AS reference_object、minor_id、COL_NAME(major_id、minor_id)AS column_name、permission_nameFROM sys.database_permissionsWHERE major_id =OBJECT_ID(N'dbo.V1')AND grantee_principal_id =USER_ID(N'user1); pre>次の出力が得られます:
reference_object minor_id column_name permit_name ------------------ ----------- ------------ - -------------- V1 1 keycol SELECTV1 2 intcol SELECT権限情報は、user1がビューの列1と2に対する権限を持っていることを示しています。ただし、メタデータは列2がintcolと呼ばれていると見なしていても、実際にはT1のcharcolにマップされています。 user1はcharcolにアクセスできないため、これは危険です。実際には、この列にパスワードなどの機密情報が含まれているとしたらどうでしょうか。
もう一度user1になりすまして、すべてのビュー列にクエリを実行しましょう:
EXECUTE AS USER ='user1'; SELECT * FROM dbo.V1;charcolにアクセスできないという許可エラーが発生します:
メッセージ230、レベル14、状態1、行211
SELECT権限は、オブジェクト「V1」、データベース「TSQLV5」、スキーマ「dbo」の列「charcol」で拒否されました。ただし、keycolとintcolを明示的に要求するとどうなるかを確認してください。
SELECT keycol、intcol FROM dbo.V1;次の出力が得られます:
keycol intcol ----------- ---------- 1 A2 Bこのクエリは成功しますが、intcolの下のcharcolの内容を返すだけです。ユーザーuser1は、この情報にアクセスできないようになっています。おっと!
この時点で、次のコードを実行して元のユーザーに戻ります。
REVERT;SQLモジュールを更新
ビューの内部テーブル式でSELECT*を使用することは悪い考えであることがはっきりとわかります。しかし、それだけではありません。一般に、参照されるオブジェクトと列に対してDDLが変更されるたびに、ビューのメタデータを更新することをお勧めします。次のように、sp_refreshviewまたはより一般的なsp_refreshmoduleを使用してこれを行うことができます。
EXEC sys.sp_refreshsqlmodule N'dbo.V1';メタデータが更新されたので、ビューを再度クエリします。
SELECT * FROM dbo.V1;今回は期待どおりの出力が得られます:
keycol charcol datecol binarycol ----------- ---------- ---------- ---------1 A 9999 -12-31 0x1122332 B 9999-12-31 0x112233列charcolは正しく名前が付けられており、正しいデータを示しています。 intcolは表示されず、datecolとbinarycolの新しい列が表示されます。
ビューの列メタデータをクエリします:
SELECT name AS column_name、column_id、TYPE_NAME(system_type_id)AS data_typeFROM sys.columnsWHERE object_id =OBJECT_ID(N'dbo.V1');出力に正しい列メタデータ情報が表示されるようになりました:
column_name column_id data_type ------------ ----------- ---------- keycol 1 intcharcol 2 varchardatecol 3 datebinarycol 4 varbinaryビューに対するuser1の権限をクエリします:
SELECT OBJECT_NAME(major_id)AS reference_object、minor_id、COL_NAME(major_id、minor_id)AS column_name、permission_nameFROM sys.database_permissionsWHERE major_id =OBJECT_ID(N'dbo.V1')AND grantee_principal_id =USER_ID(N'user1); pre>次の出力が得られます:
reference_object minor_id column_name permit_name ------------------ ----------- ------------ - -------------- V1 1 keycol SELECT許可情報が正しくなりました。ユーザーuser1には、keycolを選択するための権限のみがあり、intcolの権限情報は削除されています。
すべてが正常であることを確認するために、user1になりすましてビューにクエリを実行し、これをテストしてみましょう。
EXECUTE AS USER ='user1'; SELECT * FROM dbo.V1;datecolとbinarycolに対する権限がないため、2つの権限エラーが発生します。
メッセージ230、レベル14、状態1、行281
オブジェクト「V1」、データベース「TSQLV5」、スキーマ「dbo」の列「datecol」でSELECT権限が拒否されました。メッセージ230、レベル14、状態1、行281
オブジェクト「V1」、データベース「TSQLV5」、スキーマ「dbo」の列「binarycol」でSELECT権限が拒否されました。keycolとintcolをクエリしてみてください:
SELECT keycol、intcol FROM dbo.V1;今回のエラーは、intcolという列がないことを正しく示しています:
メッセージ207、レベル16、状態1、行279無効な列名'intcol'。
intcolのみをクエリする:
SELECT keycol FROM dbo.V1;このクエリは正常に実行され、次の出力が生成されます。
keycol -----------12この時点で、次のコードを実行して元のユーザーに戻ります。
REVERT;SELECT *を避け、明示的な列名を使用するだけで十分ですか?
ビューの内部テーブル式にSELECT*がないという慣習に従っている場合、これで問題を回避できますか?さて、見てみましょう…
次のコードを使用してテーブルとビューを再作成します。今回は、ビューの内部クエリで列を明示的にリストします。
DROP VIEW IF EXISTS dbo.V1; DROP TABLE IF EXISTS dbo.T1; GO CREATE TABLE dbo.T1(keycol INT NOT NULL IDENTITY CONSTRAINT PK_T1 PRIMARY KEY、intcol INT NOT NULL、charcol VARCHAR(10)NOT NULL); INSERT INTO dbo.T1(intcol、charcol)VALUES(10、'A')、(20、'B'); GO CREATE OR ALTER VIEW dbo.V1AS SELECT keycol、intcol、charcol FROM dbo.T1; GO>ビューをクエリします:
SELECT * FROM dbo.V1;次の出力が得られます:
keycol intcol charcol ----------- ----------- ---------- 1 10 A2 20 Bここでも、user1にkeycolとintcolを選択する権限を付与します。
GRANT SELECT ON dbo.V1(keycol、intcol)TO user1;次に、以前と同じ構造変更を適用します。
ALTER TABLE dbo.T1 ADD datecol DATE NOT NULL DEFAULT( '99991231')、binarycol VARBINARY(3)NOT NULL DEFAULT(0x112233); ALTER TABLE dbo.T1 DROP COLUMN intcol;ビューにintcolへの明示的な参照がある場合でも、SQLServerがこれらの変更を受け入れたことを確認します。繰り返しになりますが、これは、ビューがSCHEMABINDINGオプションなしで作成されたためです。
ビューをクエリします:
SELECT * FROM dbo.V1;この時点で、SQLServerは次のエラーを生成します。
メッセージ207、レベル16、状態1、手順V1、行5[バッチ開始行344]
無効な列名'intcol'。メッセージ4413、レベル16、状態1、行345
バインディングエラーのため、ビューまたは関数「dbo.V1」を使用できませんでした。SQL Serverは、ビュー内のintcol参照を解決しようとしましたが、もちろん失敗しました。
しかし、元の計画がintcolを削除し、後でそれを追加し直すことだったとしたらどうでしょうか。次のコードを使用して追加し直してから、ビューをクエリします。
ALTER TABLE dbo.T1 ADD intcol INT NOT NULL DEFAULT(0); SELECT * FROM dbo.V1;このコードは次の出力を生成します:
keycol intcol charcol ----------- ----------- ---------- 1 0 A2 0 B結果は正しいようです。
ビューをuser1としてクエリするのはどうですか?試してみましょう:
EXECUTE AS USER ='user1'; SELECT * FROM dbo.V1;すべての列をクエリすると、charcolに対する権限がないため、予期されるエラーが発生します:
メッセージ230、レベル14、状態1、行367
SELECT権限は、オブジェクト「V1」、データベース「TSQLV5」、スキーマ「dbo」の列「charcol」で拒否されました。keycolとintcolを明示的にクエリします:
SELECT keycol、intcol FROM dbo.V1;次の出力が得られます:
keycol intcol ----------- -----------1 02 0ビューのメタデータを更新しなかったにもかかわらず、ビューの内部クエリでSELECT *を使用しなかったという事実のおかげで、すべてが順調に進んでいるようです。それでも、安全を期すために、参照されるオブジェクトと列に対するDDLの変更後に、ビューのメタデータを更新することをお勧めします。
この時点で、次のコードを実行して元のユーザーに戻ります。
REVERT;スキーマバインド
SCHEMABINDINGビュー属性を使用すると、前述の問題の多くを回避できます。以前に見た問題を回避するための鍵の1つは、ビューの内部クエリでSELECT*を使用しないことです。ただし、参照列の削除など、依存オブジェクトに対する構造変更の問題もあります。これにより、ビューのクエリ時にエラーが発生する可能性があります。 SCHEMABINDINGビュー属性を使用すると、内部クエリでSELECT*を使用できなくなります。さらに、SQL Serverは、関連するDDLの変更を依存するオブジェクトと列に適用する試みを拒否します。関連して、参照されたテーブルまたは列を削除するような変更を意味します。参照されるテーブルに列を追加することは明らかに問題ではないため、SCHEMABINDINGはそのような変更を防ぐことはできません。
これを示すために、次のコードを使用して、ビュー定義にSCHEMABINDINGを使用して、テーブルとビューを再作成します。
DROP VIEW IF EXISTS dbo.V1; DROP TABLE IF EXISTS dbo.T1; GO CREATE TABLE dbo.T1(keycol INT NOT NULL IDENTITY CONSTRAINT PK_T1 PRIMARY KEY、intcol INT NOT NULL、charcol VARCHAR(10)NOT NULL); INSERT INTO dbo.T1(intcol、charcol)VALUES(10、'A')、(20、'B'); GO CREATE OR ALTER VIEW dbo.V1 WITH SCHEMABINDINGAS SELECT * FROM dbo.T1; GOエラーが発生します:
メッセージ1054、レベル15、状態6、手順V1、行5[バッチ開始行387]
構文'*'は、スキーマにバインドされたオブジェクトでは許可されていません。SCHEMABINDINGを使用する場合、ビューの内部テーブル式でSELECT*を使用することはできません。
明示的な列リストを使用して、今回のみビューを作成してみてください:
SCHEMABINDINGAS SELECT keycol、intcol、charcol FROM dbo.T1; GOを使用してビューdbo.V1を作成または変更します。今回はビューが正常に作成されました。
keycolとintcolに対するuser1権限を付与します:
GRANT SELECT ON dbo.V1(keycol、intcol)TO user1;次に、テーブルに構造上の変更を適用してみます。まず、いくつかの列を追加します:
ALTER TABLE dbo.T1 ADD datecol DATE NOT NULL DEFAULT( '99991231')、binarycol VARBINARY(3)NOT NULL DEFAULT(0x112233);列を既存のスキーマバインドビューの一部にすることはできないため、列の追加は問題になりません。したがって、このコードは正常に完了します。
列intcolを削除してみてください:
ALTER TABLE dbo.T1 DROP COLUMN intcol;次のエラーが発生します:
メッセージ5074、レベル16、状態1、行418
オブジェクト「V1」は列「intcol」に依存しています。メッセージ4922、レベル16、状態9、行418
1つ以上のオブジェクトがこの列にアクセスするため、ALTER TABLE DROPCOLUMNintcolが失敗しました。スキーマにバインドされたオブジェクトが存在する場合、参照される列を削除または変更することは許可されていません。
それでもintcolを削除する必要がある場合は、最初にスキーマにバインドされた参照ビューを削除し、変更を適用してから、次のようにビューを再作成して権限を再割り当てする必要があります。
DROP VIEW IF EXISTS dbo.V1; GO ALTER TABLE dbo.T1 DROP COLUMN intcol; GO CREATE OR ALTER VIEW dbo.V1 WITH SCHEMABINDINGAS SELECT keycol、charcol、datecol、binarycol FROM dbo.T1; GO GRANT SELECTONdbo。 V1(keycol、datecol、binarycol)TO user1; GOもちろん、この時点では、ビュー定義を新しく作成したため、ビュー定義を更新する必要はありません。
テストが完了したので、クリーンアップのために次のコードを実行します。
DROP VIEW IF EXISTS dbo.V1; DROP TABLE IF EXISTS dbo.T1; DROP USER IF EXISTS user1;概要
ビューの内部テーブル式でSELECT*を使用することは、非常に悪い考えです。参照されるオブジェクトに構造上の変更が適用されると、誤った列名が取得される可能性があり、ユーザーがアクセスできないはずのデータにアクセスできるようになる可能性もあります。参照される列名を明示的にリストすることは重要な方法です。
ビュー定義でSCHEMABINDINGを使用すると、列名を明示的に一覧表示する必要があり、依存オブジェクトに関連する構造上の変更はSQLServerによって拒否されます。したがって、SCHEMBINDINGを使用してビューを作成することは常に良い考えのように思われるかもしれません。ただし、このオプションには注意が必要です。ご覧のとおり、SCHEMBINDINGを使用するときに参照オブジェクトに構造変更を適用すると、より長く、より複雑なプロセスになります。これは、非常に高い可用性が必要なシステムでは特に問題になる可能性があります。 VARCHAR(50)として定義された列をVARCHAR(60)に変更する必要があると想像してください。この列を参照するSCHEMABINDINGで定義されたビューがある場合、これは許可された変更ではありません。他のビューなどによって参照される可能性のある一連の参照ビューを削除することの影響は、システムにとって問題になる可能性があります。つまり、企業がSCHEMABINDINGをサポートするすべてのオブジェクトで使用する必要があるというポリシーを採用することは必ずしも簡単ではありません。ただし、ビューの内部クエリでSELECT*を使用しないポリシーを採用する方が簡単です。
ビューに関しては、さらに多くのことを検討する必要があります。来月も続く…