Snowflake Semantic Viewのコード管理
この記事は、Nowcast Advent Calendar 2025 の16日目の記事です。
はじめに
こんにちは。ナウキャストでデータエンジニアをしている加山です。
生成AIでの活用の文脈でも注目を集めているSnowflakeのSemantic Viewについて、そのコード管理に関して現状の方法を整理し、それぞれの評価と実装方法をまとめました。
本記事では、Semantic Viewそのものの概要(「そもそも何か」「ユースケース」など)は扱いません。コード管理の評価(何が管理できて何ができないか)と実装方法に関して記載しています。
またSemantic Viewの具体的な設定、Tipsに関しては、
を参照してください。
なお、本記事で言及するdbtはdbt coreを前提としており、Terraform、dbt coreの実行基盤がすでに整っていることを前提としています。
利用するデータ
本記事では、以下のER図で示す売上データを例として使用します。
サンプルデータ作成SQL
-- =========================
-- 0) 作業スキーマ(任意)
-- =========================
-- USE DATABASE YOUR_DB;
-- USE SCHEMA YOUR_SCHEMA;
-- =========================
-- 1) DIM: 商品マスタ
-- =========================
CREATE OR REPLACE TABLE DIM_PRODUCT (
PRODUCT_ID NUMBER NOT NULL,
PRODUCT_CODE VARCHAR NOT NULL,
PRODUCT_NAME VARCHAR NOT NULL,
CATEGORY_L1 VARCHAR NOT NULL,
UNIT_PRICE_YEN NUMBER(10,0) NOT NULL,
IS_ACTIVE BOOLEAN NOT NULL,
CREATED_AT TIMESTAMP_NTZ NOT NULL DEFAULT CURRENT_TIMESTAMP()
);
-- =========================
-- 2) DIM: 店舗マスタ
-- =========================
CREATE OR REPLACE TABLE DIM_STORE (
STORE_ID NUMBER NOT NULL,
STORE_CODE VARCHAR NOT NULL,
STORE_NAME VARCHAR NOT NULL,
PREFECTURE VARCHAR NOT NULL,
REGION VARCHAR NOT NULL,
OPEN_DATE DATE NOT NULL,
CREATED_AT TIMESTAMP_NTZ NOT NULL DEFAULT CURRENT_TIMESTAMP()
);
-- =========================
-- 3) FACT: 売上(明細)
-- =========================
CREATE OR REPLACE TABLE FACT_POS_SALES (
SALES_ID NUMBER NOT NULL,
SALES_TS TIMESTAMP_NTZ NOT NULL,
SALES_DATE DATE NOT NULL,
STORE_ID NUMBER NOT NULL,
PRODUCT_ID NUMBER NOT NULL,
QUANTITY NUMBER(10,0) NOT NULL,
UNIT_PRICE_YEN NUMBER(10,0) NOT NULL,
SALES_AMOUNT_YEN NUMBER(12,0) NOT NULL,
PAYMENT_METHOD VARCHAR NOT NULL,
CREATED_AT TIMESTAMP_NTZ NOT NULL DEFAULT CURRENT_TIMESTAMP()
);
-- ==========================================================
-- 4) ダミーデータ投入(DIM)
-- ==========================================================
-- 商品 50件
INSERT INTO DIM_PRODUCT (PRODUCT_ID, PRODUCT_CODE, PRODUCT_NAME, CATEGORY_L1, UNIT_PRICE_YEN, IS_ACTIVE)
WITH src AS (
SELECT
SEQ4() + 1 AS PRODUCT_ID,
'P' || LPAD(TO_VARCHAR(SEQ4() + 1), 6, '0') AS PRODUCT_CODE,
'商品_' || LPAD(TO_VARCHAR(SEQ4() + 1), 3, '0') AS PRODUCT_NAME,
CASE MOD(SEQ4(), 5)
WHEN 0 THEN '飲料'
WHEN 1 THEN '菓子'
WHEN 2 THEN '日配'
WHEN 3 THEN '冷凍'
ELSE '雑貨'
END AS CATEGORY_L1,
/* 100円〜1000円 */
100 + 10 * UNIFORM(0, 91, RANDOM()) AS UNIT_PRICE_YEN,
TRUE AS IS_ACTIVE
FROM TABLE(GENERATOR(ROWCOUNT => 50))
)
SELECT * FROM src;
-- 店舗 20件
INSERT INTO DIM_STORE (STORE_ID, STORE_CODE, STORE_NAME, PREFECTURE, REGION, OPEN_DATE)
WITH src AS (
SELECT
SEQ4() + 1 AS STORE_ID,
'S' || LPAD(TO_VARCHAR(SEQ4() + 1), 5, '0') AS STORE_CODE,
'店舗_' || LPAD(TO_VARCHAR(SEQ4() + 1), 3, '0') AS STORE_NAME,
CASE MOD(SEQ4(), 6)
WHEN 0 THEN '東京都'
WHEN 1 THEN '神奈川県'
WHEN 2 THEN '千葉県'
WHEN 3 THEN '埼玉県'
WHEN 4 THEN '大阪府'
ELSE '愛知県'
END AS PREFECTURE,
CASE
WHEN MOD(SEQ4(), 6) IN (0,1,2,3) THEN '関東'
WHEN MOD(SEQ4(), 6) = 4 THEN '関西'
ELSE '中部'
END AS REGION,
DATEADD('day', -UNIFORM(365, 3650, RANDOM()), CURRENT_DATE()) AS OPEN_DATE
FROM TABLE(GENERATOR(ROWCOUNT => 20))
)
SELECT * FROM src;
-- ==========================================================
-- 5) ダミーデータ投入(FACT)
-- 例:直近30日・10,000明細
-- ==========================================================
INSERT INTO FACT_POS_SALES (
SALES_ID, SALES_TS, SALES_DATE,
STORE_ID, PRODUCT_ID,
QUANTITY, UNIT_PRICE_YEN, SALES_AMOUNT_YEN,
PAYMENT_METHOD
)
WITH base AS (
SELECT
SEQ4() + 1 AS SALES_ID,
/* 直近30日からランダム */
DATEADD('day', -UNIFORM(0, 30, RANDOM()), CURRENT_DATE()) AS SALES_DATE,
/* 時刻もランダム(0〜23時) */
DATEADD(
'hour',
UNIFORM(0, 24, RANDOM()),
TO_TIMESTAMP_NTZ(DATEADD('day', -UNIFORM(0, 30, RANDOM()), CURRENT_DATE()))
) AS SALES_TS,
UNIFORM(1, 21, RANDOM()) AS STORE_ID, -- 1..20
UNIFORM(1, 51, RANDOM()) AS PRODUCT_ID, -- 1..50
UNIFORM(1, 6, RANDOM()) AS QUANTITY, -- 1..5
CASE MOD(UNIFORM(0, 100, RANDOM()), 4)
WHEN 0 THEN 'CASH'
WHEN 1 THEN 'CREDIT'
WHEN 2 THEN 'QR'
ELSE 'IC'
END AS PAYMENT_METHOD
FROM TABLE(GENERATOR(ROWCOUNT => 10000))
),
priced AS (
SELECT
b.SALES_ID,
b.SALES_TS,
b.SALES_DATE,
b.STORE_ID,
b.PRODUCT_ID,
b.QUANTITY,
p.UNIT_PRICE_YEN,
(b.QUANTITY * p.UNIT_PRICE_YEN) AS SALES_AMOUNT_YEN,
b.PAYMENT_METHOD
FROM base b
JOIN DIM_PRODUCT p
ON p.PRODUCT_ID = b.PRODUCT_ID
)
SELECT * FROM priced;
コード管理の方法
Semantic Viewのコード管理には主に3つの方法があります。
| 方法 | 概要 |
|---|---|
| YAML + Snowflake CLI | YAMLファイルで定義し、Snowflake CLIでデプロイ |
| dbt | dbt_semantic_viewパッケージを使用してdbt経由で作成 |
| Terraform | Terraform Provider(v2.11.0以降)を使用 |
1. YAML + Snowflake CLIを使う方法
概要
YAMLファイルでSemantic Viewを定義し、Snowflake CLIでデプロイする方法です。
実装
YAMLファイル例
name: SEMANTIC_POS
description: POS売上分析用セマンティックビュー
tables:
- name: SALES
synonyms:
- 売上
- POS売上
description: POS売上明細テーブル
base_table:
database: YOUR_DATABASE
schema: '"YOUR_SCHEMA"'
table: FACT_POS_SALES
primary_key:
columns:
- SALES_ID
dimensions:
- name: SALES_DATE
synonyms:
- 売上日
description: 売上日
expr: SALES_DATE
data_type: DATE
- name: SALES_TS
synonyms:
- 売上日時
description: 売上日時(タイムスタンプ)
expr: SALES_TS
data_type: TIMESTAMP_NTZ
- name: PAYMENT_METHOD
synonyms:
- 支払方法
- 決済方法
description: 支払方法(CASH, CREDIT, QR, IC)
expr: PAYMENT_METHOD
data_type: VARCHAR
sample_values:
- CASH
- CREDIT
- QR
- IC
is_enum: true
facts:
- name: QUANTITY
synonyms:
- 数量
- 販売数量
description: 販売数量
expr: QUANTITY
data_type: NUMBER
- name: SALES_AMOUNT_YEN
synonyms:
- 売上金額
- 売上
description: 売上金額(円)= 数量 × 単価
expr: SALES_AMOUNT_YEN
data_type: NUMBER
metrics:
- name: TOTAL_SALES
synonyms:
- 売上合計
- 総売上
description: 売上合計(円)
expr: SUM(SALES_AMOUNT_YEN)
- name: TOTAL_QUANTITY
synonyms:
- 販売数量合計
description: 販売数量合計
expr: SUM(QUANTITY)
- name: ORDER_COUNT
synonyms:
- 注文件数
- 取引件数
description: 注文件数
expr: COUNT(*)
- name: PRODUCT
synonyms:
- 商品
- 商品マスタ
description: 商品マスタテーブル
base_table:
database: YOUR_DATABASE
schema: '"YOUR_SCHEMA"'
table: DIM_PRODUCT
primary_key:
columns:
- PRODUCT_ID
dimensions:
- name: PRODUCT_CODE
synonyms:
- 商品コード
description: 商品コード
expr: PRODUCT_CODE
data_type: VARCHAR
- name: PRODUCT_NAME
synonyms:
- 商品名
description: 商品名
expr: PRODUCT_NAME
data_type: VARCHAR
- name: CATEGORY_L1
synonyms:
- カテゴリ
- 大分類
description: カテゴリ(大分類)- 飲料, 菓子, 日配, 冷凍, 雑貨
expr: CATEGORY_L1
data_type: VARCHAR
sample_values:
- 飲料
- 菓子
- 日配
- 冷凍
- 雑貨
is_enum: true
- name: IS_ACTIVE
synonyms:
- 有効フラグ
description: 有効フラグ
expr: IS_ACTIVE
data_type: BOOLEAN
sample_values:
- "true"
- "false"
is_enum: true
filters:
- name: active_product
synonyms:
- available_product
- current_product
- enabled_product
- functioning_product
- in_use_product
- live_product
- operational_product
- running_product
- valid_product
- working_product
description: 販売中の有効な商品を絞るためのflag
expr: IS_ACTIVE = TRUE
- name: STORE
synonyms:
- 店舗
- 店舗マスタ
description: 店舗マスタテーブル
base_table:
database: YOUR_DATABASE
schema: '"YOUR_SCHEMA"'
table: DIM_STORE
primary_key:
columns:
- STORE_ID
dimensions:
- name: STORE_CODE
synonyms:
- 店舗コード
description: 店舗コード
expr: STORE_CODE
data_type: VARCHAR
- name: STORE_NAME
synonyms:
- 店舗名
description: 店舗名
expr: STORE_NAME
data_type: VARCHAR
- name: PREFECTURE
synonyms:
- 都道府県
description: 都道府県
expr: PREFECTURE
data_type: VARCHAR
sample_values:
- 東京都
- 神奈川県
- 千葉県
- 埼玉県
- 大阪府
- 愛知県
is_enum: false
- name: REGION
synonyms:
- 地域
- エリア
description: 地域(関東, 関西, 中部)
expr: REGION
data_type: VARCHAR
sample_values:
- 関東
- 関西
- 中部
is_enum: false
- name: OPEN_DATE
synonyms:
- 開店日
description: 開店日
expr: OPEN_DATE
data_type: DATE
relationships:
- name: SALES_TO_PRODUCT
left_table: SALES
right_table: PRODUCT
relationship_columns:
- left_column: PRODUCT_ID
right_column: PRODUCT_ID
relationship_type: many_to_one
- name: SALES_TO_STORE
left_table: SALES
right_table: STORE
relationship_columns:
- left_column: STORE_ID
right_column: STORE_ID
relationship_type: many_to_one
verified_queries:
- name: storeコード S00004 の飲料の商品ごとの月次の売り上げの推移
question: storeコード S00004 の飲料の商品ごとの月次の売り上げの推移
sql: |-
SELECT
DATE_TRUNC('MONTH', s.sales_date) AS month,
p.product_name,
p.product_code,
MIN(s.sales_date) AS start_date,
MAX(s.sales_date) AS end_date,
SUM(s.sales_amount_yen) AS monthly_sales
FROM
sales AS s
LEFT OUTER JOIN product AS p ON s.product_id = p.product_id
LEFT OUTER JOIN store AS st ON s.store_id = st.store_id
WHERE
st.store_code = 'S00004'
AND p.category_l1 = '飲料'
GROUP BY
DATE_TRUNC('MONTH', s.sales_date),
p.product_name,
p.product_code
ORDER BY
p.product_name,
month DESC NULLS LAST
use_as_onboarding_question: false
verified_by: System
verified_at: 1765676734
module_custom_instructions:
question_categorization: お店の売上データに関するデータがあります。
sql_generation: 条件(where句)を日本語で指定された場合は、英語に翻訳せずsingle quotesで囲って指定してください
実行方法
SYSTEM$CREATE_SEMANTIC_VIEW_FROM_YAMLを使用してSemantic Viewを作成・更新します。PythonスクリプトやGitHub Actionsなど、任意の方法で実行可能です。
CALL SYSTEM$CREATE_SEMANTIC_VIEW_FROM_YAML(
'YOUR_DATABASE.YOUR_SCHEMA.SEMANTIC_POS',
'<yaml_content>'
);
CIでの構文チェック
SYSTEM$CREATE_SEMANTIC_VIEW_FROM_YAMLの第3引数にverify_onlyをTRUEに設定することで、CIパイプライン上でYAMLの構文チェックが可能です。GitHub ActionsなどのCI/CDパイプラインに組み込むことで、デプロイ前に構文エラーを検出できます。
CALL SYSTEM$CREATE_SEMANTIC_VIEW_FROM_YAML(
'YOUR_DATABASE.YOUR_SCHEMA.SEMANTIC_POS',
'<yaml_content>',
TRUE -- verify_only = TRUE で構文チェックのみ実行
);
評価
管理可能な項目
| 管理項目 | 対応状況 |
|---|---|
| Fact | ✅ 対応 |
| Dim | ✅ 対応 |
| Description | ✅ 対応 |
| Primary Key | ✅ 対応 |
| Unique Key | ✅ 対応 |
| Custom Instructions | ✅ 対応 |
| Verified Queries | ✅ 対応 |
| Synonyms | ✅ 対応 |
| Sample values (enumの登録) | ✅ 対応 |
| Named filter | ✅ 対応 |
| Connect Cortex Search | ✅ 対応 |
基本的に全ての項目に対応しています。(Semantic ViewのGUI上でもYAMLに変換できるので、当たり前ですが)
Custom InstructionsやVerified Queriesなど、生成AI連携に必要な機能をすべて定義できるため、Cortex AnalystやCortexSearchなど、Snowflakeのマネージドなサービスをフル活用したい場合に有効です。ただしCI/CDのパイプラインを自前で実装、メンテナンスをする必要があったり、実装面、運用面でコストがかかります。
2. dbtを使う方法
概要
Snowflake Labsが公開しているdbt_semantic_viewを使う方法です。このパッケージを使うと、dbtのmaterialization semantic_view としてSemantic Viewを定義できます。
実装
インストール
packages.ymlに追加してdbt depsを実行します:
packages:
- package: Snowflake-Labs/dbt_semantic_view
モデル定義例
{{ config(materialized='semantic_view') }}
TABLES(
sales AS {{ ref('fact_pos_sales') }} PRIMARY KEY (SALES_ID),
product AS {{ ref('dim_product') }} PRIMARY KEY (PRODUCT_ID),
store AS {{ ref('dim_store') }} PRIMARY KEY (STORE_ID)
)
RELATIONSHIPS(
sales_to_product AS
sales (PRODUCT_ID) REFERENCES product (PRODUCT_ID),
sales_to_store AS
sales (STORE_ID) REFERENCES store (STORE_ID)
)
FACTS(
sales.QUANTITY AS QUANTITY,
sales.SALES_AMOUNT_YEN AS SALES_AMOUNT_YEN
)
DIMENSIONS(
sales.SALES_DATE AS SALES_DATE
COMMENT = '売上日',
sales.SALES_TS AS SALES_TS
COMMENT = '売上日時',
sales.PAYMENT_METHOD AS PAYMENT_METHOD
COMMENT = '支払方法(CASH, CREDIT, QR, IC)',
product.PRODUCT_CODE AS PRODUCT_CODE
COMMENT = '商品コード',
product.PRODUCT_NAME AS PRODUCT_NAME
COMMENT = '商品名',
product.CATEGORY_L1 AS CATEGORY_L1
COMMENT = 'カテゴリ(飲料, 菓子, 日配, 冷凍, 雑貨)',
store.STORE_CODE AS STORE_CODE
COMMENT = '店舗コード',
store.STORE_NAME AS STORE_NAME
COMMENT = '店舗名',
store.PREFECTURE AS PREFECTURE
COMMENT = '都道府県',
store.REGION AS REGION
COMMENT = '地域(関東, 関西, 中部)'
)
METRICS(
sales.total_sales AS SUM(sales.SALES_AMOUNT_YEN)
COMMENT = '売上合計(円)',
sales.total_quantity AS SUM(sales.QUANTITY)
COMMENT = '販売数量合計',
sales.order_count AS COUNT(*)
COMMENT = '注文件数'
)
COMMENT = 'POS売上分析用セマンティックビュー'
実行方法
dbt buildで全てのモデルをビルドするか、特定のSemantic Viewだけを作成したい場合はdbt build --select <model_name>を使用します。
dbt build --select semantic_pos
評価
管理可能な項目
| 管理項目 | 対応状況 |
|---|---|
| Fact | ✅ 対応 |
| Dim | ✅ 対応 |
| Description | ✅ 対応 |
| Primary Key | ✅ 対応 |
| Unique Key | ❌ 非対応 |
| Custom Instructions | ❌ 非対応 |
| Verified Queries | ❌ 非対応 |
| Synonyms | ❌ 非対応 |
| Sample values (enumの登録) | ❌ 非対応 |
| Named filter | ❌ 非対応 |
| Connect Cortex Search | ❌ 非対応 |
BIツールなどでのSemantic Layerとして使う場面では有効です。dbtのエコシステムと完全に統合されており、他のテーブル、ビューと共に管理できたり、リネージの可視化やモデル間の依存関係管理が容易にできます。また、以下のように、Semantic Viewとその依存するモデル間のリネージの可視化も可能です。

ただし、現状はCortex Analyst連携時のCustom InstructionsやVerified Queriesなど、Snowflakeのマネージドサービス特有の機能は利用できません。Updateに期待です。
3. Terraformを用いる方法
概要
Terraform Provider for Snowflakeのv2.11.0以降で、Semantic Viewをリソースとして管理できるようになりました。既存のTerraformインフラと統合して管理できます。
実装
Terraform Provider設定
Semantic Viewリソースを使用するには、preview_features_enabledにsnowflake_semantic_view_resourceを追加する必要があります。
terraform {
required_providers {
snowflake = {
source = "snowflakedb/snowflake"
version = ">= 2.11.0"
}
}
}
provider "snowflake" {
# ... 認証情報などの設定 ...
preview_features_enabled = [
...
"snowflake_semantic_view_resource",
]
}
Semantic Viewリソース定義例
resource "snowflake_semantic_view" "semantic_pos" {
database = "YOUR_DATABASE"
schema = "YOUR_SCHEMA"
name = "SEMANTIC_POS"
comment = "POS売上分析用セマンティックビュー"
#-----------------------------------------------------------------------------
# テーブル定義
#-----------------------------------------------------------------------------
# SALES テーブル(POS売上明細テーブル)
tables {
table_alias = "SALES"
table_name = "\"YOUR_DATABASE\".\"YOUR_SCHEMA\".\"FACT_POS_SALES\""
comment = "POS売上明細テーブル"
synonym = ["売上", "POS売上"]
primary_key = ["SALES_ID"]
}
# PRODUCT テーブル(商品マスタテーブル)
tables {
table_alias = "PRODUCT"
table_name = "\"YOUR_DATABASE\".\"YOUR_SCHEMA\".\"DIM_PRODUCT\""
comment = "商品マスタテーブル"
synonym = ["商品", "商品マスタ"]
primary_key = ["PRODUCT_ID"]
}
# STORE テーブル(店舗マスタテーブル)
tables {
table_alias = "STORE"
table_name = "\"YOUR_DATABASE\".\"YOUR_SCHEMA\".\"DIM_STORE\""
comment = "店舗マスタテーブル"
synonym = ["店舗", "店舗マスタ"]
primary_key = ["STORE_ID"]
}
#-----------------------------------------------------------------------------
# Dimensions(ディメンション)
#-----------------------------------------------------------------------------
# SALES テーブルのディメンション
dimensions {
qualified_expression_name = "\"SALES\".\"SALES_DATE\""
sql_expression = "\"SALES\".\"SALES_DATE\""
comment = "売上日"
synonym = ["売上日"]
}
dimensions {
qualified_expression_name = "\"SALES\".\"SALES_TS\""
sql_expression = "\"SALES\".\"SALES_TS\""
comment = "売上日時(タイムスタンプ)"
synonym = ["売上日時"]
}
dimensions {
qualified_expression_name = "\"SALES\".\"PAYMENT_METHOD\""
sql_expression = "\"SALES\".\"PAYMENT_METHOD\""
comment = "支払方法(CASH, CREDIT, QR, IC)"
synonym = ["支払方法", "決済方法"]
}
# PRODUCT テーブルのディメンション
dimensions {
qualified_expression_name = "\"PRODUCT\".\"PRODUCT_CODE\""
sql_expression = "\"PRODUCT\".\"PRODUCT_CODE\""
comment = "商品コード"
synonym = ["商品コード"]
}
dimensions {
qualified_expression_name = "\"PRODUCT\".\"PRODUCT_NAME\""
sql_expression = "\"PRODUCT\".\"PRODUCT_NAME\""
comment = "商品名"
synonym = ["商品名"]
}
dimensions {
qualified_expression_name = "\"PRODUCT\".\"CATEGORY_L1\""
sql_expression = "\"PRODUCT\".\"CATEGORY_L1\""
comment = "カテゴリ(大分類)- 飲料, 菓子, 日配, 冷凍, 雑貨"
synonym = ["カテゴリ", "大分類"]
}
dimensions {
qualified_expression_name = "\"PRODUCT\".\"IS_ACTIVE\""
sql_expression = "\"PRODUCT\".\"IS_ACTIVE\""
comment = "有効フラグ"
synonym = ["有効フラグ"]
}
# STORE テーブルのディメンション
dimensions {
qualified_expression_name = "\"STORE\".\"STORE_CODE\""
sql_expression = "\"STORE\".\"STORE_CODE\""
comment = "店舗コード"
synonym = ["店舗コード"]
}
dimensions {
qualified_expression_name = "\"STORE\".\"STORE_NAME\""
sql_expression = "\"STORE\".\"STORE_NAME\""
comment = "店舗名"
synonym = ["店舗名"]
}
dimensions {
qualified_expression_name = "\"STORE\".\"PREFECTURE\""
sql_expression = "\"STORE\".\"PREFECTURE\""
comment = "都道府県"
synonym = ["都道府県"]
}
dimensions {
qualified_expression_name = "\"STORE\".\"REGION\""
sql_expression = "\"STORE\".\"REGION\""
comment = "地域(関東, 関西, 中部)"
synonym = ["地域", "エリア"]
}
dimensions {
qualified_expression_name = "\"STORE\".\"OPEN_DATE\""
sql_expression = "\"STORE\".\"OPEN_DATE\""
comment = "開店日"
synonym = ["開店日"]
}
#-----------------------------------------------------------------------------
# Facts(ファクト)
#-----------------------------------------------------------------------------
facts {
qualified_expression_name = "\"SALES\".\"QUANTITY\""
sql_expression = "\"SALES\".\"QUANTITY\""
comment = "販売数量"
synonym = ["数量", "販売数量"]
}
facts {
qualified_expression_name = "\"SALES\".\"SALES_AMOUNT_YEN\""
sql_expression = "\"SALES\".\"SALES_AMOUNT_YEN\""
comment = "売上金額(円)= 数量 × 単価"
synonym = ["売上金額", "売上"]
}
#-----------------------------------------------------------------------------
# Metrics(メトリクス)
#-----------------------------------------------------------------------------
metrics {
semantic_expression {
qualified_expression_name = "\"SALES\".\"TOTAL_SALES\""
sql_expression = "SUM(\"SALES\".\"SALES_AMOUNT_YEN\")"
comment = "売上合計(円)"
synonym = ["売上合計", "総売上"]
}
}
metrics {
semantic_expression {
qualified_expression_name = "\"SALES\".\"TOTAL_QUANTITY\""
sql_expression = "SUM(\"SALES\".\"QUANTITY\")"
comment = "販売数量合計"
synonym = ["販売数量合計"]
}
}
metrics {
semantic_expression {
qualified_expression_name = "\"SALES\".\"ORDER_COUNT\""
sql_expression = "COUNT(*)"
comment = "注文件数"
synonym = ["注文件数", "取引件数"]
}
}
#-----------------------------------------------------------------------------
# Relationships(リレーションシップ)
#-----------------------------------------------------------------------------
# SALES → PRODUCT (many_to_one)
relationships {
relationship_identifier = "SALES_TO_PRODUCT"
relationship_columns = ["PRODUCT_ID"]
table_name_or_alias {
table_alias = "SALES"
}
referenced_table_name_or_alias {
table_alias = "PRODUCT"
}
referenced_relationship_columns = ["PRODUCT_ID"]
}
# SALES → STORE (many_to_one)
relationships {
relationship_identifier = "SALES_TO_STORE"
relationship_columns = ["STORE_ID"]
table_name_or_alias {
table_alias = "SALES"
}
referenced_table_name_or_alias {
table_alias = "STORE"
}
referenced_relationship_columns = ["STORE_ID"]
}
}
デプロイ
terraform init
terraform plan
terraform apply
評価
管理可能な項目
| 管理項目 | 対応状況 |
|---|---|
| Fact | ✅ 対応 |
| Dim | ✅ 対応 |
| Description | ✅ 対応 |
| Primary Key | ✅ 対応 |
| Unique Key | ❌ 非対応 |
| Custom Instructions | ❌ 非対応 |
| Verified Queries | ❌ 非対応 |
| Synonyms | ✅ 対応 |
| Sample values (enumの登録) | ❌ 非対応 |
| Named filter | ❌ 非対応 |
| Connect Cortex Search | ❌ 非対応 |
既存のTerraformインフラと統合したい場合には選択肢となりますが、Semantic Viewはビジネス要件の変更に応じて頻繁に更新される一方で、Terraformは通常インフラリソースの安定性を重視するため、ライフサイクルが大きく異なります。また、Semantic Viewの管理はデータアナリストやビジネスサイドの担当者が行うことが多いのに対し、Terraformの管理はインフラエンジニアが担当することが多く、管理者や作業者が異なる場合が多いです。そのため大きなメリットがない現状、Terraformでの管理は現実的ではないかもしれません。
比較
| 観点 | YAML + Snowflake CLI | dbt | Terraform |
|---|---|---|---|
| 基本的なSemantic Viewの定義 | ✅ | ✅ | ✅ |
| CortexAnalystとの統合 | ✅ | ⚠️ | ⚠️ |
| CortexSearchとの統合 | ✅ | ❌ | ❌ |
| Modern Data Stackとの統合 | ⚠️ | ✅ | ❌ |
| 初期実装コスト ※1 | ⚠️ | ✅ | ✅ |
| 運用コスト | ⚠️ | ✅ | ⚠️ |
※1:dbt、Terraformの実行環境が整っている前提での評価です。
まとめ
Semantic Viewのコード管理には、YAML + Snowflake CLI、dbt、Terraformの3つの方法がありますが、それぞれ一長一短があります。
各方法の選択指針
個人的には各方法の選択は、以下のような観点で判断すると良いと思います。
- CortexAnalystなど生成AIを活用、Snowflakeのマネージドな機能をフル活用したい場合 → YAML + Snowflake CLI
- BI用のみに活用する場合 → dbt
運用の課題
Cortex Analystの最適化機能では、Verified Queriesを活用してSemantic Viewをエンハンスする方法が提供されています。
一方で、現状Custom InstructionsやVerified Queriesも上書きするような仕様になっているため、コード管理が複雑になっています。また、Verified Queriesをユーザー側で追加して成長させたり、Custom Instructionsのチューニングはコードで管理しづらい側面があります。
理想的な運用としては、変更が多い部分(Custom Instructions、Verified Queriesなど)と、メトリックスの定義など管理したい部分を分けて運用できるようになると良いでしょう。例えば、メトリックスやディメンションの定義はコードで管理し、Verified QueriesやCustom InstructionsはSnowflakeのマネージド機能で管理する、といった使い分けが考えられます。
参考リンク
Discussion