mcpField
O
omni-to-snowflake-semantic-view

omni-to-snowflake-semantic-view

github.com/exploreomni数据与数据库45124 次安装1 天前更新
代码仓库

关于 omni-to-snowflake-semantic-view

此技能指导将 Omni 主题转换为 Snowflake Semantic View YAML 定义,适用于在 Snowflake 中建模 Omni 数据的用户,涵盖模型探索、字段与关系映射及 SQL 执行。

主要功能

  1. 01探索 Omni 模型与主题
  2. 02处理字段纳入和排除规则
  3. 03映射维度、度量和连接关系
  4. 04提供 Snowflake SQL 执行指引

适用场景

  1. 01将 Omni 主题转换为 Snowflake Semantic View
  2. 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/Agent:不支持原生 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 或任何基于终端的 Agent,请选择以下一种方式。按顺序检查是否可用:

选项 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 步 — 收集需求

询问用户:

  1. 要转换哪个主题?
  2. Semantic View 应命名为什么?
  3. 应在 Snowflake 的哪个位置创建(数据库和架构)?
  4. 应向哪个角色授予 Semantic View 的访问权限?

⚠️ 停止——继续之前,确认以上四个答案。


第 2 步 — 探索 Omni 模型
2a. 查找模型 ID
omni models list --model-kind SHARED

找到共享模型并记下其 id。始终优先选择共享模型,而不是架构模型或工作簿模型。

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 / 联接值Snowflake 表
ecomm__order_itemsECOMM.ORDER_ITEMS
omni_dbt_ecomm__order_itemsECOMM.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

跳过任何作为派生表的视图(在 Omni 的 SQL 中定义的 CTE)。这些视图没有可供引用的 Snowflake 物理表。

主键

在每个视图文件中,找到 primary_key: true 的维度——它会成为 Semantic View 中 unique: true 的维度。

✋ 停止——继续之前,请与用户确认表列表。


第 4 步 — 确定字段列表

主题的 fields 参数用于控制 Semantic View 中包含哪些视图字段。

字段定位规则
语法含义
(无 fields 参数)包含所有视图中的全部字段
all_views.*包含所有视图中的全部字段
view.*包含指定视图中的全部字段
tag:<value>包含带有此值标签的全部字段
view.field包含此特定字段
-view.field排除此特定字段
如何正确应用排除项

排除项(以 - 为前缀)必须在解析完所有包含项之后应用。处理流程如下:

  1. 从空的包含集合开始
  2. 按顺序处理 fields 中的每一项:
    • 如果是包含规则(view.*、view.field、tag:x)→ 将匹配的字段添加到集合中
    • 如果是排除规则(-view.field)→ 从集合中移除该字段,即使它已通过通配符添加
  3. 最终集合就是要包含在 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_typeSQL 联接类型(例如 always_left)
on_sqlSQL 条件——从中提取列名
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

度量 → 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 中检测以下两种引用模式:

模式 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: truefield IS TRUE
greater_than: 100field > 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. 完全跳过此字段,不将其纳入输出。
  2. 询问用户要硬编码的静态值或条件。
  3. 使用 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,并将 description 作为编写 SQL 的上下文。

✋ 停止 — 在生成最终输出之前,与用户确认所有维度、度量和关系。


步骤 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


创建 Semantic View

无论环境如何,SQL 都相同。不同之处在于执行方式。

要运行的 SQL:

CALL SYSTEM$CREATE_SEMANTIC_VIEW_FROM_YAML('<database>.<schema>', $$
<yaml content here>
$$);

各环境中的执行方式:

环境命令
Cortex Code在当前 Snowflake 会话中直接执行 SQL
snow CLIsnow sql -q "CALL SYSTEM$CREATE_SEMANTIC_VIEW_FROM_YAML(...)" 或将 YAML 写入临时文件并通过管道传入
SnowSQLsnowsql -a <account> -u <user> -f <sql_file.sql>
Pythonconn.cursor().execute("<sql>")

提示(适用于 CLI 环境): 如果 YAML 很长,请先将其写入临时 .sql 文件,然后执行该文件,而不要以内联方式传入,这样可以避免因 $$ 美元符号引用而产生的 shell 转义问题。

# 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 调用相同的方式执行此操作。


参考资料

评论

支持 Markdown