
Snowflake CoWorkで自然言語問い合わせできる簡易データカタログを作ってみた
かわばたです。
「このデータは何?」「オーナーは誰?」「誰がアクセスできる?」「どこから作られた?」という問い合わせがデータ管理者に集中して困るケースは多いのではないでしょうか。市販のデータカタログ製品を導入する前に、Snowflake の標準機能だけでどこまでできるかも気になるところです。
本記事では、Semantic View + Cortex Search + Cortex Agent + Snowflake CoWork(旧 Snowflake Intelligence。以下 CoWork)で日本語で問い合わせられるデータカタログを構築し、Row Access Policy でカタログ上の「データの存在自体」をロール別に隠せるかを検証します。
概要
背景
データ基盤の運用では、以下のようなニーズが増えています。
- データの所在・オーナー・アクセス権・リネージの問い合わせをセルフサービス化したい
- dbt docs 等の標準カタログではタグ・マスキングなどのガバナンス情報を俯瞰できない
- 市販データカタログ製品の導入・運用コストを抑えたい
- カタログ自体を「存在を知られたくないデータ」の漏えい経路にしたくない
カタログ検索で役員報酬テーブルの存在・オーナー・件数が誰にでも見えてしまうなら、カタログがガバナンスの穴になります。
データカタログに求める機能と実現手段
今回は一般的なデータカタログの主要機能を、次の対応で Snowflake 標準機能に割り当てます。
| カタログ機能 | 実現手段 |
|---|---|
| データ資産・カラムの発見 | INFORMATION_SCHEMA を収集したカタログテーブル + Cortex Search |
| データオーナー | 技術オーナー(所有ロール)は SHOW TABLES の owner 列から自動収集。ビジネスオーナー・スチュワードは Snowflake 上に情報が無いため、カタログ側の台帳テーブルに登録 |
| アクセス権の可視化 | SHOW GRANTS を収集しロール階層を再帰展開した実効アクセス |
| リネージ | SNOWFLAKE.CORE.GET_LINEAGE |
| タグ・分類・マスキング状況 | TAG_REFERENCES / POLICY_REFERENCES テーブル関数 |
| ビジネス用語集・品質・鮮度・申請導線 | カタログテーブルとして保持 |
| 表示対象の制御(存在隠蔽) | カタログテーブルへの Row Access Policy |
| 自然言語インターフェース | Semantic View + Cortex Agent + CoWork |
基本概念
| 用語 | 説明 |
|---|---|
| Snowflake CoWork | 旧 Snowflake Intelligence。Snowsight 上でエージェントと対話できる日本語チャット UI |
| Cortex Agent | 複数ツール(Cortex Analyst / Cortex Search 等)をオーケストレーションするエージェントオブジェクト |
| Cortex Analyst | Semantic View を参照して自然言語から SQL を生成・実行する機能 |
| Semantic View | テーブルに論理名・シノニム・メトリクス・リレーションを定義するセマンティックレイヤー |
| Cortex Search | 埋め込みモデルによるハイブリッド検索サービス。あいまいなキーワード探索に使う |
| Row Access Policy | 行単位のアクセス制御ポリシー。本記事ではカタログのメタデータ行に適用する |
全体像
検証環境
| 項目 | 内容 |
|---|---|
| クラウド / リージョン | AWS / AWS_AP_NORTHEAST_1 |
| エディション | Enterprise Edition |
| 実行ロール | ACCOUNTADMIN |
| デモデータ用DB | CATDEMO_RETAIL_DB(架空小売「クラメソマート」・全データ架空) |
| カタログ用DB | CATDEMO_CATALOG_DB |
| 検証日 | 2026年8月30日 |
デモロールは3種類です。各ロールに閲覧レベル(低・中・高)を割り当て、データ資産側にも「どのレベル以上なら見せてよいか」を設定します。「ロールの閲覧レベル ≧ データ資産に必要なレベル」のときだけ、そのデータ資産がカタログに表示される仕組みです。実装上は低・中・高を 20/30/40 の数値で持ち、Row Access Policy が大小比較で判定します。
| ロール | 閲覧レベル | カタログの見え方 |
|---|---|---|
| CATDEMO_SALES_ANALYST | 低 | 販売・顧客・分析のみ。人事・財務は存在ごと不可視 |
| CATDEMO_DATA_STEWARD | 中 | 上記+人事・財務。役員報酬テーブルは不可視 |
| CATDEMO_CATALOG_ADMIN | 高 | 全データ資産 |
事前準備
ロール・ウェアハウス・データベースの作成
ロール・ウェアハウス・データベースの作成
USE ROLE ACCOUNTADMIN;
CREATE ROLE IF NOT EXISTS CATDEMO_SALES_ANALYST;
CREATE ROLE IF NOT EXISTS CATDEMO_DATA_STEWARD;
CREATE ROLE IF NOT EXISTS CATDEMO_CATALOG_ADMIN;
GRANT ROLE CATDEMO_SALES_ANALYST TO ROLE SYSADMIN;
GRANT ROLE CATDEMO_DATA_STEWARD TO ROLE SYSADMIN;
GRANT ROLE CATDEMO_CATALOG_ADMIN TO ROLE SYSADMIN;
-- デモ用に3ロールとも検証ユーザーへ付与
GRANT ROLE CATDEMO_SALES_ANALYST TO USER <検証ユーザー>;
GRANT ROLE CATDEMO_DATA_STEWARD TO USER <検証ユーザー>;
GRANT ROLE CATDEMO_CATALOG_ADMIN TO USER <検証ユーザー>;
CREATE WAREHOUSE IF NOT EXISTS CATDEMO_WH
WAREHOUSE_SIZE = XSMALL AUTO_SUSPEND = 60 AUTO_RESUME = TRUE INITIALLY_SUSPENDED = TRUE;
GRANT USAGE, OPERATE ON WAREHOUSE CATDEMO_WH TO ROLE CATDEMO_SALES_ANALYST;
GRANT USAGE, OPERATE ON WAREHOUSE CATDEMO_WH TO ROLE CATDEMO_DATA_STEWARD;
GRANT USAGE, OPERATE ON WAREHOUSE CATDEMO_WH TO ROLE CATDEMO_CATALOG_ADMIN;
CREATE DATABASE IF NOT EXISTS CATDEMO_RETAIL_DB;
CREATE DATABASE IF NOT EXISTS CATDEMO_CATALOG_DB;
-- Cortex 利用に必要な DB ロール
GRANT DATABASE ROLE SNOWFLAKE.CORTEX_USER TO ROLE CATDEMO_SALES_ANALYST;
GRANT DATABASE ROLE SNOWFLAKE.CORTEX_USER TO ROLE CATDEMO_DATA_STEWARD;
GRANT DATABASE ROLE SNOWFLAKE.CORTEX_USER TO ROLE CATDEMO_CATALOG_ADMIN;
架空データとタグベースマスキング
架空小売企業の業務データを5スキーマ・12テーブルで作成します。全データは GENERATOR による架空データです。
タグベースマスキング
USE DATABASE CATDEMO_RETAIL_DB;
CREATE SCHEMA IF NOT EXISTS SALES COMMENT = '販売ドメイン: 店舗・商品・注文';
CREATE SCHEMA IF NOT EXISTS CRM COMMENT = '顧客ドメイン: 会員情報(PIIを含む)';
CREATE SCHEMA IF NOT EXISTS HR COMMENT = '人事ドメイン: 従業員・給与(機密)';
CREATE SCHEMA IF NOT EXISTS FINANCE COMMENT = '財務ドメイン: 決済・役員報酬';
CREATE SCHEMA IF NOT EXISTS ANALYTICS COMMENT = '分析ドメイン: 集計マート';
CREATE SCHEMA IF NOT EXISTS ADMIN COMMENT = 'ガバナンス用: タグ・マスキングポリシー';
分類タグと機密度タグを作成し、PII 列のマスキングをタグ連動にします。IS_AGENT_ACTIVATED を組み込み、同一ロールでも直接SQLとエージェント経由で見え方を変えます。
CREATE TAG IF NOT EXISTS ADMIN.DATA_CLASS
ALLOWED_VALUES 'PII', 'CONFIDENTIAL', 'INTERNAL', 'PUBLIC';
CREATE TAG IF NOT EXISTS ADMIN.SENSITIVITY
ALLOWED_VALUES 'PUBLIC', 'INTERNAL', 'CONFIDENTIAL', 'RESTRICTED';
CREATE OR REPLACE MASKING POLICY ADMIN.MASK_PII_STRING AS (val STRING) RETURNS STRING ->
CASE
WHEN COALESCE(TRY_CAST(SYS_CONTEXT('SNOWFLAKE$CURRENT','IS_AGENT_ACTIVATED') AS BOOLEAN), FALSE)
THEN '***MASKED(AGENT)***'
WHEN CURRENT_ROLE() IN ('ACCOUNTADMIN','CATDEMO_DATA_STEWARD','CATDEMO_CATALOG_ADMIN')
THEN val
ELSE '***MASKED***'
END;
CREATE OR REPLACE MASKING POLICY ADMIN.MASK_PII_DATE AS (val DATE) RETURNS DATE ->
CASE
WHEN COALESCE(TRY_CAST(SYS_CONTEXT('SNOWFLAKE$CURRENT','IS_AGENT_ACTIVATED') AS BOOLEAN), FALSE)
THEN NULL
WHEN CURRENT_ROLE() IN ('ACCOUNTADMIN','CATDEMO_DATA_STEWARD','CATDEMO_CATALOG_ADMIN')
THEN val
ELSE NULL
END;
ALTER TAG ADMIN.DATA_CLASS SET MASKING POLICY ADMIN.MASK_PII_STRING;
ALTER TAG ADMIN.DATA_CLASS SET MASKING POLICY ADMIN.MASK_PII_DATE;
IS_AGENT_ACTIVATED の詳細は以前の検証記事を参照してください。
PII を含む会員マスタを作成し、タグを付与します。
タグ付与
CREATE OR REPLACE TABLE CRM.CUSTOMERS (
CUSTOMER_ID NUMBER COMMENT '顧客ID',
CUSTOMER_NAME VARCHAR COMMENT '氏名(PII)',
EMAIL VARCHAR COMMENT 'メールアドレス(PII)',
PHONE_NUMBER VARCHAR COMMENT '電話番号(PII)',
BIRTH_DATE DATE COMMENT '生年月日(PII)',
PREF VARCHAR COMMENT '居住都道府県',
MEMBER_RANK VARCHAR COMMENT '会員ランク(BRONZE/SILVER/GOLD)',
REGISTERED_AT DATE COMMENT '会員登録日'
) COMMENT = '会員マスタ。会員登録済みで顧客IDを持つ個人。PII列はタグベースマスキング適用';
INSERT INTO CRM.CUSTOMERS
SELECT S + 1,
'デモ顧客' || LPAD(S+1, 4, '0'),
'demo_user' || LPAD(S+1, 4, '0') || '@example.com',
'090-0000-' || LPAD(MOD(S, 10000), 4, '0'),
DATEADD(day, -1 * UNIFORM(6600, 25000, RANDOM()), CURRENT_DATE()),
DECODE(MOD(S,5), 0,'東京都',1,'神奈川県',2,'大阪府',3,'愛知県',4,'福岡県'),
DECODE(MOD(S,6), 0,'GOLD',1,'SILVER',2,'SILVER',3,'BRONZE',4,'BRONZE',5,'BRONZE'),
DATEADD(day, -1 * UNIFORM(10, 2000, RANDOM()), CURRENT_DATE())
FROM (SELECT SEQ4() AS S FROM TABLE(GENERATOR(ROWCOUNT => 200)));
ALTER TABLE CRM.CUSTOMERS MODIFY COLUMN CUSTOMER_NAME SET TAG ADMIN.DATA_CLASS = 'PII';
ALTER TABLE CRM.CUSTOMERS MODIFY COLUMN EMAIL SET TAG ADMIN.DATA_CLASS = 'PII';
ALTER TABLE CRM.CUSTOMERS MODIFY COLUMN PHONE_NUMBER SET TAG ADMIN.DATA_CLASS = 'PII';
ALTER TABLE CRM.CUSTOMERS MODIFY COLUMN BIRTH_DATE SET TAG ADMIN.DATA_CLASS = 'PII';
ALTER TABLE CRM.CUSTOMERS SET TAG ADMIN.SENSITIVITY = 'CONFIDENTIAL';
存在隠蔽のデモ対象になる機密テーブルも作成します。
CREATE OR REPLACE TABLE HR.EMPLOYEE_SALARY (
EMP_ID NUMBER COMMENT '従業員ID(HR.EMPLOYEES への参照)',
FISCAL_YEAR NUMBER COMMENT '会計年度',
ANNUAL_SALARY NUMBER COMMENT '年収(円・機密)',
GRADE VARCHAR COMMENT '等級'
) COMMENT = '従業員給与。人事部のみ扱う機密データ。一般アナリストには存在自体を非公開';
ALTER TABLE HR.EMPLOYEE_SALARY SET TAG ADMIN.SENSITIVITY = 'CONFIDENTIAL';
CREATE OR REPLACE TABLE FINANCE.EXEC_COMPENSATION (
EXEC_ID NUMBER COMMENT '役員ID',
EXEC_NAME VARCHAR COMMENT '役員氏名(PII)',
FISCAL_YEAR NUMBER COMMENT '会計年度',
TOTAL_COMP NUMBER COMMENT '報酬総額(円・最高機密)'
) COMMENT = '役員報酬。最高機密。カタログ上も管理者以外には存在自体を非公開';
ALTER TABLE FINANCE.EXEC_COMPENSATION MODIFY COLUMN EXEC_NAME SET TAG ADMIN.DATA_CLASS = 'PII';
ALTER TABLE FINANCE.EXEC_COMPENSATION SET TAG ADMIN.SENSITIVITY = 'RESTRICTED';
残りのテーブル(SALES.STORES / PRODUCTS / ORDERS / ORDER_ITEMS、HR.EMPLOYEES、FINANCE.PAYMENTS)も同様に、日本語 COMMENT 付き + GENERATOR のデータで作成します(計約4,600行)。
分析マートは CTAS で作成します。これが後述の GET_LINEAGE の収集源になります。
テーブル作成
CREATE OR REPLACE TABLE ANALYTICS.DAILY_SALES
COMMENT = '日次売上集計(認定済み)。ORDERS/ORDER_ITEMS/PRODUCTS から日次バッチで生成'
AS
SELECT DATE_TRUNC('day', o.ORDER_TS)::DATE AS SALES_DATE,
o.STORE_ID,
p.CATEGORY,
SUM(oi.AMOUNT) AS SALES_AMOUNT,
COUNT(DISTINCT o.ORDER_ID) AS ORDER_COUNT
FROM SALES.ORDERS o
JOIN SALES.ORDER_ITEMS oi ON o.ORDER_ID = oi.ORDER_ID
JOIN SALES.PRODUCTS p ON oi.PRODUCT_ID = p.PRODUCT_ID
WHERE o.STATUS = 'COMPLETED'
GROUP BY 1, 2, 3;
同様に、ANALYTICS.CUSTOMER_LTV と、廃止予定のデモ用である ANALYTICS.DAILY_SALES_LEGACY も CTAS で作成しました。実データへの SELECT 権限は、ANALYST に SALES/CRM/ANALYTICS、STEWARD にさらに HR と FINANCE.PAYMENTS、ADMIN に全テーブルを付与しています。
カタログマートの構築
テーブル設計
CATDEMO_CATALOG_DB に5スキーマ(CORE / GOVERNANCE / SEMANTIC / SEARCH / AGENT)を作り、CORE に19テーブルを定義します。データ資産に紐づく主要テーブルには共通の結合キー FULL_NAME(DB.SCHEMA.OBJECT)を持たせ、後で共通の Row Access Policy を適用するのがポイントです。リネージは SOURCE_FULL_NAME と TARGET_FULL_NAME の両端で判定し、ロール管理・収集履歴・用語集などの非データ資産系テーブルは別キーで管理します。
| テーブル | 内容 |
|---|---|
| CATALOG_SCOPE | 収集対象DBの制御。ここに無いDBは収集されない |
| ASSETS | データ資産の一覧(説明・ドメイン・機密度・信頼度・ライフサイクル・鮮度・利用統計) |
| COLUMNS_META | カラムメタデータ(型・説明・分類・PII・マスキングポリシー・サンプル値) |
| OWNERS | オーナー(TECHNICAL / BUSINESS / STEWARD) |
| TAG_ASSIGNMENTS | タグ付与状況 |
| GRANTS_EDGES / ROLE_EDGES / ROLE_USERS | 権限付与・ロール階層・ユーザー付与のエッジ |
| EFFECTIVE_ACCESS | ロール階層を展開した実効アクセス(権限経路) |
| LINEAGE_EDGES | リネージ(取得方法・取得日時・信頼度つき) |
| MANAGED_OBJECTS | ウェアハウス・ロール・タグ・ポリシーの一覧 |
| ASSET_VISIBILITY / ROLE_CLEARANCE | データ資産ごとの必要閲覧レベルと、ロールごとの閲覧レベル(存在隠蔽の駆動テーブル) |
| COLLECTION_RUNS / COLLECTION_OBJECT_STATUS | 収集の実行履歴と対象別結果(回答の鮮度の根拠) |
| BUSINESS_GLOSSARY / ASSET_TERM_RELATIONS | ビジネス用語集とデータ資産の関連 |
| DATA_HEALTH | 鮮度・品質 |
| ACCESS_REQUEST_GUIDE | アクセス申請の導線(申請方法・承認者・リードタイム) |
主要テーブルの DDL は以下です。
DDL
USE DATABASE CATDEMO_CATALOG_DB;
CREATE SCHEMA IF NOT EXISTS CORE;
CREATE SCHEMA IF NOT EXISTS GOVERNANCE;
CREATE SCHEMA IF NOT EXISTS SEMANTIC;
CREATE SCHEMA IF NOT EXISTS SEARCH;
CREATE SCHEMA IF NOT EXISTS AGENT;
CREATE TABLE IF NOT EXISTS CORE.ASSETS (
FULL_NAME VARCHAR COMMENT 'アセット完全名 DB.SCHEMA.OBJECT',
ASSET_TYPE VARCHAR COMMENT 'TABLE/VIEW',
DATABASE_NAME VARCHAR,
SCHEMA_NAME VARCHAR,
OBJECT_NAME VARCHAR,
DESCRIPTION VARCHAR COMMENT '説明(COMMENT 由来)',
DOMAIN VARCHAR COMMENT '業務ドメイン',
SENSITIVITY VARCHAR COMMENT '機密度(SENSITIVITY タグ由来)',
ROW_COUNT NUMBER,
BYTES NUMBER,
CREATED_AT TIMESTAMP_LTZ,
LAST_ALTERED TIMESTAMP_LTZ,
TRUST_LEVEL VARCHAR COMMENT 'CERTIFIED/REVIEWED/UNVERIFIED',
LIFECYCLE_STATUS VARCHAR COMMENT 'DRAFT/ACTIVE/DEPRECATED/RETIRED',
IS_RECOMMENDED BOOLEAN,
REPLACED_BY VARCHAR COMMENT '置き換え先アセットの完全名',
DEPRECATION_DATE DATE,
USAGE_GUIDANCE VARCHAR,
ACCESS_REQUESTABLE BOOLEAN,
LAST_COLLECTED_AT TIMESTAMP_LTZ COMMENT 'カタログ収集日時',
FRESHNESS_STATUS VARCHAR COMMENT 'FRESH/STALE/UNKNOWN',
QUERY_COUNT_30D NUMBER,
DISTINCT_USERS_30D NUMBER,
USAGE_SOURCE VARCHAR COMMENT '利用統計の取得元(ACCOUNT_USAGE/SEEDED)'
);
CREATE TABLE IF NOT EXISTS CORE.COLUMNS_META (
FULL_NAME VARCHAR,
COLUMN_NAME VARCHAR,
ORDINAL NUMBER,
DATA_TYPE VARCHAR,
DESCRIPTION VARCHAR,
DATA_CLASS VARCHAR COMMENT 'DATA_CLASS タグ由来',
IS_PII BOOLEAN,
MASKING_POLICY VARCHAR COMMENT '適用中のマスキングポリシー名',
SAMPLE_VALUES VARCHAR COMMENT 'サンプル値。PII/機密列は収集対象外(生値を保管しない)'
);
CREATE TABLE IF NOT EXISTS CORE.ASSET_VISIBILITY (
FULL_NAME VARCHAR,
VISIBILITY_LEVEL NUMBER COMMENT '必要クリアランス 10/20/30/40',
VISIBILITY_REASON_CODE VARCHAR COMMENT 'PUBLIC_CATALOG/INTERNAL/CONFIDENTIAL/RESTRICTED',
HIDE_EXISTENCE_FLG BOOLEAN COMMENT 'TRUE: レベル未満のロールには存在自体を隠す'
) COMMENT = '未登録アセットは fail-closed(誰にも見えない)。管理者のみ参照可';
CREATE TABLE IF NOT EXISTS CORE.ROLE_CLEARANCE (
ROLE_NAME VARCHAR,
CLEARANCE_LEVEL NUMBER
) COMMENT = 'ロール→クリアランスのマッピング。管理者のみ参照可';
CREATE TABLE IF NOT EXISTS CORE.COLLECTION_RUNS (
RUN_ID VARCHAR,
STARTED_AT TIMESTAMP_LTZ,
FINISHED_AT TIMESTAMP_LTZ,
STATUS VARCHAR COMMENT 'SUCCESS/PARTIAL_SUCCESS/FAILED/RUNNING',
REQUESTED_SCOPE VARCHAR,
ERROR_SUMMARY VARCHAR
) COMMENT = 'カタログ収集の実行履歴。回答の鮮度の根拠';
メタデータ収集は低遅延なソースを優先する
ACCOUNT_USAGE のビューは反映まで最大3時間の遅延があります。作成直後のオブジェクトを早くカタログに映すため、ACCOUNT_USAGE だけに依存せず、次の低遅延なソースを優先して収集します。
| 収集対象 | ソース |
|---|---|
| データ資産・カラム | INFORMATION_SCHEMA.TABLES / COLUMNS |
| タグ付与状況 | TAG_REFERENCES_ALL_COLUMNS テーブル関数 |
| マスキング適用状況 | POLICY_REFERENCES テーブル関数 |
| 権限・ロール階層 | SHOW GRANTS → RESULT_SCAN |
| リネージ | SNOWFLAKE.CORE.GET_LINEAGE |
| 技術オーナー | SHOW TABLES の owner |
収集スクリプト
収集は DELETE→INSERT にし、実行履歴を COLLECTION_RUNS に記録します。
データ資産・カラムは INFORMATION_SCHEMA からの INSERT SELECT です。
収集スクリプト
SET RUN_ID = (SELECT UUID_STRING());
MERGE INTO CORE.CATALOG_SCOPE s USING (SELECT 'CATDEMO_RETAIL_DB' AS D) x
ON s.DATABASE_NAME = x.D
WHEN NOT MATCHED THEN INSERT (DATABASE_NAME) VALUES (x.D);
INSERT INTO CORE.COLLECTION_RUNS (RUN_ID, STARTED_AT, STATUS, REQUESTED_SCOPE)
SELECT $RUN_ID, CURRENT_TIMESTAMP(), 'RUNNING', LISTAGG(DATABASE_NAME, ',') FROM CORE.CATALOG_SCOPE;
DELETE FROM CORE.ASSETS;
INSERT INTO CORE.ASSETS
(FULL_NAME, ASSET_TYPE, DATABASE_NAME, SCHEMA_NAME, OBJECT_NAME, DESCRIPTION, DOMAIN,
ROW_COUNT, BYTES, CREATED_AT, LAST_ALTERED, TRUST_LEVEL, LIFECYCLE_STATUS, IS_RECOMMENDED,
ACCESS_REQUESTABLE, LAST_COLLECTED_AT, FRESHNESS_STATUS, USAGE_SOURCE)
SELECT t.TABLE_CATALOG || '.' || t.TABLE_SCHEMA || '.' || t.TABLE_NAME,
IFF(t.TABLE_TYPE = 'VIEW', 'VIEW', 'TABLE'),
t.TABLE_CATALOG, t.TABLE_SCHEMA, t.TABLE_NAME,
t.COMMENT,
CASE t.TABLE_SCHEMA
WHEN 'SALES' THEN '販売' WHEN 'CRM' THEN '顧客' WHEN 'HR' THEN '人事'
WHEN 'FINANCE' THEN '財務' WHEN 'ANALYTICS' THEN '分析' ELSE 'その他' END,
t.ROW_COUNT, t.BYTES, t.CREATED, t.LAST_ALTERED,
'UNVERIFIED', 'ACTIVE', FALSE, TRUE, CURRENT_TIMESTAMP(),
IFF(t.LAST_ALTERED >= DATEADD(day, -7, CURRENT_TIMESTAMP()), 'FRESH', 'STALE'),
NULL
FROM CATDEMO_RETAIL_DB.INFORMATION_SCHEMA.TABLES t
WHERE t.TABLE_CATALOG IN (SELECT DATABASE_NAME FROM CORE.CATALOG_SCOPE)
AND t.TABLE_SCHEMA NOT IN ('INFORMATION_SCHEMA', 'ADMIN');
新規のデータ資産には閲覧レベルの既定値を MERGE で自動付与します(既存行は保持)。
fail-closed 運用でも収集のたびに可視性マップが追従する形です。
新規データ資産
MERGE INTO CORE.ASSET_VISIBILITY v USING (
SELECT FULL_NAME,
CASE WHEN FULL_NAME = 'CATDEMO_RETAIL_DB.FINANCE.EXEC_COMPENSATION' THEN 40
WHEN SCHEMA_NAME IN ('HR', 'FINANCE') THEN 30
ELSE 20 END AS LVL
FROM CORE.ASSETS) n
ON v.FULL_NAME = n.FULL_NAME
WHEN NOT MATCHED THEN INSERT (FULL_NAME, VISIBILITY_LEVEL, VISIBILITY_REASON_CODE, HIDE_EXISTENCE_FLG)
VALUES (n.FULL_NAME, n.LVL,
CASE n.LVL WHEN 40 THEN 'RESTRICTED' WHEN 30 THEN 'CONFIDENTIAL' ELSE 'INTERNAL' END,
n.LVL >= 30);
タグとマスキング適用状況は、テーブル関数をデータ資産ごとにループで照会します。
タグとマスキング適用状況
DECLARE
c_assets CURSOR FOR SELECT FULL_NAME FROM CORE.ASSETS;
BEGIN
DELETE FROM CORE.TAG_ASSIGNMENTS;
FOR r IN c_assets DO
EXECUTE IMMEDIATE
'INSERT INTO CORE.TAG_ASSIGNMENTS (FULL_NAME, TAG_NAME, TAG_VALUE, TAG_LEVEL, COLUMN_NAME) ' ||
'SELECT OBJECT_DATABASE || ''.'' || OBJECT_SCHEMA || ''.'' || OBJECT_NAME, TAG_NAME, TAG_VALUE, "LEVEL", COLUMN_NAME ' ||
'FROM TABLE(CATDEMO_RETAIL_DB.INFORMATION_SCHEMA.TAG_REFERENCES_ALL_COLUMNS(''' || r.FULL_NAME || ''', ''table''))';
END FOR;
END;
POLICY_REFERENCES も同様にループで照会し、COLUMNS_META.MASKING_POLICY を更新します。タグ由来の分類・機密度も反映します。TAG_REFERENCES_ALL_COLUMNS はカラムレベルだけでなくテーブルレベルのタグも返す("LEVEL" 列で区別)ため、機密度タグはテーブルレベルの行から取り込みます。
-- カラムレベル: データ分類・PII フラグ
UPDATE CORE.COLUMNS_META cm
SET DATA_CLASS = t.TAG_VALUE, IS_PII = (t.TAG_VALUE = 'PII')
FROM CORE.TAG_ASSIGNMENTS t
WHERE t.FULL_NAME = cm.FULL_NAME AND t.COLUMN_NAME = cm.COLUMN_NAME
AND t.TAG_NAME = 'DATA_CLASS' AND t.TAG_LEVEL = 'COLUMN';
-- テーブルレベル: 機密度(ASSETS.SENSITIVITY へ反映)
UPDATE CORE.ASSETS a
SET SENSITIVITY = t.TAG_VALUE
FROM CORE.TAG_ASSIGNMENTS t
WHERE t.FULL_NAME = a.FULL_NAME
AND t.TAG_NAME = 'SENSITIVITY' AND t.TAG_LEVEL = 'TABLE';
UPDATE CORE.ASSETS SET SENSITIVITY = 'INTERNAL' WHERE SENSITIVITY IS NULL;
PII のサンプル値はカタログに保管しない
サンプル値は便利ですが、マスキングは表示制御であり元テーブルには生値が残ります。
カタログ側に PII を複製しないよう、サンプル値の収集対象を「PII でない・分類タグなし・機密テーブルでない・テキスト型」の列に限定しました。
サンプル値
DECLARE
c_sample_cols CURSOR FOR
SELECT cm.FULL_NAME AS FN, cm.COLUMN_NAME AS CN
FROM CORE.COLUMNS_META cm
JOIN CORE.ASSETS a ON a.FULL_NAME = cm.FULL_NAME
WHERE COALESCE(cm.IS_PII, FALSE) = FALSE
AND cm.DATA_CLASS IS NULL
AND COALESCE(a.SENSITIVITY, 'INTERNAL') NOT IN ('CONFIDENTIAL', 'RESTRICTED')
AND cm.DATA_TYPE = 'TEXT';
BEGIN
FOR r IN c_sample_cols DO
EXECUTE IMMEDIATE
'UPDATE CORE.COLUMNS_META SET SAMPLE_VALUES = LEFT((' ||
'SELECT LISTAGG(V, '' | '') FROM (SELECT DISTINCT LEFT(TO_VARCHAR(' || r.CN || '), 40) AS V ' ||
'FROM ' || r.FN || ' SAMPLE (50 ROWS) LIMIT 3) S), 130) ' ||
'WHERE FULL_NAME = ''' || r.FN || ''' AND COLUMN_NAME = ''' || r.CN || '''';
END FOR;
END;
リネージの収集
本検証では、CTAS 実行直後の GET_LINEAGE で上流テーブルを取得できました。リネージの取得可否・範囲は対象オブジェクト種別・権限・変換方式に依存するため、運用導入前に自環境のパイプラインでの検証をおすすめします。
SELECT SOURCE_OBJECT_SCHEMA || '.' || SOURCE_OBJECT_NAME AS SRC, DISTANCE
FROM TABLE(SNOWFLAKE.CORE.GET_LINEAGE(
'CATDEMO_RETAIL_DB.ANALYTICS.DAILY_SALES', 'TABLE', 'UPSTREAM', 3));
なお、収集スコープ外の上流データ資産は可視性マップに行が無いため、fail-closed によりリネージのエッジごと非表示になります(利用者には「上流なし」に見える)。安全側の挙動ですが、収集の完全性をステータス列で持たせるなどの補完が実運用では有効です。
権限とロール階層の収集
SHOW GRANTS の結果を RESULT_SCAN で取り込みます。
ロール
DELETE FROM CORE.GRANTS_EDGES;
SHOW GRANTS TO ROLE CATDEMO_SALES_ANALYST;
INSERT INTO CORE.GRANTS_EDGES (FULL_NAME, PRIVILEGE, GRANTED_ON, GRANTEE_ROLE)
SELECT "name", "privilege", "granted_on", "grantee_name"
FROM TABLE(RESULT_SCAN(LAST_QUERY_ID()))
WHERE "granted_on" IN ('TABLE', 'VIEW') AND "privilege" = 'SELECT'
AND "name" LIKE 'CATDEMO_RETAIL_DB.%';
SHOW GRANTS OF ROLE CATDEMO_SALES_ANALYST;
INSERT INTO CORE.ROLE_EDGES (PARENT_ROLE, CHILD_ROLE)
SELECT "role", "grantee_name" FROM TABLE(RESULT_SCAN(LAST_QUERY_ID())) WHERE "granted_to" = 'ROLE';
実効アクセスは再帰CTEでロール階層を展開し、継承経路(VIA_PATH)ごと実体化します。
DELETE FROM CORE.EFFECTIVE_ACCESS;
INSERT INTO CORE.EFFECTIVE_ACCESS (FULL_NAME, ROLE_NAME, PRIVILEGE, VIA_PATH, NOTE)
WITH RECURSIVE holders AS (
SELECT FULL_NAME, PRIVILEGE, GRANTEE_ROLE AS ROLE_NAME, GRANTEE_ROLE AS VIA_PATH
FROM CORE.GRANTS_EDGES
UNION ALL
SELECT h.FULL_NAME, h.PRIVILEGE, re.CHILD_ROLE, h.VIA_PATH || ' -> ' || re.CHILD_ROLE
FROM holders h
JOIN CORE.ROLE_EDGES re ON re.PARENT_ROLE = h.ROLE_NAME
WHERE LEN(h.VIA_PATH) < 500
)
SELECT DISTINCT FULL_NAME, ROLE_NAME, PRIVILEGE, VIA_PATH,
'権限経路の情報です。実データの可視行・可視列は Row Access Policy / Masking Policy の実行時評価に従います'
FROM holders;
NOTE 列に注記を持たせているのは、「アクセスできるロール」と「実データで見える範囲」を分けて回答させるためです。権限経路があっても、行・列レベルの制御で実際の可視範囲は変わります。
なお EFFECTIVE_ACCESS は「収集対象ロールについて観測できた付与・継承経路」であり、実効可視性の完全な判定ではありません。本検証ではデモ3ロールのみを対象とし、データベースロール・future grants・OWNERSHIP・共有経由は展開していません。
収集の最後に、Cortex Search 用の検索文書(後述の SEARCH_DOC_GENERAL)を最新のカタログ内容で更新し、実行履歴を確定します。検索文書の更新を収集に組み込まないと、カタログ本体を更新しても Cortex Search は古い文書を検索し続けます(TARGET_LAG はソースの変更を反映する目標値であり、途中に置いた静的テーブルを自動で再作成するものではありません)。
-- Search 文書の更新(DELETE + INSERT でテーブルを維持し、変更追跡を効かせる)
DELETE FROM SEARCH.SEARCH_DOC_GENERAL;
INSERT INTO SEARCH.SEARCH_DOC_GENERAL
SELECT ...; -- 作成時(後述)と同じ SELECT
-- 実行履歴の確定(セクション単位の失敗があれば PARTIAL_SUCCESS を記録)
UPDATE CORE.COLLECTION_RUNS
SET FINISHED_AT = CURRENT_TIMESTAMP(),
STATUS = IFF(EXISTS (SELECT 1 FROM CORE.COLLECTION_OBJECT_STATUS
WHERE RUN_ID = $RUN_ID AND STATUS = 'FAILED'),
'PARTIAL_SUCCESS', 'SUCCESS')
WHERE RUN_ID = $RUN_ID;
Row Access Policy
なぜ Row Access Policy なのか
カタログでは「1行 = 1データ資産」のため、「存在を隠す」=「行を返さない」ことです。行を消せるのは行レベルアクセス制御のためこちらを採用しました。
| 手段 | 存在隠蔽に使えるか |
|---|---|
| GRANT(オブジェクト権限) | テーブル・スキーマ単位の制御のみ。カタログは1テーブルに全データ資産の行が入るため行単位の出し分けは不可。閲覧レベル別にカタログを複製すると同期ずれと管理コストが発生 |
| マスキングポリシー | 列の値は隠せるが行は返る。「名前がマスクされた1行」が返る時点で件数・存在が漏れる |
| セキュアビュー + CURRENT_ROLE フィルタ | 実現は可能だが、ロール別にビューが増殖し Semantic View の参照先を1つに保てない。実質的に Row Access Policy の手組みになる |
| Agent の instructions | プロンプトでありセキュリティ境界ではない。直接SQLで迂回できる |
Row Access Policy はテーブル自体に付くため、直接SQL・SEMANTIC_VIEW()・Cortex Analyst が生成するSQL・JOIN・集計のすべてで、クエリ結果の行に強制されます。COUNT(*) も可視な行だけが対象になるため、SQL の集計結果から「12件中2件非表示」を直接算出されることは防げます。
ただし Row Access Policy が保護するのは「クエリ結果の行」だけです。届かない経路には別の対策を組み合わせます。
- Cortex Search の索引(owner's rights)→ 閲覧レベル「低」のデータ資産のみ事前フィルタして索引化
- カタログへの PII 複製 → 収集時にサンプル値を保管しない
- Semantic View 定義・AI検証済みクエリ・Agent instructions に書いた文字列 → Row Access Policy の保護対象外で LLM のコンテキストに入り得るため、非公開のデータ資産名をこれらに一切含めない
- 別経路の総数情報・命名規則・監査ログなどからの推論 → 本記事のスコープ外(運用設計で対応)
ポリシーの作成と適用
データ資産の必要閲覧レベルとロールの閲覧レベルを突き合わせる Row Access Policy を作り、カタログの全関連テーブルに共通適用します。
ポリシー作成
DELETE FROM CORE.ROLE_CLEARANCE;
INSERT INTO CORE.ROLE_CLEARANCE VALUES
('CATDEMO_SALES_ANALYST', 20),
('CATDEMO_DATA_STEWARD', 30),
('CATDEMO_CATALOG_ADMIN', 40),
('ACCOUNTADMIN', 40); -- 収集・運用用
CREATE OR REPLACE ROW ACCESS POLICY GOVERNANCE.RAP_ASSET_VISIBILITY
AS (ASSET_FN VARCHAR) RETURNS BOOLEAN ->
EXISTS (
SELECT 1
FROM CATDEMO_CATALOG_DB.CORE.ASSET_VISIBILITY v
JOIN CATDEMO_CATALOG_DB.CORE.ROLE_CLEARANCE rc
ON rc.CLEARANCE_LEVEL >= v.VISIBILITY_LEVEL
WHERE v.FULL_NAME = ASSET_FN
AND rc.ROLE_NAME = CURRENT_ROLE()
);
-- リネージは両端が可視のときだけ行を返す(片側が隠しアセットならエッジごと不可視)
CREATE OR REPLACE ROW ACCESS POLICY GOVERNANCE.RAP_LINEAGE_VISIBILITY
AS (SRC_FN VARCHAR, TGT_FN VARCHAR) RETURNS BOOLEAN ->
EXISTS (
SELECT 1 FROM CATDEMO_CATALOG_DB.CORE.ASSET_VISIBILITY v
JOIN CATDEMO_CATALOG_DB.CORE.ROLE_CLEARANCE rc ON rc.CLEARANCE_LEVEL >= v.VISIBILITY_LEVEL
WHERE v.FULL_NAME = SRC_FN AND rc.ROLE_NAME = CURRENT_ROLE()
)
AND EXISTS (
SELECT 1 FROM CATDEMO_CATALOG_DB.CORE.ASSET_VISIBILITY v
JOIN CATDEMO_CATALOG_DB.CORE.ROLE_CLEARANCE rc ON rc.CLEARANCE_LEVEL >= v.VISIBILITY_LEVEL
WHERE v.FULL_NAME = TGT_FN AND rc.ROLE_NAME = CURRENT_ROLE()
);
ALTER TABLE CORE.ASSETS ADD ROW ACCESS POLICY GOVERNANCE.RAP_ASSET_VISIBILITY ON (FULL_NAME);
ALTER TABLE CORE.COLUMNS_META ADD ROW ACCESS POLICY GOVERNANCE.RAP_ASSET_VISIBILITY ON (FULL_NAME);
ALTER TABLE CORE.OWNERS ADD ROW ACCESS POLICY GOVERNANCE.RAP_ASSET_VISIBILITY ON (FULL_NAME);
ALTER TABLE CORE.TAG_ASSIGNMENTS ADD ROW ACCESS POLICY GOVERNANCE.RAP_ASSET_VISIBILITY ON (FULL_NAME);
ALTER TABLE CORE.GRANTS_EDGES ADD ROW ACCESS POLICY GOVERNANCE.RAP_ASSET_VISIBILITY ON (FULL_NAME);
ALTER TABLE CORE.EFFECTIVE_ACCESS ADD ROW ACCESS POLICY GOVERNANCE.RAP_ASSET_VISIBILITY ON (FULL_NAME);
ALTER TABLE CORE.DATA_HEALTH ADD ROW ACCESS POLICY GOVERNANCE.RAP_ASSET_VISIBILITY ON (FULL_NAME);
ALTER TABLE CORE.ACCESS_REQUEST_GUIDE ADD ROW ACCESS POLICY GOVERNANCE.RAP_ASSET_VISIBILITY ON (FULL_NAME);
ALTER TABLE CORE.ASSET_TERM_RELATIONS ADD ROW ACCESS POLICY GOVERNANCE.RAP_ASSET_VISIBILITY ON (FULL_NAME);
ALTER TABLE CORE.LINEAGE_EDGES ADD ROW ACCESS POLICY GOVERNANCE.RAP_LINEAGE_VISIBILITY ON (SOURCE_FULL_NAME, TARGET_FULL_NAME);
判定を CURRENT_ROLE() にしたのは、Snowsight のロール切替と結果が1対1で対応し、デモと監査の説明性が高いためです。複数ロール併用の組織では IS_ROLE_IN_SESSION() への置換も検討できます。
Semantic View
Cortex Analyst 用の Semantic View を、Row Access Policy 適用済みテーブルの上に定義します。類義語と回答原則をコメントに埋め込み、代表質問を AI検証済みクエリとして登録します。
Semantic View
CREATE OR ALTER SEMANTIC VIEW CATDEMO_CATALOG_DB.SEMANTIC.SV_DATA_CATALOG
TABLES (
assets AS CATDEMO_CATALOG_DB.CORE.ASSETS
PRIMARY KEY (FULL_NAME)
WITH SYNONYMS = ('アセット', 'テーブル', 'データ資産', 'オブジェクト', 'asset')
COMMENT = 'データアセットのインベントリ。行が返らない場合は「該当するデータは見つかりませんでした」と回答する(存在の有無・権限の有無には言及しない)',
cols AS CATDEMO_CATALOG_DB.CORE.COLUMNS_META
PRIMARY KEY (FULL_NAME, COLUMN_NAME)
WITH SYNONYMS = ('カラム', '列', 'column')
COMMENT = 'カラムメタデータ。PII 列のサンプル値は収集していない',
owners AS CATDEMO_CATALOG_DB.CORE.OWNERS
PRIMARY KEY (FULL_NAME, OWNER_TYPE)
WITH SYNONYMS = ('オーナー', '所有者', 'データオーナー', 'owner')
COMMENT = 'データオーナー(TECHNICAL=技術 / BUSINESS=ビジネス / STEWARD=スチュワード)',
access AS CATDEMO_CATALOG_DB.CORE.EFFECTIVE_ACCESS
PRIMARY KEY (FULL_NAME, ROLE_NAME, PRIVILEGE, VIA_PATH)
WITH SYNONYMS = ('アクセス権', '権限', '実効アクセス', 'access')
COMMENT = '権限経路の情報。実データの可視行・可視列は Row Access Policy / Masking Policy の実行時評価に従う点を必ず添える',
lineage AS CATDEMO_CATALOG_DB.CORE.LINEAGE_EDGES
PRIMARY KEY (SOURCE_FULL_NAME, TARGET_FULL_NAME, COLLECTED_VIA)
WITH SYNONYMS = ('リネージ', '系譜', 'データの流れ', 'lineage')
COMMENT = 'データリネージ。COLLECTED_VIA(取得方法)と COLLECTED_AT(取得日時)を回答に添える',
health AS CATDEMO_CATALOG_DB.CORE.DATA_HEALTH
PRIMARY KEY (FULL_NAME)
WITH SYNONYMS = ('品質', '鮮度', 'データ品質', 'health')
COMMENT = 'データ品質・鮮度。品質ステータスはデモ用シード値を含む',
guide AS CATDEMO_CATALOG_DB.CORE.ACCESS_REQUEST_GUIDE
PRIMARY KEY (FULL_NAME)
WITH SYNONYMS = ('アクセス申請', '利用申請', '申請方法')
COMMENT = 'アクセス申請の導線。可視なアセットについてのみ案内する',
glossary AS CATDEMO_CATALOG_DB.CORE.BUSINESS_GLOSSARY
PRIMARY KEY (TERM_ID)
WITH SYNONYMS = ('用語集', 'ビジネス用語', '用語定義', 'glossary')
COMMENT = 'ビジネス用語集。業務概念の正式定義',
mobj AS CATDEMO_CATALOG_DB.CORE.MANAGED_OBJECTS
PRIMARY KEY (OBJECT_TYPE, NAME)
WITH SYNONYMS = ('管理オブジェクト', 'ウェアハウス', 'ポリシー一覧')
COMMENT = '管理対象オブジェクト(ウェアハウス・ロール・タグ・ポリシー)',
runs AS CATDEMO_CATALOG_DB.CORE.COLLECTION_RUNS
PRIMARY KEY (RUN_ID)
WITH SYNONYMS = ('収集履歴', 'カタログ更新履歴')
COMMENT = 'カタログ収集の実行履歴。カタログ情報の鮮度の根拠'
)
RELATIONSHIPS (
cols_to_assets AS cols (FULL_NAME) REFERENCES assets (FULL_NAME),
owners_to_assets AS owners (FULL_NAME) REFERENCES assets (FULL_NAME),
access_to_assets AS access (FULL_NAME) REFERENCES assets (FULL_NAME),
lineage_to_assets AS lineage (TARGET_FULL_NAME) REFERENCES assets (FULL_NAME),
health_to_assets AS health (FULL_NAME) REFERENCES assets (FULL_NAME),
guide_to_assets AS guide (FULL_NAME) REFERENCES assets (FULL_NAME)
)
FACTS (
assets.row_count AS assets.ROW_COUNT
WITH SYNONYMS = ('行数', 'レコード数') COMMENT = 'テーブルの行数',
assets.bytes AS assets.BYTES
WITH SYNONYMS = ('サイズ', '容量') COMMENT = 'テーブルサイズ(バイト)',
assets.query_count_30d AS assets.QUERY_COUNT_30D
WITH SYNONYMS = ('クエリ数', '利用回数') COMMENT = '直近30日のクエリ数(USAGE_SOURCE=SEEDED はデモ用架空値)',
assets.distinct_users_30d AS assets.DISTINCT_USERS_30D
WITH SYNONYMS = ('利用ユーザー数') COMMENT = '直近30日の利用ユーザー数(USAGE_SOURCE=SEEDED はデモ用架空値)'
)
DIMENSIONS (
assets.full_name AS assets.FULL_NAME
WITH SYNONYMS = ('完全名', 'フルネーム') COMMENT = 'DB.SCHEMA.OBJECT 形式の完全名',
assets.object_name AS assets.OBJECT_NAME
WITH SYNONYMS = ('テーブル名', 'オブジェクト名') COMMENT = 'オブジェクト名',
assets.schema_name AS assets.SCHEMA_NAME
WITH SYNONYMS = ('スキーマ') COMMENT = 'スキーマ名',
assets.asset_type AS assets.ASSET_TYPE COMMENT = 'TABLE/VIEW',
assets.description AS assets.DESCRIPTION
WITH SYNONYMS = ('説明', '概要') COMMENT = 'アセットの説明',
assets.domain AS assets.DOMAIN
WITH SYNONYMS = ('ドメイン', '業務領域', '業務ドメイン') COMMENT = '業務ドメイン(販売/顧客/人事/財務/分析)',
assets.sensitivity AS assets.SENSITIVITY
WITH SYNONYMS = ('機密度', '機密レベル') COMMENT = '機密度(SENSITIVITY タグ由来)',
assets.trust_level AS assets.TRUST_LEVEL
WITH SYNONYMS = ('信頼度', '認定', '認定状態') COMMENT = 'CERTIFIED=認定済み / REVIEWED=レビュー済み / UNVERIFIED=未検証',
assets.lifecycle_status AS assets.LIFECYCLE_STATUS
WITH SYNONYMS = ('ライフサイクル', '廃止予定', 'ステータス') COMMENT = 'DEPRECATED は置き換え先(REPLACED_BY)を必ず案内する',
assets.is_recommended AS assets.IS_RECOMMENDED
WITH SYNONYMS = ('推奨') COMMENT = '推奨アセットフラグ',
assets.replaced_by AS assets.REPLACED_BY
WITH SYNONYMS = ('置き換え先', '後継') COMMENT = '置き換え先アセット',
assets.deprecation_date AS assets.DEPRECATION_DATE
WITH SYNONYMS = ('廃止予定日') COMMENT = '廃止予定日',
assets.usage_guidance AS assets.USAGE_GUIDANCE
WITH SYNONYMS = ('利用ガイダンス', '利用上の注意') COMMENT = '利用上の注意',
assets.freshness_status AS assets.FRESHNESS_STATUS
WITH SYNONYMS = ('鮮度') COMMENT = 'FRESH/STALE',
assets.last_altered AS assets.LAST_ALTERED
WITH SYNONYMS = ('最終更新', '最終更新日時') COMMENT = '最終更新日時',
assets.last_collected_at AS assets.LAST_COLLECTED_AT
WITH SYNONYMS = ('収集日時', 'カタログ収集日時') COMMENT = 'カタログへの収集日時。回答の鮮度の根拠として添える',
assets.usage_source AS assets.USAGE_SOURCE
WITH SYNONYMS = ('統計取得元') COMMENT = 'SEEDED はデモ用架空値のため回答時にその旨を添える',
cols.column_name AS cols.COLUMN_NAME
WITH SYNONYMS = ('カラム名', '列名') COMMENT = 'カラム名',
cols.data_type AS cols.DATA_TYPE COMMENT = 'データ型',
cols.col_description AS cols.DESCRIPTION
WITH SYNONYMS = ('カラム説明') COMMENT = 'カラムの説明',
cols.data_class AS cols.DATA_CLASS
WITH SYNONYMS = ('データ分類', '分類') COMMENT = 'DATA_CLASS タグ由来(PII/CONFIDENTIAL 等)',
cols.is_pii AS cols.IS_PII
WITH SYNONYMS = ('個人情報', 'PII') COMMENT = 'PII フラグ',
cols.masking_policy AS cols.MASKING_POLICY
WITH SYNONYMS = ('マスキング', 'マスキングポリシー') COMMENT = '適用中のマスキングポリシー',
cols.sample_values AS cols.SAMPLE_VALUES
WITH SYNONYMS = ('サンプル値', 'サンプル') COMMENT = 'PII/機密列は収集対象外(生値をカタログに保管しない方針)',
owners.owner_type AS owners.OWNER_TYPE COMMENT = 'TECHNICAL/BUSINESS/STEWARD',
owners.owner_name AS owners.OWNER_NAME
WITH SYNONYMS = ('オーナー名', '所有者名', '担当部署') COMMENT = 'オーナー名',
owners.contact AS owners.CONTACT
WITH SYNONYMS = ('連絡先') COMMENT = '連絡先',
access.role_name AS access.ROLE_NAME
WITH SYNONYMS = ('ロール', 'ロール名') COMMENT = 'アクセス経路を持つロール',
access.privilege AS access.PRIVILEGE
WITH SYNONYMS = ('権限種別') COMMENT = '権限(SELECT 等)',
access.via_path AS access.VIA_PATH
WITH SYNONYMS = ('経路', '継承経路') COMMENT = 'ロール継承の経路',
access.note AS access.NOTE COMMENT = '実データ可視性に関する注記。回答に必ず添える',
lineage.source_full_name AS lineage.SOURCE_FULL_NAME
WITH SYNONYMS = ('上流', 'ソース', '元テーブル') COMMENT = '上流アセット',
lineage.target_full_name AS lineage.TARGET_FULL_NAME
WITH SYNONYMS = ('下流', 'ターゲット') COMMENT = '下流アセット',
lineage.collected_via AS lineage.COLLECTED_VIA
WITH SYNONYMS = ('取得方法') COMMENT = 'GET_LINEAGE=システム実測 / MANUAL=手動申告',
lineage.lineage_collected_at AS lineage.COLLECTED_AT COMMENT = 'リネージ取得日時',
health.health_freshness AS health.FRESHNESS_STATUS
WITH SYNONYMS = ('鮮度ステータス') COMMENT = 'FRESH/STALE/UNKNOWN',
health.quality_status AS health.QUALITY_STATUS
WITH SYNONYMS = ('品質ステータス') COMMENT = 'PASS/WARN/FAIL/UNKNOWN(デモ用シード値)',
health.last_refreshed_at AS health.LAST_REFRESHED_AT
WITH SYNONYMS = ('最終リフレッシュ') COMMENT = '最終データ更新日時',
guide.requestable AS guide.REQUESTABLE
WITH SYNONYMS = ('申請可能') COMMENT = 'アクセス申請可能か',
guide.request_channel AS guide.REQUEST_CHANNEL
WITH SYNONYMS = ('申請方法', '申請窓口') COMMENT = '申請チャネル',
guide.approver AS guide.APPROVER
WITH SYNONYMS = ('承認者') COMMENT = '承認者',
guide.standard_lead_time_days AS guide.STANDARD_LEAD_TIME_DAYS
WITH SYNONYMS = ('リードタイム') COMMENT = '標準リードタイム(営業日)',
glossary.term_name_ja AS glossary.TERM_NAME_JA
WITH SYNONYMS = ('用語', '用語名') COMMENT = 'ビジネス用語',
glossary.definition_ja AS glossary.DEFINITION_JA
WITH SYNONYMS = ('定義', '業務定義') COMMENT = '用語の業務定義',
glossary.term_domain AS glossary.DOMAIN COMMENT = '用語の業務ドメイン',
glossary.approval_status AS glossary.APPROVAL_STATUS COMMENT = 'APPROVED/DRAFT',
mobj.object_type AS mobj.OBJECT_TYPE
WITH SYNONYMS = ('オブジェクト種別') COMMENT = 'WAREHOUSE/ROLE/TAG/MASKING_POLICY/ROW_ACCESS_POLICY',
mobj.mobj_name AS mobj.NAME
WITH SYNONYMS = ('管理オブジェクト名') COMMENT = 'オブジェクト名',
mobj.detail AS mobj.DETAIL COMMENT = '詳細(サイズ・所有者等)',
runs.run_started_at AS runs.STARTED_AT
WITH SYNONYMS = ('収集開始') COMMENT = '収集開始日時',
runs.run_status AS runs.STATUS COMMENT = '収集結果(SUCCESS/PARTIAL_SUCCESS/FAILED)'
)
METRICS (
assets.asset_count AS COUNT(assets.FULL_NAME)
WITH SYNONYMS = ('アセット数', 'テーブル数', '件数') COMMENT = 'アセット件数',
assets.total_rows AS SUM(assets.ROW_COUNT)
WITH SYNONYMS = ('総行数') COMMENT = '行数の合計',
cols.column_count AS COUNT(cols.COLUMN_NAME)
WITH SYNONYMS = ('カラム数') COMMENT = 'カラム件数',
cols.pii_column_count AS SUM(CASE WHEN cols.IS_PII THEN 1 ELSE 0 END)
WITH SYNONYMS = ('PIIカラム数', '個人情報カラム数') COMMENT = 'PII カラムの件数',
access.accessible_role_count AS COUNT(DISTINCT access.ROLE_NAME)
WITH SYNONYMS = ('アクセス可能ロール数') COMMENT = 'アクセス経路を持つロール数'
)
COMMENT = 'AIデータカタログの Semantic View。回答原則: (1)結果が0件のときは「該当するデータは見つかりませんでした」とだけ回答し、存在や権限には言及しない (2)鮮度・利用統計は取得元を添える (3)権限の回答には「実データの可視性は実行時ポリシー評価に従う」注記を添える (4)DEPRECATED アセットは置き換え先を案内する'
AI_VERIFIED_QUERIES (
q_domain_inventory AS (
QUESTION 'どんなデータがありますか?ドメイン別に教えて'
ONBOARDING_QUESTION TRUE
SQL 'SELECT DOMAIN, COUNT(*) AS ASSET_COUNT, LISTAGG(OBJECT_NAME, '', '') WITHIN GROUP (ORDER BY OBJECT_NAME) AS OBJECTS FROM CATDEMO_CATALOG_DB.CORE.ASSETS GROUP BY DOMAIN ORDER BY DOMAIN'
),
q_pii_tables AS (
QUESTION '個人情報(PII)を含むテーブルはどこにありますか?'
ONBOARDING_QUESTION TRUE
SQL 'SELECT a.FULL_NAME, a.DESCRIPTION, LISTAGG(c.COLUMN_NAME, '', '') WITHIN GROUP (ORDER BY c.COLUMN_NAME) AS PII_COLUMNS FROM CATDEMO_CATALOG_DB.CORE.ASSETS a JOIN CATDEMO_CATALOG_DB.CORE.COLUMNS_META c ON c.FULL_NAME = a.FULL_NAME WHERE c.IS_PII GROUP BY 1, 2 ORDER BY 1'
),
q_owner_customers AS (
QUESTION 'CUSTOMERS テーブルのオーナーは誰ですか?'
ONBOARDING_QUESTION TRUE
SQL 'SELECT a.FULL_NAME, o.OWNER_TYPE, o.OWNER_NAME, o.CONTACT FROM CATDEMO_CATALOG_DB.CORE.ASSETS a JOIN CATDEMO_CATALOG_DB.CORE.OWNERS o ON o.FULL_NAME = a.FULL_NAME WHERE a.OBJECT_NAME = ''CUSTOMERS'' ORDER BY o.OWNER_TYPE'
),
q_keyword_where AS (
QUESTION '売上に関するテーブルはどこにありますか?'
SQL 'SELECT a.FULL_NAME, a.DESCRIPTION, a.TRUST_LEVEL, o.OWNER_NAME FROM CATDEMO_CATALOG_DB.CORE.ASSETS a LEFT JOIN CATDEMO_CATALOG_DB.CORE.OWNERS o ON o.FULL_NAME = a.FULL_NAME AND o.OWNER_TYPE = ''BUSINESS'' WHERE a.DESCRIPTION LIKE ''%売上%'' OR a.OBJECT_NAME LIKE ''%SALES%'' ORDER BY a.FULL_NAME'
),
q_access_customers AS (
QUESTION 'CUSTOMERS テーブルには誰がアクセスできますか?経路も教えて'
SQL 'SELECT FULL_NAME, ROLE_NAME, PRIVILEGE, VIA_PATH, NOTE FROM CATDEMO_CATALOG_DB.CORE.EFFECTIVE_ACCESS WHERE FULL_NAME = ''CATDEMO_RETAIL_DB.CRM.CUSTOMERS'' ORDER BY ROLE_NAME, VIA_PATH'
),
q_lineage_daily_sales AS (
QUESTION 'DAILY_SALES は何から作られていますか?上流の鮮度も教えて'
SQL 'SELECT l.SOURCE_FULL_NAME, l.COLLECTED_VIA, l.CONFIDENCE, l.COLLECTED_AT, h.FRESHNESS_STATUS, h.LAST_REFRESHED_AT FROM CATDEMO_CATALOG_DB.CORE.LINEAGE_EDGES l LEFT JOIN CATDEMO_CATALOG_DB.CORE.DATA_HEALTH h ON h.FULL_NAME = l.SOURCE_FULL_NAME WHERE l.TARGET_FULL_NAME = ''CATDEMO_RETAIL_DB.ANALYTICS.DAILY_SALES'' ORDER BY l.SOURCE_FULL_NAME'
),
q_masked_columns AS (
QUESTION 'マスキングが適用されているカラムの一覧を教えて'
SQL 'SELECT FULL_NAME, COLUMN_NAME, DATA_CLASS, MASKING_POLICY FROM CATDEMO_CATALOG_DB.CORE.COLUMNS_META WHERE MASKING_POLICY IS NOT NULL ORDER BY FULL_NAME, COLUMN_NAME'
),
q_popular_tables AS (
QUESTION '直近30日でよく使われているテーブルのトップ5は?'
SQL 'SELECT FULL_NAME, QUERY_COUNT_30D, DISTINCT_USERS_30D, USAGE_SOURCE FROM CATDEMO_CATALOG_DB.CORE.ASSETS ORDER BY QUERY_COUNT_30D DESC NULLS LAST LIMIT 5'
),
q_managed_objects AS (
QUESTION '管理対象のウェアハウスやポリシーの一覧を見せて'
SQL 'SELECT OBJECT_TYPE, NAME, DETAIL, COMMENT_TEXT FROM CATDEMO_CATALOG_DB.CORE.MANAGED_OBJECTS ORDER BY OBJECT_TYPE, NAME'
),
q_deprecated AS (
QUESTION '廃止予定のテーブルと置き換え先は?'
SQL 'SELECT FULL_NAME, DEPRECATION_DATE, REPLACED_BY, USAGE_GUIDANCE FROM CATDEMO_CATALOG_DB.CORE.ASSETS WHERE LIFECYCLE_STATUS = ''DEPRECATED'' ORDER BY DEPRECATION_DATE'
),
q_glossary_sales AS (
QUESTION '「売上」の定義を教えて'
SQL 'SELECT TERM_NAME_JA, DEFINITION_JA, DOMAIN, DATA_OWNER, APPROVAL_STATUS FROM CATDEMO_CATALOG_DB.CORE.BUSINESS_GLOSSARY WHERE TERM_NAME_JA = ''売上'''
),
q_catalog_freshness AS (
QUESTION 'このカタログの情報はいつ収集されたものですか?'
SQL 'SELECT RUN_ID, STARTED_AT, FINISHED_AT, STATUS, REQUESTED_SCOPE FROM CATDEMO_CATALOG_DB.CORE.COLLECTION_RUNS ORDER BY STARTED_AT DESC LIMIT 3'
)
)
;
GRANT USAGE ON SCHEMA SEMANTIC TO ROLE CATDEMO_SALES_ANALYST;
GRANT USAGE ON SCHEMA SEMANTIC TO ROLE CATDEMO_DATA_STEWARD;
GRANT USAGE ON SCHEMA SEMANTIC TO ROLE CATDEMO_CATALOG_ADMIN;
GRANT SELECT ON SEMANTIC VIEW SEMANTIC.SV_DATA_CATALOG TO ROLE CATDEMO_SALES_ANALYST;
GRANT SELECT ON SEMANTIC VIEW SEMANTIC.SV_DATA_CATALOG TO ROLE CATDEMO_DATA_STEWARD;
GRANT SELECT ON SEMANTIC VIEW SEMANTIC.SV_DATA_CATALOG TO ROLE CATDEMO_CATALOG_ADMIN;
Semantic View へのクエリ権限は SELECT です(USAGE ではない点に注意)。
重要な設計ポイントとして、AI検証済みクエリ・質問文・シノニム・コメントには非公開のデータ資産名を含めていません。これらの文字列は Row Access Policy の保護対象外で、Cortex Analyst のコンテキストに入り得るためです。探索のパターンは可視なデータ資産の例(売上・CUSTOMERS)で教え、非公開資産への質問は Row Access Policy 適用後の結果(0件)に委ねます。
SEMANTIC_VIEW() クエリでも Row Access Policy は効きます。SALES_ANALYST では人事・財務がドメイン別集計にすら現れませんでした。
USE ROLE CATDEMO_SALES_ANALYST;
SELECT * FROM SEMANTIC_VIEW(
CATDEMO_CATALOG_DB.SEMANTIC.SV_DATA_CATALOG
DIMENSIONS assets.domain
METRICS assets.asset_count
) ORDER BY 1;
Cortex Search
あいまい検索は Cortex Search で実装しますが、Cortex Search はサービス所有者の権限で索引化されるため、検索結果には利用者の Row Access Policy が効きません。
そこで、閲覧レベル「低」(全ロールに見せてよい)のデータ資産だけを事前フィルタした文書テーブルを索引化します。
Cortex Search
CREATE OR REPLACE TABLE SEARCH.SEARCH_DOC_GENERAL AS
SELECT a.FULL_NAME,
a.OBJECT_NAME,
a.SCHEMA_NAME,
a.DOMAIN,
a.SENSITIVITY,
a.ASSET_TYPE,
a.OBJECT_NAME || ' — ' || COALESCE(a.DESCRIPTION, '') ||
' / ドメイン: ' || COALESCE(a.DOMAIN, '') ||
' / 機密度: ' || COALESCE(a.SENSITIVITY, '') ||
' / 信頼度: ' || COALESCE(a.TRUST_LEVEL, '') ||
' / カラム: ' || COALESCE(c.COLS_TEXT, '') ||
' / オーナー: ' || COALESCE(o.OWNERS_TEXT, '') AS SEARCH_TEXT
FROM CORE.ASSETS a
JOIN CORE.ASSET_VISIBILITY v
ON v.FULL_NAME = a.FULL_NAME AND v.VISIBILITY_LEVEL <= 20 -- 事前フィルタ(存在隠蔽の徹底)
LEFT JOIN (
SELECT FULL_NAME,
LISTAGG(COLUMN_NAME || IFF(DESCRIPTION IS NOT NULL, '(' || DESCRIPTION || ')', ''), ', ')
WITHIN GROUP (ORDER BY ORDINAL) AS COLS_TEXT
FROM CORE.COLUMNS_META GROUP BY FULL_NAME
) c ON c.FULL_NAME = a.FULL_NAME
LEFT JOIN (
SELECT FULL_NAME, LISTAGG(OWNER_TYPE || ':' || OWNER_NAME, ', ') AS OWNERS_TEXT
FROM CORE.OWNERS GROUP BY FULL_NAME
) o ON o.FULL_NAME = a.FULL_NAME;
CREATE OR REPLACE CORTEX SEARCH SERVICE SEARCH.CATALOG_SEARCH_GENERAL
ON SEARCH_TEXT
ATTRIBUTES FULL_NAME, SCHEMA_NAME, DOMAIN, SENSITIVITY, ASSET_TYPE
WAREHOUSE = CATDEMO_WH
TARGET_LAG = '24 hours'
EMBEDDING_MODEL = 'snowflake-arctic-embed-l-v2.0'
AS (
SELECT SEARCH_TEXT, FULL_NAME, OBJECT_NAME, SCHEMA_NAME, DOMAIN, SENSITIVITY, ASSET_TYPE
FROM SEARCH.SEARCH_DOC_GENERAL
);
-- ソーステーブルの変更追跡を明示的に有効化(収集時の DELETE + INSERT を索引に反映させる)
ALTER TABLE SEARCH.SEARCH_DOC_GENERAL SET CHANGE_TRACKING = TRUE;
GRANT USAGE ON SCHEMA SEARCH TO ROLE CATDEMO_SALES_ANALYST;
GRANT USAGE ON CORTEX SEARCH SERVICE SEARCH.CATALOG_SEARCH_GENERAL TO ROLE CATDEMO_SALES_ANALYST;
-- STEWARD / ADMIN にも同様に付与
この構成の前提を2点補足します。
- 検索文書には列名・カラム説明・オーナー名などのメタデータを含めています(サンプル値は含めない)。これらのメタデータを閲覧レベル「低」に公開してよいことが前提で、列名等も秘匿対象なら文書から除外してください
- 検索サービスは閲覧レベル「低」の文書だけを参照するため、中・高ロールのあいまい検索でも低レベルのデータ資産しかヒットしません(構造化問い合わせは各ロールの可視範囲全体に届きます)。中・高向けのあいまい検索が必要な場合は、レベル別に文書とサービス自体を分離し、対応ロールにのみ
USAGEを付与します
Cortex Agent と CoWork 登録
Agent には2ツール(Cortex Analyst / Cortex Search)を持たせ、instructions で回答原則を指示します。
Cortex Agent&CoWork
CREATE OR REPLACE AGENT CATDEMO_CATALOG_DB.AGENT.DATA_CATALOG_AGENT
COMMENT = 'AIデータカタログ: 日本語でアセット検索・オーナー・アクセス権・リネージに即答する'
PROFILE = '{"display_name": "データカタログ アシスタント"}'
FROM SPECIFICATION
$$
models:
orchestration: auto
instructions:
response: |
あなたは社内データカタログのアシスタントです。以下の原則で日本語で回答してください。
1. 回答は必ずカタログ(CATDEMO_CATALOG_DB)の検索結果に基づき、出典がカタログである旨がわかるように答える。
2. 問い合わせ結果が0件のときは「該当するデータは見つかりませんでした」とだけ答える。
存在の有無・権限の有無・別の可能性には一切言及しない(「権限がありません」は禁止)。
3. 鮮度や利用状況を答えるときは、取得元(USAGE_SOURCE)とカタログ収集日時(LAST_COLLECTED_AT)を添える。
USAGE_SOURCE が SEEDED の統計は「デモ用の参考値」と明示する。
4. アクセス権の回答は2層で答える: (a) ロール継承上の権限経路(VIA_PATH)
(b) 実データで見える行・列は Row Access Policy / Masking Policy の実行時評価に従う旨の注記。
5. 可視かつ申請可能なアセットへのアクセス方法を聞かれたら、申請窓口・承認者・必要な記載事項・
標準リードタイムを ACCESS_REQUEST_GUIDE から案内する。
6. LIFECYCLE_STATUS が DEPRECATED のアセットは、置き換え先(REPLACED_BY)と廃止予定日を必ず案内する。
7. サンプル値について、PII・機密カラムは「ポリシーによりサンプル値は収集・提供されません」と答える。
8. 推測で補完しない。カタログにない情報は「カタログに情報がありません」と答える。
orchestration: |
ツールの使い分け:
- オーナー・アクセス権・リネージ・件数・一覧・鮮度・用語定義など構造化された質問は catalog_analyst を使う。
- 「〜みたいなデータ」「〜に関するテーブル」などキーワード・あいまい表現の探索は catalog_search で候補を
見つけ、必要に応じて catalog_analyst で詳細を補う。
- 両ツールの結果が0件なら、それ以上の推測はせず「該当するデータは見つかりませんでした」と回答する。
sample_questions:
- question: "どんなデータがありますか?ドメイン別に教えて"
- question: "個人情報を含むテーブルはどこにありますか?"
- question: "CUSTOMERS テーブルのオーナーは誰ですか?"
- question: "DAILY_SALES は何から作られていますか?"
tools:
- tool_spec:
type: "cortex_analyst_text_to_sql"
name: "catalog_analyst"
description: "データカタログへの構造化問い合わせ。アセット一覧・オーナー・アクセス権・リネージ・品質・用語集・管理オブジェクト・収集履歴に答える"
- tool_spec:
type: "cortex_search"
name: "catalog_search"
description: "データカタログのあいまい検索。キーワードからアセット候補を探す"
tool_resources:
catalog_analyst:
semantic_view: "CATDEMO_CATALOG_DB.SEMANTIC.SV_DATA_CATALOG"
catalog_search:
name: "CATDEMO_CATALOG_DB.SEARCH.CATALOG_SEARCH_GENERAL"
id_column: "FULL_NAME"
title_column: "OBJECT_NAME"
max_results: "10"
$$;
CoWork(Snowflake Intelligence)に登録します。SI オブジェクトが無いアカウントでは先に作成します。
CREATE SNOWFLAKE INTELLIGENCE IF NOT EXISTS SNOWFLAKE_INTELLIGENCE_OBJECT_DEFAULT;
GRANT USAGE ON SCHEMA AGENT TO ROLE CATDEMO_SALES_ANALYST;
GRANT USAGE ON AGENT AGENT.DATA_CATALOG_AGENT TO ROLE CATDEMO_SALES_ANALYST;
-- STEWARD / ADMIN にも同様に付与
ALTER SNOWFLAKE INTELLIGENCE SNOWFLAKE_INTELLIGENCE_OBJECT_DEFAULT
ADD AGENT CATDEMO_CATALOG_DB.AGENT.DATA_CATALOG_AGENT;
動かしてみた
CoWork(Snowsight の AI & ML > Snowflake CoWork)でロールを切り替えながら質問します。判定は CURRENT_ROLE ベースのため、画面右上のロール切替がそのまま回答範囲の切替になります。
一般アナリスト(SALES_ANALYST)で聞く
「どんなデータがありますか?ドメイン別に教えて」と聞くと、販売4・顧客1・分析3テーブルの棚卸しが返ります。
人事・財務ドメインは件数にも一覧にも現れません。

「顧客のメールアドレスを含むテーブルはどこ?サンプル値も見たい」には、CRM.CUSTOMERS を特定し、PII 分類と適用中のマスキングポリシー名(MASK_PII_STRING)を提示します。

「従業員の給与データはどこにありますか?」には「該当するデータは見つかりませんでした」とだけ返ります。Row Access Policy により Cortex Analyst の生成SQLが0件になるためです。

データスチュワード(DATA_STEWARD)に切り替える
同じ質問「従業員の給与データはどこにありますか?」で、HR.EMPLOYEE_SALARY が出現し、説明・機密度(CONFIDENTIAL)・ビジネスオーナー(人事部)・アクセス申請導線まで返ります。

「EMPLOYEE_SALARY には誰がアクセスできますか?経路も教えて」には、実効アクセスからロールと継承経路を一覧します。

「DAILY_SALES は何から作られていますか?上流の鮮度も教えて」には、GET_LINEAGE で実測した上流3テーブル(ORDERS / ORDER_ITEMS / PRODUCTS)を、取得方法・取得日時・鮮度ステータスつきで返します。

CoWork の回答では、Vega-Lite 形式と思われる可視化が表示されました(表現の幅は今後の拡充に期待です)。

カタログ管理者(CATALOG_ADMIN)に切り替える
「機密度別のデータ資産の数を教えて」で、ここで初めて RESTRICTED 1件(役員報酬テーブル)を含む全12件のデータ資産が集計に現れます。

課題
構築・検証を通じて感じた課題は次のとおりです。
- 既存の Snowflake Horizon Catalog との棲み分けの考慮が必要です。基本方針は Horizon Catalog を中心に据え、本記事のような構成は独自の問い合わせ体験や存在隠蔽の要件がある場合に補完的に採用するのがよいと考えます
- dbt でデータパイプラインを構築するケースも多いため、dbt のメタデータ(モデル・テスト・ドキュメント)の取り込みも拡張候補です
- 収集を DELETE→INSERT にしているため、人手で付与する情報(認定・廃止予定・利用ガイダンス等)は収集のたびに再設定しています。実運用では自動収集系と人手管理系のテーブル分離、または MERGE で収集元の列だけ更新する構成が安全です
まとめ
カタログ製品を追加購入しなくても、Snowflake 標準機能の組み合わせで「日本語で聞けて、ロールごとに見える範囲が変わり、見せないデータは存在ごと隠す」簡易的なデータカタログを構築できました。既存の権限・タグ・ポリシー資産をそのまま統制に転用できるのがネイティブ構成の利点と考えています。
組織内での利用用途によってカスタマイズする形が良さそうです。
参考
- https://docs.snowflake.com/en/user-guide/views-semantic/overview
- https://docs.snowflake.com/en/user-guide/snowflake-cortex/cortex-agents
- https://docs.snowflake.com/en/user-guide/snowflake-cortex/cortex-search/cortex-search-overview
- https://docs.snowflake.com/en/user-guide/snowflake-cortex/cortex-search/query-cortex-search-service
- https://docs.snowflake.com/en/user-guide/security-row-intro
- https://docs.snowflake.com/en/user-guide/tag-based-masking-policies
- https://docs.snowflake.com/en/sql-reference/functions/get_lineage-snowflake-core
- https://docs.snowflake.com/en/sql-reference/functions/is_agent_activated
この記事が何かの参考になれば幸いです!






