omni-to-snowflake-semantic-view について
Omni のトピックを Snowflake Semantic View の YAML 定義へ変換する手順を案内するスキルです。Snowflake で Omni データをモデル化するユーザー向けに、モデル探索、フィールドやリレーションのマッピング、SQL 実行を扱います。
主な機能
- 01Omni モデルとトピックの探索
- 02フィールドの採用・除外処理
- 03ディメンション、メジャー、結合のマッピング
- 04Snowflake SQL の実行案内
活用シーン
- 01Omni トピックの Snowflake Semantic View への変換
- 02ビュー作成前のソーステーブルと結合の確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ exploreomni/ omni-agent-skills --skill omni-to-snowflake-semantic-viewインストール後、Claude Code で/omni-to-snowflake-semantic-viewを入力して読み込まれたか確認します。
詳細
Omni → Snowflake Semantic View
まず API を通じて Omni モデルを調査し、その定義を Snowflake Semantic View 形式に変換して、Omni トピックを Snowflake Semantic View の YAML 定義に変換します。
前提条件
# Verify the Omni CLI is installed — if not, ask the user to install it
# See: https://github.com/exploreomni/cli#readme
command -v omni >/dev/null || echo "ERROR: Omni CLI is not installed."
# Show available profiles and select the appropriate one
omni config show
# If multiple profiles exist, ask the user which to use, then switch:
omni config use <profile-name>
# Confirm the active profile is authenticated and inspect your permissions:
omni whoami whoami
認証:プロファイルは API キー または OAuth で認証します。
whoami(または任意の呼び出し)が 401 を返した場合は、処理を引き継ぎ、ユーザーに! omni config login <profile>を実行するよう依頼してください(OAuth 2.1 のブラウザーフローで、ブラウザー上で約 2 分間処理がブロックされます)。ヘッドレス/CI セッションではconfig loginを実行しないでください(ブラウザーがないためタイムアウトします)。ローカルの対話型マシンでは実行してもかまいません。プロファイルのセットアップ(omni config init --auth oauth)と、--schemaを使ってコマンドやリクエスト本文の形式を確認する方法については、omni-api-conventionsルールを参照してください。
ヒント:プログラムで解析する場合は
-o jsonで構造化出力を強制し、読みやすいテーブルを表示する場合は-o humanを使用してください。デフォルトはauto(TTY では人間向け、パイプ処理時は JSON)です。
実行環境の検出
開始する前に、実行環境を確認してください。環境によって、ステップ 7 で Snowflake に対して SQL を実行する方法が決まります。
検出方法
組み込みの Snowflake SQL 実行機能が利用できるか確認します。
- Cortex Code(Snowflake Notebooks/Cortex Analyst):ネイティブの SQL 実行機能を利用できます。追加のツールは不要です。
- Claude Code、Cursor、またはその他の外部 IDE/エージェント:ネイティブの Snowflake 接続はありません。以下の CLI オプションのいずれかを使用する必要があります。
検出のヒューリスティック
If the agent can execute a SQL statement like `SELECT CURRENT_USER()` directly without any shell command → you are in Cortex Code.
Otherwise → you are in an external environment.
不明な場合は、ユーザーに 「Snowflake 内(Cortex Code/Notebooks)で実行していますか。それとも Claude Code や Cursor のような外部ツールで実行していますか?」 と尋ねてください。
外部環境:Snowflake の接続方法
Claude Code、Cursor、またはターミナルベースのエージェントでは、以下のいずれかを選択してください。利用可能かどうかを上から順に確認します。
オプション A — Snowflake CLI(snow)✅ 推奨
# Check if available
command -v snow
# Configure a connection (first time)
snow connection add
# Execute SQL
snow sql -q "SELECT CURRENT_USER();" --connection <connection_name>
毎回の呼び出しで --connection を繰り返し指定せずに済むよう、デフォルト接続を設定します。
snow connection set-default <connection_name>
オプション B — SnowSQL(従来の CLI)
# Check if available
command -v snowsql
# Execute SQL
snowsql -a <account> -u <user> -q "SELECT CURRENT_USER();"
オプション C — Python(snowflake-connector-python)
pip install snowflake-connector-python
import snowflake.connector
conn = snowflake.connector.connect(
account="<account>",
user="<user>",
password="<password>", # or use key-pair / SSO
warehouse="<warehouse>",
database="<database>",
schema="<schema>",
)
conn.cursor().execute("<SQL here>")
✋ 停止 — ステップ 7 に進む前に、Snowflake の接続方法をユーザーに確認してください。ステップ 7 で正しい実行コマンドを生成できるよう、使用する方法を記録してください。
ワークフロー
ステップ 1 — 要件の確認
ユーザーに次の項目を尋ねます。
- 変換する トピック はどれですか?
- Semantic View の名前 は何にしますか?
- Snowflake のどこに作成しますか(データベース と スキーマ)?
- Semantic View へのアクセスを許可する ロール はどれですか?
⚠️ 停止 — 先に進む前に、4 つすべての回答を確認してください。
ステップ 2 — Omni モデルの調査
2a. モデル ID を特定する
omni models list --model-kind SHARED
Shared Model を特定し、その id を記録します。Schema または Workbook のモデルより、常に Shared Model を優先してください。
2b. トピックファイルを取得する
omni models yaml-get <modelId> --file-name <topic_name>.topic
2c. リレーションシップファイルを取得する
omni models yaml-get <modelId> --file-name relationships
2d. トピックで参照されている各ビューのファイルを取得する
base_view と joins に含まれる各ビューについて、YAML を取得します。
omni models yaml-get <modelId> --file-name <view_name>.view
ビューのプレフィックスが
omni_dbt_の場合は、同じくomni_dbt_で始まるファイルを取得してください(例:omni_dbt_ecomm__order_items.view)。
ステップ 3 — テーブルと結合の特定
ビュー名を Snowflake のテーブルに対応付ける
トピックファイルの base_view はプライマリテーブルです。ビュー名を Snowflake のテーブル参照に変換します。
base_view/join の値 | Snowflake のテーブル |
|---|---|
ecomm__order_items | ECOMM.ORDER_ITEMS |
omni_dbt_ecomm__order_items | ECOMM.ORDER_ITEMS(omni_dbt_ を削除) |
__ 区切り文字は、左側がスキーマ、右側がテーブル名に対応します。Snowflake にスキーマが存在しない場合は、そのテーブル全体をスキップしてください。
結合階層を確認する
トピックの joins パラメーターでは、インデントによって結合チェーンを定義します。あるテーブルの下にインデントされたテーブルは、その親テーブルに結合されます。
joins:
user_order_facts: {} # skip — this is a derived CTE, not a physical table
ecomm__users: {} # joins to base_view (ORDER_ITEMS)
ecomm__inventory_items: # joins to base_view (ORDER_ITEMS)
ecomm__products: # joins to INVENTORY_ITEMS
demo__product_images: {} # joins to PRODUCTS
ecomm__distribution_centers: {} # joins to PRODUCTS
SQL で定義された Omni の CTE など、派生テーブルであるビューはスキップしてください。これらには参照可能な物理 Snowflake テーブルがありません。
主キー
各ビューのファイルで、primary_key: true が設定されたディメンションを確認してください。これが Semantic View の unique: true ディメンションになります。
✋ 停止 — 続行する前に、テーブルの一覧をユーザーに確認してください。
ステップ 4 — フィールド一覧を解決する
トピックの fields パラメーターは、ビューから Semantic View に含めるフィールドを制御します。
フィールド指定のルール
| 構文 | 意味 |
|---|---|
(fields パラメーターなし) | すべてのビューの全フィールドを含めます |
all_views.* | すべてのビューの全フィールドを含めます |
view.* | 指定したビューの全フィールドを含めます |
tag:<value> | この値のタグが付いた全フィールドを含めます |
view.field | この特定のフィールドを含めます |
-view.field | この特定のフィールドを除外します |
除外を正しく適用する方法
除外(- が先頭に付くもの)は、すべての包含を解決した後に適用する必要があります。手順は次のとおりです。
- 空の包含セットから始めます
fieldsの各項目を順に処理します:- 包含ルール(
view.*、view.field、tag:x)の場合 → 一致するフィールドをセットに追加します - 除外ルール(
-view.field)の場合 → ワイルドカードによって追加されていても、そのフィールドをセットから削除します
- 包含ルール(
- 最終的なセットが、Semantic View に含める完全なフィールド一覧です
例:
fields:
- ecomm__order_items.* # include all order_items fields
- ecomm__users.country # include this one users field
- -ecomm__order_items.cost # remove cost — excluded even though * was used above
- -ecomm__order_items.raw_json # remove raw_json — same reason
結果:order_items の全フィールドから cost と raw_json を除外し、さらに users.country を追加します。
⚠️ 重要:
-による除外が常に優先されます。ワイルドカードで含まれていても、明示的に除外されたフィールドは絶対に含めないでください。
ステップ 5 — リレーションシップを構築する
ステップ 3 の結合階層と、ステップ 2c で取得した relationships.yaml を使って、各結合を Snowflake Semantic View のリレーションシップに対応付けます。
relationships.yaml の各項目は次のようになっています。
- join_from_view: ecomm__order_items
join_to_view: ecomm__inventory_items
join_type: always_left
on_sql: ${ecomm__order_items.inventory_item_id} = ${ecomm__inventory_items.id}
relationship_type: assumed_many_to_one
on_sql フィールドには結合列が示されています。列名を抽出し、出力の relationship_columns に設定してください。
使用可能なリレーションシップのパラメーター:
| パラメーター | 説明 |
|---|---|
join_from_view | 結合元のビュー |
join_to_view | 結合先のビュー |
join_type | SQL の結合タイプ(例:always_left) |
on_sql | SQL 条件 — ここから列名を抽出します |
relationship_type | カーディナリティ(例:assumed_many_to_one) |
reversible | 結合が双方向かどうか |
where_sql | 結合が有効な場合に適用される追加の WHERE 句 |
ステップ 6 — ディメンションとメジャーをマッピングする
解決済みのフィールド一覧にある各ビューについて、Omni のフィールド定義を Semantic View の項目に変換します。
⚠️ ステップ 4 の包含・除外の解決後に残ったフィールドのみを変換してください。除外されたフィールドは追加しないでください。
ディメンション → dimensions または time_dimensions
label が定義されていない場合、フィールド名がディメンション名になります。description と synonyms はそのまま引き継ぎます。
標準ディメンション:
# Omni view YAML
city:
sql: '"CITY"'
label: City
description: Customer's city
# Semantic View output
- name: city
expr: CITY
description: Customer's city
data_type: TEXT
日付/タイムスタンプディメンション → time_dimensions を使用します:
# Omni
created_at:
sql: '"CREATED_AT"'
type: time
label: Created At
# Semantic View output
time_dimensions:
- name: created_at
expr: CREATED_AT
data_type: TIMESTAMP
グループディメンション → CASE WHEN 式に変換します:
# Omni
device_type_groups:
sql: ${device_type}
label: Device Type Groups
groups:
- filter:
is: [ mobile, tablet ]
name: Handheld
- filter:
is: desktop
name: Desktop
else: Other
-- expr value
CASE
WHEN "DEVICE_TYPE" IN ('mobile', 'tablet') THEN 'Handheld'
WHEN "DEVICE_TYPE" = 'desktop' THEN 'Desktop'
ELSE 'Other'
END
ビンディメンション → 範囲を表す CASE WHEN 式に変換します:
# Omni
age_bin:
sql: ${age}
bin_boundaries: [ 18, 35, 50, 65 ]
-- expr value
CASE
WHEN "AGE" < 18 THEN 'below 18'
WHEN "AGE" >= 18 AND "AGE" < 35 THEN '>= 18 and < 35'
WHEN "AGE" >= 35 AND "AGE" < 50 THEN '>= 35 and < 50'
WHEN "AGE" >= 50 AND "AGE" < 65 THEN '>= 50 and < 65'
WHEN "AGE" >= 65 THEN '65 and above'
ELSE NULL
END
期間ディメンション → TIMESTAMPDIFF 式に変換します:
# Omni
fulfillment_days:
duration:
sql_start: ${created_at[date]}
sql_end: ${delivered_at[date]}
intervals: [ days ]
-- expr value (days)
CASE
WHEN TIMESTAMPADD(DAY, 1 * TIMESTAMPDIFF(DAY, DATE_TRUNC('DAY', "CREATED_AT"), DATE_TRUNC('DAY', "DELIVERED_AT")), DATE_TRUNC('DAY', "CREATED_AT"))
<= DATE_TRUNC('DAY', "DELIVERED_AT")
THEN TIMESTAMPDIFF(DAY, DATE_TRUNC('DAY', "CREATED_AT"), DATE_TRUNC('DAY', "DELIVERED_AT"))
ELSE TIMESTAMPDIFF(DAY, DATE_TRUNC('DAY', "CREATED_AT"), DATE_TRUNC('DAY', "DELIVERED_AT")) - 1
END
ブール値ディメンション → 名前付き filter になります(ディメンションではありません):
# Omni
is_returned:
sql: '"IS_RETURNED"'
description: Whether the item was returned
completed_orders:
sql: ${status} = 'Complete'
label: Completed Orders
# Semantic View output — goes in filters, not dimensions
filters:
- name: is_returned
expr: IS_RETURNED
description: Whether the item was returned
- name: completed_orders
expr: STATUS = 'Complete'
description: Completed Orders
Measures → metrics
sql フィールドはソース列を参照し、aggregate_type は集計方法を定義します。
標準メジャー:
# Omni
total_sale_price:
sql: ${sale_price}
aggregate_type: sum
label: Total Sale Price
description: Total revenue of orders
synonyms: [ Total Revenue, Total Receipts ]
# Semantic View output
metrics:
- name: total_sale_price
expr: COALESCE(SUM("SALE_PRICE"), 0)
description: Total revenue of orders
synonyms: [ Total Revenue, Total Receipts ]
派生メジャー(aggregate_type なし)— 他のメジャーを参照します:
# Omni
gross_margin:
sql: ${total_sale_price} - ${total_cost}
label: Gross Margin
# Semantic View output — top-level derived metric
metrics:
- name: gross_margin
expr: total_sale_price - total_cost
フィルター付きメジャー → CASE WHEN でラップします:
# Omni
california_revenue:
sql: ${sale_price}
aggregate_type: sum
filters:
users.state:
is: California
-- expr value
COALESCE(SUM(CASE WHEN "users"."STATE" = 'California' THEN "SALE_PRICE" ELSE NULL END), 0)
配列およびブール値のフィルターバリエーション:
# is: [New York, New Jersey] → "STATE" IN ('New York', 'New Jersey')
# is: true → field IS TRUE
フィルター専用フィールド(テンプレート化されたフィルター)
Omni のフィルター専用フィールドは、ビュー YAML の filters: ブロックで定義するパラメーター化された入力です。実際の列には対応せず、クエリ実行時に Mustache テンプレートを介して他のフィールドの SQL に動的な値を挿入します。Snowflake Semantic Views には同等のランタイムパラメーターシステムがないため、変換時に静的 SQL として解決する必要があります。
フィルター専用フィールド定義の例:
# In a view file — under a top-level `filters:` key
filters:
status_filter:
type: string
default_filter:
is: "Complete"
order_date_filter:
type: timestamp
ディメンション/メジャーの SQL で検出する 2 つの参照パターン:
パターン 1 — ブロック(条件付き)構文:
{{# order_items.status_filter.filter }} order_items.status {{/ order_items.status_filter.filter }}
このブロックは、ユーザーがフィルターを適用した場合に条件付きで有効になります。{{^ }} ブロックは、フィルターが適用されていない場合のフォールバックです:
{{# order_items.status_filter.filter }} order_items.status {{/ order_items.status_filter.filter }}
{{^ order_items.status_filter.filter }} 1=1 {{/ order_items.status_filter.filter }}
パターン 2 — 値(直接挿入)構文:
-- Direct value substitution
DATE_TRUNC('day', created_at) >= '{{filters.order_items.order_date_filter.value}}'
-- Range variants for date filters
created_at BETWEEN '{{filters.order_items.order_date_filter.range_start}}' AND '{{filters.order_items.order_date_filter.range_end}}'
以下のシグネチャを使って、すべてのディメンションおよびメジャーの SQL から両方のパターンを検索します:
{{# <view>.<field>.filter }}— ブロック開始{{^ <view>.<field>.filter }}— フォールバックブロック開始{{filters.<view>.<field>.value}}— 直接値{{filters.<view>.<field>.range_start}}/{{filters.<view>.<field>.range_end}}— 日付範囲
解決ロジック — 検出した参照ごとに、次の順序で適用します:
ステップ 1 — {{^ }} フォールバックのインライン処理(ブロックパターンのみ):
SQL に {{^ view.field.filter }} フォールバックブロックが含まれている場合は、フォールバック式を静的 SQL として使用します。これはフィルターが適用されていない場合に実行される式です。
-- Input
{{# order_items.status_filter.filter }} order_items.status {{/ order_items.status_filter.filter }}
{{^ order_items.status_filter.filter }} 1=1 {{/ order_items.status_filter.filter }}
-- Resolved static SQL
1=1
ステップ 2 — フィルターフィールド定義の default_filter:
ビュー YAML で参照されているフィルター専用フィールドを見つけ、default_filter プロパティがあるか確認します。静的 SQL 条件としてレンダリングします:
default_filter の値 | レンダリングされる SQL |
|---|---|
is: "Complete" | field = 'Complete' |
is: ["New York", "CA"] | field IN ('New York', 'CA') |
is: true | field IS TRUE |
greater_than: 100 | field > 100 |
値構文の場合は、レンダリングしたスカラー値を SQL 文字列に直接代入します:
-- Input
DATE_TRUNC('day', created_at) >= '{{filters.order_items.order_date_filter.value}}'
-- default_filter: is: "2024-01-01"
-- Resolved
DATE_TRUNC('day', created_at) >= '2024-01-01'
範囲構文の場合は、default_filter に定義されていれば range_start/range_end を使用し、定義されていなければユーザーに警告します。
ステップ 3 — フォールバックも default_filter もない場合は、ユーザーに警告します:
⚠️ フィールド
<view>.<field>はフィルター専用フィールド<view>.<filter_field>を参照していますが、default_filterもインラインの{{^ }}フォールバックもありません。Snowflake Semantic View 用の静的な解決はできません。次の方法があります:
- このフィールドを出力から完全に除外します。
- 静的に設定する値または条件をユーザーに尋ねます。
- フィールドの形状を維持しつつフィルターの効果をなくすために、
1=1(常に真となる何もしない条件)を使用します。
判断フローの概要:
Does the SQL reference a filter-only field?
(via {{# view.field.filter }} OR {{filters.view.field.value}} etc.)
│
├── Block pattern → Does SQL have {{^ view.field.filter }} fallback?
│ ├── Yes → Use fallback expression as static SQL
│ └── No → Go to default_filter check ↓
│
├── Value pattern → Go to default_filter check ↓
│
└── default_filter check:
├── Yes → Render default_filter as static SQL / value substitution
└── No → Warn user, ask how to proceed
⚠️ フィルター専用フィールド自体は、Semantic View の出力でディメンション、メトリクス、フィルターとして出力されることはありません。これらは他のフィールドの SQL にパラメーターを指定するためだけに存在します。
AI コンテキスト → module_custom_instructions
トピックに ai_context パラメーターがある場合は、次のように含めます:
module_custom_instructions:
question_categorization: <ai_context value>
サンプルクエリ → verified_queries
sample_queries 配下の各項目を、verified_queries 用の SQL 文に変換します。prompt を question として使用し、SQL の作成には description をコンテキストとして使用します。
✋ 停止 — 最終出力を生成する前に、すべてのディメンション、メジャー、リレーションシップをユーザーと確認してください。
ステップ 7 — Semantic View を生成して実行する
出力 YAML の構造
name: <name>
description: <string>
tables:
- name: <name>
description: <string>
base_table:
database: <database>
schema: <schema>
table: <table name>
dimensions:
- name: <name>
synonyms: [ <string>, ... ]
description: <string>
expr: <SQL expression>
data_type: <data type>
unique: <boolean>
time_dimensions:
- name: <name>
synonyms: [ <string>, ... ]
description: <string>
expr: <SQL expression>
data_type: <data type>
facts:
- name: <name>
synonyms: [ <string>, ... ]
description: <string>
expr: <SQL expression>
data_type: <data type>
metrics:
- name: <name>
synonyms: [ <string>, ... ]
description: <string>
expr: <SQL expression>
filters:
- name: <name>
synonyms: [ <string>, ... ]
description: <string>
expr: <SQL expression>
relationships:
- name: <string>
left_table: <table>
right_table: <table>
relationship_columns:
- left_column: <column>
right_column: <column>
metrics:
- name: <name>
synonyms: [ <string>, ... ]
description: <string>
expr: <SQL expression>
verified_queries:
- name: <string>
question: <string>
sql: <string>
use_as_onboarding_question: <boolean>
有効な最上位キーのみ:
name、description、tables、relationships、metrics、verified_queries、module_custom_instructions
セマンティックビューを作成する
SQLは環境に関係なく同じです。異なるのは、実行方法です。
実行するSQL:
CALL SYSTEM$CREATE_SEMANTIC_VIEW_FROM_YAML('<database>.<schema>', $$
<yaml content here>
$$);
環境ごとの実行方法:
| 環境 | コマンド |
|---|---|
| Cortex Code | アクティブな Snowflake セッションで SQL を直接実行します |
snow CLI | snow sql -q "CALL SYSTEM$CREATE_SEMANTIC_VIEW_FROM_YAML(...)" を実行するか、YAML を一時ファイルに書き込んでパイプします |
| SnowSQL | snowsql -a <account> -u <user> -f <sql_file.sql> |
| Python | conn.cursor().execute("<sql>") |
CLI 環境でのヒント: YAML が長い場合は、インラインで渡すのではなく、最初に一時的な
.sqlファイルに書き込んでから実行してください。これにより、$$のドル引用符に関するシェルのエスケープ問題を回避できます。
# snow CLI example with a file
snow sql -f /tmp/create_semantic_view.sql
アクセス権を付与する
GRANT SELECT ON SEMANTIC VIEW <database>.<schema>.<name> TO ROLE <role>;
Runtime Environment Detection の手順で確立した接続方法を使用し、上記の CREATE 呼び出しと同じ方法でこれを実行します。