アクセス履歴¶
このトピックでは、Snowflakeのユーザーアクセス履歴に関する概念を説明します。
概要¶
Snowflakeのアクセス履歴とは、ユーザークエリがデータを読み取り、 SQL ステートメントが INSERT、 UPDATE、 DELETE などのデータ書き込み操作を、ソースデータオブジェクトからターゲットデータオブジェクトに COPY コマンドのバリエーションとともに実行するときを指します。ユーザーアクセス履歴は、 ACCOUNT_USAGE および ORGANIZATION_USAGE スキーマの ACCESS_HISTORY ビューをクエリすることで確認できます。これらのビューの記録は、ユーザー(すなわちクエリ演算子)、クエリ、テーブルまたはビュー、列、およびデータの間に直接リンクがあるため、規制コンプライアンス監査を容易にし、人気のある、頻繁にアクセスされるテーブルや列に関する洞察を提供します。
ACCESS_HISTORY ビューの各行には、 SQL ステートメントごとに単一の記録が含まれます。記録には次の種類の情報が含まれます。
クエリのデータの基になるテーブルなど、クエリが直接および間接的にアクセスした ソース列。
SELECT ステートメントで指定された列など、クエリ結果でユーザーに表示される 投影列。
クエリ結果を決定するために使用されるけれども、投影されない列。例えば、結果をフィルターするための WHERE 句にある列など。
例:
CREATE OR REPLACE VIEW v1 (vc1, vc2) AS
SELECT c1 as vc1,
c2 as vc2
FROM t
WHERE t.c3 > 0
;
列C1とC2は、ビューが直接アクセスするソース列で、 ACCESS_HISTORY ビューの
base_objects_accessed
列に記録されます。列C3は、 ACCESS_HISTORY ビューの
base_objects_accessed
列に記録されている、ビューに含まれる行のフィルターに使用されます。列 VC1 と VC2 は、ビューをクエリするとき(
SELECT * FROM v1;
)にユーザーに表示される投影列で、 ACCESS_HISTORY ビューのdirect_objects_accessed
列に記録されます。
同じ動作が WHERE 句のキー列にも当てはまります。例:
CREATE OR REPLACE VIEW join_v (vc1, vc2, c1) AS
SELECT
bt.c1 AS vc1,
bt.c2 AS vc2,
jt.c1
FROM bt, jt
WHERE bt.c3 = jt.c1;
ビューを作成するには、
bt
(ベーステーブル)とjt
(結合テーブル)の2つの異なるテーブルが必要です。ベーステーブルの列C1、列C2、列C3、結合テーブルの列C1は、 ACCESS_HISTORY ビューの
base_objects_accessed
列にすべて記録されます。列 VC1、 VC2、およびC1は、ビューをクエリするとき(
SELECT * FROM join_v;
)にユーザーに表示される投影列で、 ACCESS_HISTORY ビューのdirect_objects_accessed
列に記録されます。
注釈
Account Usage QUERY_HISTORY ビューの記録が、常に ACCESS_HISTORY ビューに記録されるとは限りません。SQL ステートメントの構造によって、Snowflakeが ACCESS_HISTORY ビューにエントリを記録するかどうかが決まります。
ACCESS_HISTORY ビューでSnowflakeがサポートする読み取りおよび書き込み操作の詳細については、ビュー 使用上の注意 をご参照ください。
読み取りおよび書き込み操作の追跡¶
ACCOUNT_USAGE と ORGANIZATION_USAGE の両スキーマの ACCESS_HISTORY ビューには、以下の列があります。
query_id | query_start_time | user_name | direct_objects_accessed | base_objects_accessed | objects_modified | object_modified_by_ddl | policies_referenced | parent_query_id | root_query_id
読み取り操作は最初の5列で追跡され、最後の列 objects_modified
は、Snowflakeの列、テーブル、およびステージに関連するデータ書き込み情報を指定します。
Snowflakeのクエリとデータベースオブジェクトの作成方法により、Snowflakeが base_objects_accessed
列、 objects_modified
列、 および direct_objects_accessed
列に対して返す情報が決まります。
同様に、クエリが行アクセスポリシーによって保護されたオブジェクトまたはマスキングポリシーによって保護された列を参照する場合、Snowflakeはポリシー情報を policies_referenced
列に記録します。
object_modified_by_ddl
列は、データベース、スキーマ、テーブル、ビュー、および列に対する DDL 操作を記録します。これらの操作には、テーブルやビューに対する行アクセスポリシーを指定するステートメント、列に対するマスキングポリシーを指定するステートメント、オブジェクトや列に対するタグの更新(例: タグの設定、タグ値の変更)も含まれます。
parent_query_id
列と root_query_id
列には、次に対応するクエリ IDs が記録されています。
他のオブジェクトに対して読み取りまたは書き込み操作を行うクエリ。
ネストされたストアドプロシージャの呼び出しなど、ストアドプロシージャを呼び出すオブジェクトに対して読み取りまたは書き込み操作を実行するクエリ。詳細については、 祖先クエリ (このトピック内)をご参照くださいい。
列の詳細については、 ACCESS_HISTORY ビューの 列 セクションをご参照ください。
読む¶
読み取りクエリと、 ACCESS_HISTORY ビューがこの情報を記録する方法を理解するには、次のシナリオを検討してください。
一連のオブジェクト:
base_table
»view_1
»view_2
»view_3
。次のような
view_2
の読み取りクエリ:select * from view_2;
この例では、Snowflakeは次を返します。
direct_objects_accessed
列にview_2
。クエリでview_2
が指定されているためです。base_objects_accessed
列にbase_table
。これがview_2
にあるデータの元ソースであるためです。
view_1
と view_3
は、 direct_objects_accessed
と base_objects_accessed
の列に含まれません。これらのビューはどちらもクエリに含まれておらず、 view_2
のデータのソースとして機能するベースオブジェクトではないためです。
書き込み¶
書き込み操作と、 ACCESS_HISTORY ビューがこの情報を記録する方法を理解するには、次のシナリオを検討してください。
データソース:
base_table
データソース(つまり、 CTAS)からテーブルを作成します。
create table table_1 as select * from base_table;
この例では、Snowflakeは次を返します。
テーブルは直接アクセスされ、データのソースであるため、
base_objects_accessed
列とdirect_objects_accessed
列内のbase_table
。テーブルの作成時に書き込まれた列を含む
objects_modified
列にtable_1
。
サポートされている操作¶
ACCESS_HISTORY ビューがサポートする読み取りおよび書き込み操作の詳細については、 ACCESS_HISTORY ビュー の使用上の注意セクションをご参照ください。
単一リクエストで複数のステートメント¶
Snowflakeは複数のステートメントを同時に単一リクエストとして実行することをサポートしています。アクセス履歴でリクエストをどのように追跡するかは、リクエストが Snowsight で実行されたか、プログラムで実行されたかによって異なります。
Snowsight を使用して複数のステートメントを実行すると、クエリが1つずつ実行され、最後に実行されたクエリの
query_id
が返されます。実行されたすべてのステートメントとその戻り値は、 ACCESS_HISTORY ビューで確認できます。Snowflake PythonコネクタやSnowflake SQL API のような機能は、複数の SQL ステートメントを単一リクエストにまとめ、すべてのステートメントに対して単一の
query_id
を返します。この番号は、実際には個々のステートメントすべての親クエリIDです。リクエストを構成する各ステートメントのquery_id
を返すには、parent_query_id
を使用して ACCESS_HISTORY ビューをクエリする必要があります。例えば、リクエストがquery_id = 6789
を返した場合、以下を実行することで個々のステートメントのクエリIDを返すことができます。SELECT query_id, parent_query_id, direct_objects_accessed FROM snowflake.account_usage.access_history WHERE parent_query_id = 6789;
利点¶
Snowflakeのアクセス履歴には、読み取りおよび書き込み操作に関する次の利点があります。
- データ検出:
未使用のデータを検出して、データをアーカイブするか削除するかを決定します。
- 機密データの移動状況を追跡する:
外部クラウドストレージの場所(例: Amazon S3バケット)からターゲットのSnowflakeテーブルへの、またはその逆のデータ移動を追跡します。
あるSnowflakeテーブルから異なるSnowflakeテーブルへの内部データの移動を追跡します。
機密データの移動をトレースした後、ポリシー(マスキング および 行アクセス)を適用してデータを保護し、 アクセス制御設定 を更新して、ステージへのアクセスをさらに規制します。テーブル、および タグ を設定して、機密データを含むステージ、テーブル、および列のコンプライアンス要件について追跡できるようにします。
- データ検証:
レポート、ダッシュボード、およびチャートやグラフなどのデータ可視化製品の正確性と整合性は、データを元のソースまで追跡できるため、検証されます。
データスチュワードは、特定のテーブルまたはビューを削除または変更する前にユーザーに通知することもできます。
- コンプライアンス監査:
GDPR や CCPA などのコンプライアンス規制を満たすために、テーブルまたはステージで書き込み操作を実行したSnowflakeユーザーを特定し、書き込み操作がいつ発生したかを特定します。
- 全体的なデータガバナンスを強化する:
ACCESS_HISTORY ビューは、アクセスされたデータ、データアクセスがいつ行われたか、およびアクセスされたデータがデータソースオブジェクトからデータターゲットオブジェクトにどのように移動したかを統一的に示します。
列の系列¶
列系統(つまり、列のアクセス履歴)は、Account Usage ACCESS_HISTORY ビューを拡張して、書き込み操作でソース列からターゲット列にデータがどのように流れるかを指定します。Snowflakeは、ソース列からのデータを参照する後続のすべてのテーブルオブジェクト(例: INSERT、 MERGE、 CTAS)を介して、ソース列からのデータを追跡します。ただし、系統チェーン内のオブジェクトがドロップされている場合を除きます。Snowflakeは、 ACCESS_HISTORY ビューの objects_modified
列を強化することで、列系統にアクセスできるようにします。
列系統は次のようなメリットをもたらします。
- 派生オブジェクトの保護:
データスチュワードは、派生オブジェクト(例: CTAS)を作成した後に追加作業なしで、機密性の高いソース列に簡単に タグ付け できます。その後、データスチュワードは、機密性の高い列を含むテーブルを 行アクセスポリシー で保護するか、機密性の高い列自体を マスキングポリシー または タグベースのマスキングポリシー で保護できます。
- 機密列のコピー頻度:
データプライバシー担当者は、機密データを含む列のオブジェクト数(例: テーブル1つ、ビュー2つ)を迅速に判断できます。機密データを含む列がテーブルオブジェクトに何回出現するかを知ることで、データプライバシー担当者は、規制コンプライアンス基準をどのように満たしているかを証明できます(例: 欧州連合の一般データ保護規則(GDPR)基準に準拠)。
- 根本原因の分析:
列系統は、データをソースまで追跡するメカニズムを提供します。これは、データ品質の低下に起因する障害点を特定し、トラブルシューティングプロセス中に分析する列の数を減らすのに役立ちます。
列系統についての詳細は、こちらをご覧ください。
例: 列の系列 (このトピック内)
マスキングと行アクセスポリシーの参照¶
POLICY_REFERENCED 列は、テーブルに設定された行アクセスポリシーまたは列に設定されたマスキングポリシーを持つオブジェクトを指定します。これには、行アクセスポリシーまたはマスキングポリシーによって保護されている中間オブジェクトが含まれます。Snowflakeは、テーブルまたは列に適用されるポリシーを記録します。
次のオブジェクトを検討してください。
t1
» v1
» v2
条件:
t1
はベーステーブル。v1
はベーステーブルから構築されたビュー。v2
はv1
から作成されたビュー。
ユーザーが v2
をクエリすると、 policies_referenced
列には、 v2
を保護する行アクセスポリシー、 v2
内の列を保護する各マスキングポリシー、または該当する場合は両方の種類のポリシーが記録されます。さらに、この列には、 t1
と v1
を保護するマスキングポリシーまたは行アクセスポリシーが記録されます。
これらの記録は、ポリシーで保護されたオブジェクトにアクセスされた方法をデータ管理者が理解するのに役立ちます。
policies_referenced
列は、 ACCESS_HISTORY ビューに追加の利点を提供します。
特定のクエリでユーザーがアクセスする、ポリシーで保護されたオブジェクトを識別します。
ポリシー監査プロセスを簡素化します。
ACCESS_HISTORY ビューをクエリすると、ユーザーがアクセスする保護されたオブジェクトと保護された列に関する情報を取得するために、他のAccount Usageビュー(例: POLICY_REFERENCES や QUERY_HISTORY)での複雑な結合が不必要になります。
アカウントレベルと組織レベルのアクセス履歴の比較¶
管理者は、アカウントの ACCOUNT_USAGE スキーマの ACCESS_HISTORY ビュー をクエリすることで、アカウントレベルでのアクセス履歴を監視します。ACCOUNT_USAGE.ACCESS_HISTORY ビューに関する追加コストはありません。
ORGANIZATION_USAGE スキーマの ACCESS_HISTORY ビューは、組織内のすべてのアカウントのアクセス履歴を単一のビューに集め、組織レベルのアクセス履歴を提供します。この ORGANIZATION_USAGE.ACCESS_HISTORY ビューは、 組織アカウント にのみあります。
ORGANIZATION_USAGE スキーマにおける組織レベルのアクセス履歴は、 ACCOUNT_USAGE スキーマにおけるアクセス履歴とは以下の点で異なります。
- 追加列:
組織アカウントの ORGANIZATION_USAGE.ACCESS_HISTORY ビューには、 組織リスト に関連する洞察を提供する追加の列が含まれています。これらの列を使用して、コンシューマーのクエリによって組織リストに添付されたデータ製品のどれにアクセスしたか、およびそれらのデータ製品がマスキングポリシーなどのポリシーによって保護されているかどうかを判断できます。詳細については、 組織リストガバナンス をご参照ください。
- 追加コスト:
組織アカウントの ORGANIZATION_USAGE.ACCESS_HISTORY ビューは、以下のコストが発生するプレミアムビューです。
ACCESS_HISTORY ビューを生成するサーバーレスタスクに関連するコストを計算します。
ACCESS_HISTORY ビューのデータ保存に関連するストレージコスト。
これらのコストについては、 プレミアムビューに関わるコスト をご覧ください。
サポートされているオブジェクト¶
SQLステートメントに特定のタイプのオブジェクトが含まれる場合に、ACCESS_HISTORYビューに記録が含まれるかどうかを判断するには、以下の表を使用します。SQLステートメントの内容は以下の通りです。
データ操作言語(DML)ステートメント。例えば、テーブルにデータを挿入するステートメント。
データクエリ言語(DQL)ステートメント。例えば、SELECTステートメントを使用してデータを投影するステートメント。
データ定義言語(DDL)ステートメント。例えば、Snowflakeオブジェクトを作成または変更するステートメント。
オブジェクト |
DML |
DQL |
DDL |
注意 |
---|---|---|---|---|
DATABASE |
なし |
なし |
一部 |
DDLのサポートは、データベースでタグをセットする場合に限られます。 |
EXTERNAL TABLE |
✔ |
✔ |
✔ |
|
FUNCTION |
なし |
✔ |
✔ |
DQLのサポートは、SELECTステートメントに現れる関数に限られます。 |
ICEBERG TABLE |
一部 |
✔ |
一部 |
Snowflake が管理する Apache Iceberg™ テーブルに完全対応しています(DML、DQL、DDL)。管理されていない Apache Iceberg™ テーブルのDQLをサポートしています。 |
MATERIALIZED VIEW |
なし |
✔ |
✔ |
|
POLICY |
なし |
✔ |
✔ |
DDLのサポートは、ポリシーがオブジェクトに適用されたときに表示されます。DQLのサポートにより、クエリ実行時に施行中のポリシーが表示されます。 |
PROCEDURE |
なし |
✔ |
✔ |
プロシージャは複数のSQLステートメントを持つことができ、各ステートメントは別々の記録を生成します。 |
SCHEMA |
なし |
なし |
一部 |
DDLのサポートは、スキーマでタグをセットする場合に限られます。 |
SEQUENCE |
なし |
✔ |
DMLへの非サポートは意図的なものです。 |
|
STAGE |
一部 |
✔ |
DMLのサポートは、ステージをテーブルのソースとして使用する場合に限られます。DQLでは、ステージに対するクエリはサポートされていません。 |
|
STREAM |
なし |
一部 |
✔ |
DQLのサポートは、ストリームをテーブルのソースとして使用する場合に限られます。 |
TABLE |
✔ |
✔ |
✔ |
|
TAG |
なし |
なし |
✔ |
|
VIEW |
なし |
✔ |
✔ |
ACCESS_HISTORY ビューのクエリ¶
次のセクションでは、 ACCESS_HISTORY ビューのクエリ例を示します。
一部のサンプルクエリは、クエリのパフォーマンスを向上させるために query_start_time
列でフィルターすることに注意してください。パフォーマンスを向上させるもう1つのオプションは、より狭い時間範囲でクエリを実行することです。
例: クエリの読み取り¶
以下のサブセクションでは、次のユースケースで読み取り操作のために ACCESS_HISTORY ビューをクエリする方法について詳しく説明します。
特定のユーザーのアクセス履歴を取得します。
次の質問に答えるために、
object_id
(例: テーブルID)に基づいて、過去30日間の機密データアクセスのコンプライアンス監査を促進します。誰がデータにアクセスしましたか?
データはいつアクセスされましたか?
どの列がアクセスされましたか?
ユーザーのアクセス履歴を返す¶
最新のアクセスから開始して、ユーザーとクエリの開始時刻の順にユーザーアクセス履歴を返します。
SELECT user_name , query_id , query_start_time , direct_objects_accessed , base_objects_accessed FROM access_history ORDER BY 1, 3 desc ;
コンプライアンス監査を促進する¶
次の例は、コンプライアンス監査を容易にするために役立ちます。
object_id
値を追加して、過去30日間に機密テーブルにアクセスしたユーザーを特定します。SELECT distinct user_name FROM access_history , lateral flatten(base_objects_accessed) f1 WHERE f1.value:"objectId"::int=<fill_in_object_id> AND f1.value:"objectDomain"::string='Table' AND query_start_time >= dateadd('day', -30, current_timestamp()) ;
32998411400350
のobject_id
値を使用して、過去30日間にアクセスがあった時期を特定します。SELECT query_id , query_start_time FROM access_history , lateral flatten(base_objects_accessed) f1 WHERE f1.value:"objectId"::int=32998411400350 AND f1.value:"objectDomain"::string='Table' AND query_start_time >= dateadd('day', -30, current_timestamp()) ;
32998411400350
のobject_id
値を使用して、過去30日間にアクセスされた列を特定します。SELECT distinct f4.value AS column_name FROM access_history , lateral flatten(base_objects_accessed) f1 , lateral flatten(f1.value) f2 , lateral flatten(f2.value) f3 , lateral flatten(f3.value) f4 WHERE f1.value:"objectId"::int=32998411400350 AND f1.value:"objectDomain"::string='Table' AND f4.key='columnName' ;
例: 書き込み操作¶
以下のサブセクションでは、次のユースケースで書き込み操作のために ACCESS_HISTORY ビューをクエリする方法について詳しく説明します。
ステージからテーブルにデータをロードします。
テーブルからステージにデータをアンロードします。
PUT コマンドを使用して、ローカルファイルをステージにアップロードします。
GET コマンドを使用して、ステージからローカルディレクトリにデータファイルを取得します。
機密性の高いステージデータの動きを追跡します。
ステージからテーブルにデータをロードする¶
外部クラウドストレージのデータファイルからターゲットテーブルの列に値のセットをロードします。
copy into table1(col1, col2) from (select t.$1, t.$2 from @mystage1/data1.csv.gz);
direct_objects_accessed
列と base_objects_accessed
列は、外部の名前付きステージにアクセスしたことを指定します。
{ "objectDomain": STAGE "objectName": "mystage1", "objectId": 1, "stageKind": "External Named" }
objects_modified
列は、データがテーブルの2つの列に書き込まれたことを指定します。
{ "columns": [ { "columnName": "col1", "columnId": 1 }, { "columnName": "col2", "columnId": 2 } ], "objectId": 1, "objectName": "TEST_DB.TEST_SCHEMA.TABLE1", "objectDomain": TABLE }
テーブルからステージにデータをアンロードする¶
値のセットをSnowflakeテーブルからクラウドストレージにアンロードします。
copy into @mystage1/data1.csv from table1;
direct_objects_accessed
列と base_objects_accessed
列は、アクセスされたテーブル列を指定します。
{ "objectDomain": TABLE "objectName": "TEST_DB.TEST_SCHEMA.TABLE1", "objectId": 123, "columns": [ { "columnName": "col1", "columnId": 1 }, { "columnName": "col2", "columnId": 2 } ] }
objects_modified
列は、アクセスされたデータが書き込まれたステージを指定します。
{ "objectId": 1, "objectName": "mystage1", "objectDomain": STAGE, "stageKind": "External Named" }
PUT コマンドを使用して、ローカルファイルをステージにアップロードする¶
データファイルを内部(つまり、Snowflake)ステージにコピーします。
put file:///tmp/data/mydata.csv @my_int_stage;
direct_objects_accessed
列と base_objects_accessed
列は、アクセスされたファイルへのローカルパスを指定します。
{ "location": "file:///tmp/data/mydata.csv" }
objects_modified
列は、アクセスされたデータが書き込まれたステージを指定します。
{ "objectId": 1, "objectName": "my_int_stage", "objectDomain": STAGE, "stageKind": "Internal Named" }
GET コマンドを使用して、ステージからローカルディレクトリにデータファイルを取得する¶
内部ステージからローカルマシン上のディレクトリにデータファイルを取得します。
get @%mytable file:///tmp/data/;
direct_objects_accessed
列と base_objects_accessed
列は、アクセスされたステージとローカルディレクトリを指定します。
{ "objectDomain": Stage "objectName": "mytable", "objectId": 1, "stageKind": "Table" }
objects_modified
列は、アクセスされたデータが書き込まれたディレクトリを指定します。
{ "location": "file:///tmp/data/" }
機密性の高いステージデータの動きの追跡¶
時系列で実行される一連のクエリを移動するときに、機密性の高いステージデータを追跡します。
次のコマンドを実行します。5つのステートメントがステージデータにアクセスすることに注意してください。したがって、ステージアクセスの ACCESS_HISTORY ビューをクエリする場合、結果セットには5行が含まれている必要があります。
use test_db.test_schema; create or replace table T1(content variant); insert into T1(content) select parse_json('{"name": "A", "id":1}'); -- T1 -> T6 insert into T6 select * from T1; -- S1 -> T1 copy into T1 from @S1; -- T1 -> T2 create table T2 as select content:"name" as name, content:"id" as id from T1; -- T1 -> S2 copy into @S2 from T1; -- S1 -> T3 create or replace table T3(customer_info variant); copy into T3 from @S1; -- T1 -> T4 create or replace table T4(name string, id string, address string); insert into T4(name, id) select content:"name", content:"id" from T1; -- T6 -> T7 create table T7 as select * from T6;条件:
T1
、T2
...T7
は、テーブルの名前を指定します。
S1
およびS2
は、ステージの名前を指定します。
アクセス履歴をクエリし、ステージ S1
へのアクセスを決定します。
次のテーブルに、
direct_objects_accessed
、base_objects_accessed
、およびobjects_modified
列のデータを示します。
direct_objects_accessed
base_objects_accessed
objects_modified
[ { "columns": [ { "columnId": 68610, "columnName": "CONTENT" } ], "objectDomain": "Table", "objectId": 66564, "objectName": "TEST_DB.TEST_SCHEMA.T1" } ] [ { "columns": [ { "columnId": 68610, "columnName": "CONTENT" } ], "objectDomain": "Table", "objectId": 66564, "objectName": "TEST_DB.TEST_SCHEMA.T1" } ] [ { "columns": [ { "columnId": 68611, "columnName": "CONTENT" } ], "objectDomain": "Table", "objectId": 66566, "objectName": "TEST_DB.TEST_SCHEMA.T6" } ] [ { "objectDomain": "Stage", "objectId": 117, "objectName": "TEST_DB.TEST_SCHEMA.S1", "stageKind": "External Named" } ] [ { "objectDomain": "Stage", "objectId": 117, "objectName": "TEST_DB.TEST_SCHEMA.S1", "stageKind": "External Named" } ] [ { "columns": [ { "columnId": 68610, "columnName": "CONTENT" } ], "objectDomain": "Table", "objectId": 66564, "objectName": "TEST_DB.TEST_SCHEMA.T1" } ] [ { "columns": [ { "columnId": 68610, "columnName": "CONTENT" } ], "objectDomain": "Table", "objectId": 66564, "objectName": "TEST_DB.TEST_SCHEMA.T1" } ] [ { "columns": [ { "columnId": 68610, "columnName": "CONTENT" } ], "objectDomain": "Table", "objectId": 66564, "objectName": "TEST_DB.TEST_SCHEMA.T1" } ] [ { "columns": [ { "columnId": 68613, "columnName": "ID" }, { "columnId": 68612, "columnName": "NAME" } ], "objectDomain": "Table", "objectId": 66568, "objectName": "TEST_DB.TEST_SCHEMA.T2" } ] [ { "columns": [ { "columnId": 68610, "columnName": "CONTENT" } ], "objectDomain": "Table", "objectId": 66564, "objectName": "TEST_DB.TEST_SCHEMA.T1" } ] [ { "columns": [ { "columnId": 68610, "columnName": "CONTENT" } ], "objectDomain": "Table", "objectId": 66564, "objectName": "TEST_DB.TEST_SCHEMA.T1" } ] [ { "objectDomain": "Stage", "objectId": 118, "objectName": "TEST_DB.TEST_SCHEMA.S2", "stageKind": "External Named" } ] [ { "objectDomain": "Stage", "objectId": 117, "objectName": "TEST_DB.TEST_SCHEMA.S1", "stageKind": "External Named" } ] [ { "objectDomain": "Stage", "objectId": 117, "objectName": "TEST_DB.TEST_SCHEMA.S1", "stageKind": "External Named" } ] [ { "columns": [ { "columnId": 68614, "columnName": "CUSTOMER_INFO" } ], "objectDomain": "Table", "objectId": 66570, "objectName": "TEST_DB.TEST_SCHEMA.T3" } ] [ { "columns": [ { "columnId": 68610, "columnName": "CONTENT" } ], "objectDomain": "Table", "objectId": 66564, "objectName": "TEST_DB.TEST_SCHEMA.T1" } ] [ { "columns": [ { "columnId": 68610, "columnName": "CONTENT" } ], "objectDomain": "Table", "objectId": 66564, "objectName": "TEST_DB.TEST_SCHEMA.T1" } ] [ { "columns": [ { "columnId": 68615, "columnName": "NAME" }, { "columnId": 68616, "columnName": "ID" } ], "objectDomain": "Table", "objectId": 66572, "objectName": "TEST_DB.TEST_SCHEMA.T4" } ] [ { "columns": [ { "columnId": 68611, "columnName": "CONTENT" } ], "objectDomain": "Table", "objectId": 66566, "objectName": "TEST_DB.TEST_SCHEMA.T6" } ] [ { "columns": [ { "columnId": 68611, "columnName": "CONTENT" } ], "objectDomain": "Table", "objectId": 66566, "objectName": "TEST_DB.TEST_SCHEMA.T6" } ] [ { "columns": [ { "columnId": 68618, "columnName": "CONTENT" } ], "objectDomain": "Table", "objectId": 66574, "objectName": "TEST_DB.TEST_SCHEMA.T7" } ]クエリの例については、次の点に注意してください。
再帰共通テーブル式 を使用します。
with access_history_flatten as ( select r.value:"objectId" as source_id, r.value:"objectName" as source_name, r.value:"objectDomain" as source_domain, w.value:"objectId" as target_id, w.value:"objectName" as target_name, w.value:"objectDomain" as target_domain, c.value:"columnName" as target_column, t.query_start_time as query_start_time from (select * from TEST_DB.ACCOUNT_USAGE.ACCESS_HISTORY) t, lateral flatten(input => t.BASE_OBJECTS_ACCESSED) r, lateral flatten(input => t.OBJECTS_MODIFIED) w, lateral flatten(input => w.value:"columns", outer => true) c ), sensitive_data_movements(path, target_id, target_name, target_domain, target_column, query_start_time) as -- Common Table Expression ( -- Anchor Clause: Get the objects that access S1 directly select f.source_name || '-->' || f.target_name as path, f.target_id, f.target_name, f.target_domain, f.target_column, f.query_start_time from access_history_flatten f where f.source_domain = 'Stage' and f.source_name = 'TEST_DB.TEST_SCHEMA.S1' and f.query_start_time >= dateadd(day, -30, date_trunc(day, current_date)) union all -- Recursive Clause: Recursively get all the objects that access S1 indirectly select sensitive_data_movements.path || '-->' || f.target_name as path, f.target_id, f.target_name, f.target_domain, f.target_column, f.query_start_time from access_history_flatten f join sensitive_data_movements on f.source_id = sensitive_data_movements.target_id and f.source_domain = sensitive_data_movements.target_domain and f.query_start_time >= sensitive_data_movements.query_start_time ) select path, target_name, target_id, target_domain, array_agg(distinct target_column) as target_columns from sensitive_data_movements group by path, target_id, target_name, target_domain;クエリは、ステージ
S1
のデータ移動に関連する次の結果セットを生成します。
PATH
TARGET_NAME
TARGET_ID
TARGET_DOMAIN
TARGET_COLUMNS
TEST_DB.TEST_SCHEMA.S1-->TEST_DB.TEST_SCHEMA.T1
TEST_DB.TEST_SCHEMA.T1
66564
テーブル
["CONTENT"]
TEST_DB.TEST_SCHEMA.S1-->TEST_DB.TEST_SCHEMA.T1-->TEST_DB.TEST_SCHEMA.S2
TEST_DB.TEST_SCHEMA.S2
118
ステージ
[]
TEST_DB.TEST_SCHEMA.S1-->TEST_DB.TEST_SCHEMA.T1-->TEST_DB.TEST_SCHEMA.T2
TEST_DB.TEST_SCHEMA.T2
66568
テーブル
["NAME","ID"]
TEST_DB.TEST_SCHEMA.S1-->TEST_DB.TEST_SCHEMA.T1-->TEST_DB.TEST_SCHEMA.T4
TEST_DB.TEST_SCHEMA.T4
66572
テーブル
["ID","NAME"]
TEST_DB.TEST_SCHEMA.S1-->TEST_DB.TEST_SCHEMA.T3
TEST_DB.TEST_SCHEMA.T3
66570
テーブル
["CUSTOMER_INFO"]
例: 列の系列¶
次の例では、 ACCESS_HISTORY ビューをクエリし、 FLATTEN 関数を使用して objects_modified
列をフラット化します。
代表的な例として、Snowflakeアカウントで次の SQL クエリを実行して、以下のテーブルを生成します。ここで、番号付きのコメントは次を示しています。
// 1
:directSources
フィールドとターゲット列の間のマッピングを取得します。// 2
:baseSources
フィールドとターゲット列の間のマッピングを取得します。
// 1
select
directSources.value: "objectId" as source_object_id,
directSources.value: "objectName" as source_object_name,
directSources.value: "columnName" as source_column_name,
'DIRECT' as source_column_type,
om.value: "objectName" as target_object_name,
columns_modified.value: "columnName" as target_column_name
from
(
select
*
from
snowflake.account_usage.access_history
) t,
lateral flatten(input => t.OBJECTS_MODIFIED) om,
lateral flatten(input => om.value: "columns", outer => true) columns_modified,
lateral flatten(
input => columns_modified.value: "directSources",
outer => true
) directSources
union
// 2
select
baseSources.value: "objectId" as source_object_id,
baseSources.value: "objectName" as source_object_name,
baseSources.value: "columnName" as source_column_name,
'BASE' as source_column_type,
om.value: "objectName" as target_object_name,
columns_modified.value: "columnName" as target_column_name
from
(
select
*
from
snowflake.account_usage.access_history
) t,
lateral flatten(input => t.OBJECTS_MODIFIED) om,
lateral flatten(input => om.value: "columns", outer => true) columns_modified,
lateral flatten(
input => columns_modified.value: "baseSources",
outer => true
) baseSources
;
戻り値:
SOURCE_OBJECT_ID
SOURCE_OBJECT_NAME
SOURCE_COLUMN_NAME
SOURCE_COLUMN_TYPE
TARGET_OBJECT_NAME
TARGET_COLUMN_NAME
1
D.S.T0
NAME
BASE
D.S.T1
NAME
2
D.S.V1
NAME
DIRECT
D.S.T1
NAME
例: 行アクセスポリシーの参照を追跡する¶
テーブル、ビュー、またはマテリアライズドビューに行アクセスポリシーが重複することなく設定されている場合は、各インスタンスに対して行を返します。
use role accountadmin; select distinct obj_policy.value:"policyName"::VARCHAR as policy_name from snowflake.account_usage.access_history as ah , lateral flatten(ah.policies_referenced) as obj , lateral flatten(obj.value:"policies") as obj_policy ;
例: マスキングポリシーの参照を追跡する¶
マスキングポリシーによって列が重複することなく保護されている場合は、各インスタンスに対して行を返します。 policies_referenced
列は、テーブルの行アクセスポリシーよりも1レベル深い列のマスキングポリシーを指定するため、追加のフラット化が必要であることに注意してください。
use role accountadmin; select distinct policies.value:"policyName"::VARCHAR as policy_name from snowflake.account_usage.access_history as ah , lateral flatten(ah.policies_referenced) as obj , lateral flatten(obj.value:"columns") as columns , lateral flatten(columns.value:"policies") as policies ;
例: クエリで強制されたポリシーを追跡する¶
指定された時間枠内の指定されたクエリの、ポリシーが更新された時間(POLICY_CHANGED_TIME)とポリシー条件(POLICY_BODY)を返します。
このクエリを使用する前に、 WHERE 句の入力値を更新します。
where query_start_time > '2023-07-07' and
query_start_time < '2023-07-08' and
query_id = '01ad7987-0606-6e2c-0001-dd20f12a9777')
条件:
query_start_time > '2023-07-07'
開始タイムスタンプを指定します。
query_start_time < '2023-07-08'
終了タイムスタンプを指定します。
query_id = '01ad7987-0606-6e2c-0001-dd20f12a9777'
Account Usage ACCESS_HISTORY ビューにあるクエリ識別子を指定します。
クエリを実行します。
SELECT *
from(
select j1.*,j2.QUERY_START_TIME as POLICY_CHANGED_TIME, POLICY_BODY
from
(
select distinct t1.*,
t4.value:"policyId"::number as PID
from (select *
from SNOWFLAKE.ACCOUNT_USAGE.ACCESS_HISTORY
where query_start_time > '2023-07-07' and
query_start_time < '2023-07-08' and
query_id = '01ad7987-0606-6e2c-0001-dd20f12a9777') as t1, //
lateral flatten (input => t1.POLICIES_REFERENCED,OUTER => TRUE) t2,
lateral flatten (input => t2.value:"columns", OUTER => TRUE) t3,
lateral flatten (input => t3.value:"policies",OUTER => TRUE) t4
) as j1
left join
(
select OBJECT_MODIFIED_BY_DDL:"objectId"::number as PID,
QUERY_START_TIME,
OBJECT_MODIFIED_BY_DDL:"properties"."policyBody"."value" as POLICY_BODY
from SNOWFLAKE.ACCOUNT_USAGE.ACCESS_HISTORY
where OBJECT_MODIFIED_BY_DDL is not null and
(OBJECT_MODIFIED_BY_DDL:"objectDomain" ilike '%masking%' or OBJECT_MODIFIED_BY_DDL:"objectDomain" ilike '%row%')
) as j2
On j1.POLICIES_REFERENCED is not null and j1.pid = j2.pid and j1.QUERY_START_TIME>j2.QUERY_START_TIME) as j3
QUALIFY ROW_NUMBER() OVER (PARTITION BY query_id,pid ORDER BY policy_changed_time DESC) = 1;
例: UDFs¶
これらの UDF の例は、Account Usage ACCESS_HISTORY ビューが記録する方法を示しています。
get_product
という名前の UDF を 呼び出し ています。get_product
関数を呼び出した積をmydb.tables.t1
という名前のテーブルに 挿入 します。共有 UDFs。
UDF を呼び出す¶
2つの数値の積を計算する次の SQL UDF を検討し、それが mydb.udfs
という名前のスキーマに格納されていると仮定します。
CREATE FUNCTION MYDB.UDFS.GET_PRODUCT(num1 number, num2 number) RETURNS number AS $$ NUM1 * NUM2 $$ ;
get_product
を直接 呼び出す と、 UDF の詳細が direct_objects_accessed
列に記録されます。
[ { "objectDomain": "FUNCTION", "objectName": "MYDB.UDFS.GET_PRODUCT", "objectId": "2", "argumentSignature": "(NUM1 NUMBER, NUM2 NUMBER)", "dataType": "NUMBER(38,0)" } ]
この例は、 ストアドプロシージャの呼び出し (このトピック内)に類似しています。
INSERT DML を使用した UDF¶
mydb.tables.t1
という名前のテーブルの1および2という名前の列を更新するために、次の INSERT ステートメントを検討してください。
insert into t1(product) select get_product(c1, c2) from mydb.tables.t1;
ACCESS_HISTORY ビューは、 get_product
関数を次の場所に記録します。
direct_objects_accessed
列。これは、 SQL ステートメントで明示的に関数の名前が付けられているため。また、directSources
配列にあるobjects_modified
列。これは、関数が列に挿入される値のソースであるため。
同様に、テーブル t1
は次の同じ列に記録されます。
direct_objects_accessed
objects_modified
[ { "objectDomain": "FUNCTION", "objectName": "MYDB.UDFS.GET_PRODUCT", "objectId": "2", "argumentSignature": "(NUM1 NUMBER, NUM2 NUMBER)", "dataType": "NUMBER(38,0)" }, { "objectDomain": "TABLE", "objectName": "MYDB.TABLES.T1", "objectId": 1, "columns": [ { "columnName": "c1", "columnId": 1 }, { "columnName": "c2", "columnId": 2 } ] } ] [ { "objectDomain": "TABLE", "objectName": "MYDB.TABLES.T1", "objectId": 2, "columns": [ { "columnId": "product", "columnName": "201", "directSourceColumns": [ { "objectDomain": "Table", "objectName": "MYDB.TABLES.T1", "objectId": "1", "columnName": "c1" }, { "objectDomain": "Table", "objectName": "MYDB.TABLES.T1", "objectId": "1", "columnName": "c2" }, { "objectDomain": "FUNCTION", "objectName": "MYDB.UDFS.GET_PRODUCT", "objectId": "2", "argumentSignature": "(NUM1 NUMBER, NUM2 NUMBER)", "dataType": "NUMBER(38,0)" } ], "baseSourceColumns":[] } ] } ]
例: DDL 操作によって変更されたオブジェクトの追跡¶
ALLOWED_VALUES でタグを作成する¶
タグを作成します。
create tag governance.tags.pii allowed_values 'sensitive','public';
列の値:
{ "objectDomain": "TAG", "objectName": "governance.tags.pii", "objectId": "1", "operationType": "CREATE", "properties": { "allowedValues": { "sensitive": { "subOperationType": "ADD" }, "public": { "subOperationType": "ADD" } } } }
注釈
タグの作成時に許容値を指定しなかった場合、 properties
フィールドは空の配列になります(つまり、 {}
)。
タグおよびマスキングポリシーでテーブルを作成する¶
列にマスキングポリシー、列にタグ、テーブルにタグを付けてテーブルを作成します。
create or replace table hr.data.user_info( email string with masking policy governance.policies.email_mask with tag (governance.tags.pii = 'sensitive') ) with tag (governance.tags.pii = 'sensitive');
列の値:
{ "objectDomain": "TABLE", "objectName": "hr.data.user_info", "objectId": "1", "operationType": "CREATE", "properties": { "tags": { "governance.tags.pii": { "subOperationType": "ADD", "objectId": { "value": "1" }, "tagValue": { "value": "sensitive" } } }, "columns": { "email": { objectId: { "value": 1 }, "subOperationType": "ADD", "tags": { "governance.tags.pii": { "subOperationType": "ADD", "objectId": { "value": "1" }, "tagValue": { "value": "sensitive" } } }, "maskingPolicies": { "governance.policies.email_mask": { "subOperationType": "ADD", "objectId": { "value": 2 } } } } } } }
タグにマスキングポリシーを設定する¶
タグにマスキングポリシーを設定します(例: タグベースのマスキング)。
alter tag governance.tags.pii set masking policy governance.policies.email_mask;
列の値:
{ "objectDomain": "TAG", "objectName": "governance.tags.pii", "objectId": "1", "operationType": "ALTER", "properties": { "maskingPolicies": { "governance.policies.email_mask": { "subOperationType": "ADD", "objectId": { "value": 2 } } } } }
テーブルを入れ替える¶
t2
という名前のテーブルを t3
という名前のテーブルと入れ替えます。
alter table governance.tables.t2 swap with governance.tables.t3;
ビューの2つの異なる記録に注目してください。
記録1:
{ "objectDomain": "Table", "objectId": 0, "objectName": "GOVERNANCE.TABLES.T2", "operationType": "ALTER", "properties": { "swapTargetDomain": { "value": "Table" }, "swapTargetId": { "value": 0 }, "swapTargetName": { "value": "GOVERNANCE.TABLES.T3" } } }
記録2:
{ "objectDomain": "Table", "objectId": 0, "objectName": "GOVERNANCE.TABLES.T3", "operationType": "ALTER", "properties": { "swapTargetDomain": { "value": "Table" }, "swapTargetId": { "value": 0 }, "swapTargetName": { "value": "GOVERNANCE.TABLES.T2" } } }
マスキングポリシーをドロップする¶
マスキングポリシーをドロップします。
drop masking policy governance.policies.email_mask;
列の値:
{ "objectDomain" : "MASKING_POLICY", "objectName": "governance.policies.email_mask", "objectId" : "1", "operationType": "DROP", "properties" : {} }注釈
列の値は代表的なもので、タグと行のアクセスポリシーに対する DROP 操作に適用されます。
properties
フィールドは空の配列で、 DROP 操作前のポリシーに関する情報は提供されません。
列のタグ参照を追跡する¶
タグがどのように列に設定されているかをモニターするために、 object_modified_by_ddl
列をクエリします。
テーブル管理者として、列にタグを設定し、タグの設定を解除し、異なる文字列値でタグを更新します。
alter table hr.tables.empl_info alter column email set tag governance.tags.test_tag = 'test'; alter table hr.tables.empl_info alter column email unset tag governance.tags.test_tag; alter table hr.tables.empl_info alter column email set tag governance.tags.data_category = 'sensitive';
データエンジニアとして、タグの値を変更します。
alter table hr.tables.empl_info alter column email set tag governance.tags.data_category = 'public';
ACCESS_HISTORY ビューをクエリして変更をモニターします。
select query_start_time, user_name, object_modified_by_ddl:"objectName"::string as table_name, 'EMAIL' as column_name, tag_history.value:"subOperationType"::string as operation, tag_history.key as tag_name, nvl((tag_history.value:"tagValue"."value")::string, '') as value from TEST_DB.ACCOUNT_USAGE.access_history ah, lateral flatten(input => ah.OBJECT_MODIFIED_BY_DDL:"properties"."columns"."EMAIL"."tags") tag_history where true and object_modified_by_ddl:"objectDomain" = 'Table' and object_modified_by_ddl:"objectName" = 'TEST_DB.TEST_SH.T' order by query_start_time asc;
戻り値:
+-----------------------------------+---------------+---------------------+-------------+-----------+-------------------------------+-----------+ | QUERY_START_TIME | USER_NAME | TABLE_NAME | COLUMN_NAME | OPERATION | TAG_NAME | VALUE | +-----------------------------------+---------------+---------------------+-------------+-----------+-------------------------------+-----------+ | Mon, Feb. 14, 2023 12:01:01 -0600 | TABLE_ADMIN | HR.TABLES.EMPL_INFO | EMAIL | ADD | GOVERNANCE.TAGS.TEST_TAG | test | | Mon, Feb. 14, 2023 12:02:01 -0600 | TABLE_ADMIN | HR.TABLES.EMPL_INFO | EMAIL | DROP | GOVERNANCE.TAGS.TEST_TAG | | | Mon, Feb. 14, 2023 12:03:01 -0600 | TABLE_ADMIN | HR.TABLES.EMPL_INFO | EMAIL | ADD | GOVERNANCE.TAGS.DATA_CATEGORY | sensitive | | Mon, Feb. 14, 2023 12:04:01 -0600 | DATA_ENGINEER | HR.TABLES.EMPL_INFO | EMAIL | ADD | GOVERNANCE.TAGS.DATA_CATEGORY | public | +-----------------------------------+---------------+---------------------+-------------+-----------+-------------------------------+-----------+
例: ストアドプロシージャを呼び出す¶
次のストアドプロシージャを検討し、 mydb.procedures
という名前のスキーマに格納されていると仮定します。
create or replace procedure get_id_value(name string) returns string not null language javascript as $$ var my_sql_command = "select id from A where name = '" + NAME + "'"; var statement = snowflake.createStatement( {sqlText: my_sql_command} ); var result = statement.execute(); result.next(); return result.getColumnValue(1); $$ ;
my_procedure
を直接 呼び出す と、次のように direct_objects_accessed
列と base_objects_accessed
列の両方にプロシージャの詳細が記録されます。
[ { "objectDomain": "PROCEDURE", "objectName": "MYDB.PROCEDURES.GET_ID_VALUE", "argumentSignature": "(NAME STRING)", "dataType": "STRING" } ]
この例は、 UDF の呼び出し (このトピック内)に類似しています。
例: ストアドプロシージャによる祖先クエリ¶
parent_query_id
列と root_query_id
列を使用して、ストアドプロシージャの呼び出しが互いにどのように関連しているかを理解することができます。
3つの異なるストアドプロシージャステートメントがあり、それらを次の順序で実行したとします。
CREATE OR REPLACE PROCEDURE myproc_child() RETURNS INTEGER LANGUAGE SQL AS $$ BEGIN SELECT * FROM mydb.mysch.mytable; RETURN 1; END $$; CREATE OR REPLACE PROCEDURE myproc_parent() RETURNS INTEGER LANGUAGE SQL AS $$ BEGIN CALL myproc_child(); RETURN 1; END $$; CALL myproc_parent();
ACCESS_HISTORY ビューのクエリは、以下のように情報を記録します。
SELECT query_id, parent_query_id, root_query_id, direct_objects_accessed FROM SNOWFLAKE.ACCOUNT_USAGE.ACCESS_HISTORY;+----------+-----------------+---------------+-----------------------------------+ | QUERY_ID | PARENT_QUERY_ID | ROOT_QUERY_ID | DIRECT_OBJECTS_ACCESSED | +----------+-----------------+---------------+-----------------------------------+ | 1 | NULL | NULL | [{"objectName": "myproc_parent"}] | | 2 | 1 | 1 | [{"objectName": "myproc_child"}] | | 3 | 2 | 1 | [{"objectName": "mytable"}] | +----------+-----------------+---------------+-----------------------------------+
最初の行は、
direct_objects_accessed
列に示されているように、myproc_parent
という2番目のプロシージャを呼び出すことに対応しています。このストアドプロシージャを直接呼び出したため、
parent_query_id
とroot_query_id
列は NULL を返します。2行目は、
direct_objects_accessed column
に示されているように、myproc_child
という最初のプロシージャを呼び出すクエリに対応しています。parent_query_id
列とroot_query_id
列が同じクエリ ID を返すのは、myproc_child
を呼び出すクエリが、直接呼びだしたmyproc_parent
を呼び出すクエリによって開始されたためです。3行目は、
direct_objects_accessed
列に示されているように、myproc_child
プロシージャでmytable
という名前のテーブルにアクセスしたクエリに対応しています。parent_query_id
列は、mytable
にアクセスしたクエリのクエリ ID を返し、これはmyproc_child
の呼び出しに対応します。このストアドプロシージャは、myproc_parent
を呼び出すクエリによって開始されました。root_query_id
列に示されています。
例: シーケンス¶
シーケンスを作成する次の SQL ステートメントを考慮します。
CREATE SEQUENCE SEQ
START = 2
INCREMENT = 7
COMMENT = 'Comment on sequence';
このシーケンスを作成すると、アクセス履歴に以下のようなエントリが作成されます。
{
"objectDomain": "Sequence",
"objectId": 1,
"objectName": "TEST_DB.TEST_SCHEMA.SEQ",
"operationType": "CREATE",
"properties": {
"start": {
"value": "2"
},
"increment": {
"value": "7"
},
"comment": {
"value": "Comment on Sequence"
}
}
}
例: 結合¶
クエリ内の結合は、 direct_accessed_objects
列の joinObject
としてアクセス履歴に表示されます。アクセス履歴はクエリに明示的に記述された結合のみを追跡するため、 joinObject
は他の列には表示されません。
例えば、テーブル t1
とテーブル t2
を結合する以下のクエリを考えてみます。
CREATE OR REPLACE VIEW v1 (vc1, vc2) AS
SELECT
t1.c1 AS vc1,
t2.c2 AS vc2
FROM t1 LEFT OUTER JOIN t2
ON t1.c2 = t2.c1;
このクエリを実行すると、 direct_accessed_objects
列に t1
オブジェクトが以下のように表示されます。
{
"columns": [
{
"columnId": 0,
"columnName": "C1"
},
{
"columnId": 0,
"columnName": "C2"
}
],
"joinObjects": [
{
"joinType": "LEFT_OUTER_JOIN",
"node": {
"objectDomain": "Table",
"objectId": 0,
"objectName": "DB1.SCH.T2"
}
}
],
"objectDomain": "Table",
"objectId": 0,
"objectName": "DB1.SCH.T1"
}
注釈
この例では、アクセス履歴には t2
オブジェクトの joinObject
は含まれません。テーブル t1
の joinObject
が提供する情報と重複してしまうからです。