mcpField
O
omni-model-builder

omni-model-builder

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

关于 omni-model-builder

omni-model-builder 是通过 YAML API 创建和修改 Omni 语义模型的技能,适用于负责定义组织级数据模型的用户。它支持在分支中构建并验证视图、主题、维度、度量和关系,再由用户决定是否合并。

主要功能

  1. 01通过 YAML 编辑语义模型
  2. 02创建视图、主题和关系
  3. 03定义维度与度量
  4. 04在分支中安全开发
  5. 05验证模型并测试查询

适用场景

  1. 01为分析添加可复用的数据字段
  2. 02在分支中准备模型改动
  3. 03验证新增字段与查询结果
  4. 04同步数据库结构变更

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/exploreomni/omni-agent-skills --skill omni-model-builder

安装后在 Claude Code 中输入/omni-model-builder确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

evals/
evals.json
references/
aggregate-awareness.md
composite-topics.md
level-of-detail.md
modelParameters.md
query-view-examples.md
schema-refresh.md
templated-filters.md
topic-scoped-relationships.md
topic-scoped-views.md
validation-and-testing.md
yaml-filter-syntax.md

详细介绍

Omni 模型构建器

通过 YAML API 创建和修改 Omni 的语义模型 — 视图、主题、维度、度量、关系和查询视图。

提示:始终先使用 omni-model-explorer 了解现有模型。

先决条件

# 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** 规则。

你需要 Modeler 或 Connection Admin 权限。在任何命令中添加 -o json,即可强制输出结构化数据以供解析(默认的 auto 在 TTY 中输出人类可读内容,通过管道传输时则输出 JSON)。

Omni 的分层建模架构

Omni 采用分层方法,每一层都建立在上一层之上:

  1. 架构层 — 根据数据库自动生成,反映表、视图、列及其类型。通过架构刷新保持同步。
  2. 共享模型层 — 受治理的语义模型。在此定义可供整个组织重复使用的维度、度量、联接和主题。
  3. 工作簿模型层 — 各个工作簿中的临时扩展。在将实验性字段提升到共享模型之前,可在此使用。
  4. 分支层 — 中间开发层。在分支中处理更改并将其合并到共享模型之前使用。

关键概念:架构层是表/列结构的事实来源(数据库发生变化时刷新);所有用户创建的内容(维度、度量、关系、主题)都通过共享模型层流转。你可以在分支中构建和修改模型(见下文“安全开发工作流”),然后再将更改合并回共享模型。

确定 SQL 方言

编写任何 SQL 表达式之前,请从连接信息中确认方言 — 不要根据连接名称猜测:

# 1. List models to find connectionId
omni models list

# 2. Look up the connection's dialect
omni connections list
# → find your connectionId and read the "dialect" field
# → e.g. "bigquery", "postgres", "snowflake", "databricks"

在 SQL 中使用适合相应方言的函数(例如,BigQuery 使用 SAFE_DIVIDE,Postgres/Snowflake 使用 a / NULLIF(b, 0))。

创建新的共享模型(少见)。大多数工作都基于现有模型 — 但如果要使用 omni models create 创建模型,请使用 { modelKind: "SHARED", connectionId } 作为请求正文(不要包含 baseModelId;它会继承连接的架构视图和假定关系 — 运行 omni models create --schema 查看完整字段列表)。易踩的坑:创建时使用 modelName,更新时使用 name。 创建时传入 name 会被静默忽略,模型名称将根据连接生成 — 之后你就得运行 omni models update <id> --body '{"name":"…"}' 来修正。创建时传入 modelName,不要再重命名。

删除模型(CLI ≥ 1.2.2)。omni models delete <modelId> 会将共享模型或共享扩展模型移至回收站,并一并处理基于它构建的工作簿、仪表板和子扩展模型 — 运行前请与用户确认影响范围。此操作需要 Connection Admin 权限。它不会删除分支:删除分支请使用 omni models delete-branch。

架构刷新:与数据库更改同步

架构层根据数据库自动生成。当数据库架构发生变化(新增/删除/重命名列、类型变化)时,请刷新架构以保持同步:omni models refresh <modelId>(添加 --branch-id <branchId> 可限定到某个分支;需要 Connection Admin 权限)。

有关何时触发刷新、刷新内容及其副作用、已删除/重命名列的影响检查工作流,以及连接/凭据错误处理,请参阅 references/schema-refresh.md。

已知问题与安全默认值

🛑 严格禁止:绝不可主动合并或发布分支

omni models merge-branch(以及任何将内容合并、发布或推送到共享模型的操作)都是单独的、由用户发起的步骤。除非用户在当前对话中明确要求你合并、推送、发布、提升或“正式上线”,否则不得运行此命令。对于“构建/添加/建模”请求,准备分支(创建 → 编写 YAML → 验证 → 测试)就是全部工作;是否发布是用户单独决定的事项。

以下情况均不构成合并许可——不得因此绕过此限制:

  • “这项更改只是增补/很小/风险低/不会造成任何问题”
  • “规范(或工单、文档)要求建模并发布”
  • “用户笼统地要求‘把整个东西都构建好’”
  • “交付物要正常工作就必须这样做”/“不合并的话,仪表板就无法解析”
  • “这只是一个试验场/沙箱/非 git 模型”

下游内容需要某个字段/视图,但尚未合并时,正确做法是创建绑定分支的草稿(omni-content-builder → 绑定分支的草稿),而不是合并。确实需要合并时,停下来询问(“要我将 <branch> 合并到共享模型吗?”),并等待明确答复。如果执行环境/权限层阻止合并,说明安全防护正在正常发挥作用——如实说明并询问;不得寻找其他途径绕过限制。

  • 每次会话开始时及任何间隔后,都要确认分支仍然存在——omni models list --model-id <sharedModelId> --include activeBranches。其他人可能已经合并了该分支;此后写入会失败并显示 Branch does not exist,绑定到该分支的草稿也可能已经发布(检查 omni documents list-drafts <identifier>)。如果分支已合并,请将本地 YAML 与共享模型进行差异比较,并为后续更改创建新分支。
  • 建模前先分析数据。 检查粒度(对每个事实表使用 COUNT(*) 与 COUNT(DISTINCT <key>) 进行比较)、隐含单位(按类别计算数值 ÷ 数量,以识别存放金额却标为“小时”的列),以及历史数据的起始时间(较早的记录可能需要期初余额)。对于含义不明确的术语(如“费用类型”“状态”),先询问业务负责人,不要凭猜测开展工作。
  • 确认之前,将评估创建的文件保留在分支上——创建字段/视图以供验证时,报告分支名称/ID、验证状态和测试查询结果;合并仍受上述严格禁止条款约束。

查找命令

omni models --help                # List all model operations
omni models yaml-create --help    # Show flags for writing YAML
omni models yaml-create --schema  # Print the body's JSON schema + a filled example (no token)

安全开发工作流

始终在分支上工作——绝不直接写入生产环境——并且先确认你有权限创建分支。 创建分支需要完整模型访问权限。运行 omni whoami whoami --model-id <modelId>:存在 QUERY_FULL_MODEL → 你可以创建分支(若也存在 UPDATE,则可以合并/发布;否则请创建 PR/请求合并);不存在 → 你无法创建分支——一次性字段应放在文档的工作簿模型中(omni-content-builder → 更新仪表板的模型)。完整的权限→能力对应关系:omni-admin → 模型角色与调用方访问权限。

步骤 0:创建分支
omni models create-branch <modelId> --name "my-feature-branch"

响应中的 model.id 就是你的 branchId——一个 UUID,你需要在后续所有 API 调用中传入它。要随时列出已有分支:

omni models list --include activeBranches

⚠️ 连接 Git 的模型——切勿在 Git 中手动编辑模型 YAML。 仓库是 Omni 模型的投影,用于治理(PR、审查、审计),而非事实来源,也不是编写内容的地方。Omni 会根据自身的权威状态重新生成默认分支,并删除该状态中不存在、仅存在于 Git 的模型文件——曾发生过一起客户事故:仅提交到 Git 的文件在下一次同步时悄然消失。因此,请通过 Omni API 在分支上编写 → omni models commit(步骤 3)→ 在 Git 提供商中审查/合并;切勿直接推送手动编辑的模型 YAML。

模型内容与仓库治理——以下划分可确保安全:

  • Omni 模型内容(视图 / 主题 / 关系 YAML):在 Omni 分支上编写 → omni models commit → PR。切勿直接在 Git 中提交——Omni 会重新生成并覆盖这些内容。
  • 仓库治理 / 非模型文件(根目录下的 omni/OWNERS.yaml、CODEOWNERS、CI 配置、文档、脚本):不属于 Omni 模型内容,因此可以直接通过 Git 提交——Omni 重新生成时会保留这些文件。
步骤 1:将 YAML 写入分支
omni models yaml-create <modelId> --body '{
  "fileName": "my_new_view.view",
  "yaml": "dimensions:\n  order_id:\n    primary_key: true\n  status:\n    label: Order Status\nmeasures:\n  count:\n    aggregate_type: count",
  "mode": "extension",
  "branchId": "{branchId}",
  "commitMessage": "Add my_new_view with status dimension and count measure"
}'

注意:branchId 参数必须是服务器提供的 UUID(步骤 0)。如果传入字符串名称,则会返回 400 Bad Request: Unrecognized key: "branchName"。

⚠️ 编辑现有文件 = 在其确切路径上对整个文件进行读取-修改-写入。 yaml-create 会替换文件中编写的内容(不会逐字段合并),因此请先运行 yaml-get 获取文件,完成编辑后再写回完整文件,否则其他已编写字段会被丢弃。fileName 是文件的确切路径(不是正则表达式,与读取时不同)——请原样复用完整路径键,包括文件夹前缀(例如 MARTS/fct_ai_events.view);名称不匹配时不会报错,而是会在该路径上悄然创建重复文件(success: true)。(架构基础列位于架构层,因此不受影响。)要检查分支,不带 --file-name 运行 yaml-get 会枚举整个模型——使用 --mode extension 仅查看分支中已更改的文件(你的差异),使用 --mode combined 查看完整的组合模型(架构 + 共享内容 + 分支);然后通过文件的确切路径深入查看任意文件。

连接 dbt 的模型:omni models branch-dbt-get <modelId> <branchName>(CLI ≥ 1.1.2)可读取分支所解析到的 dbt 环境,包括其编译所依据的 dbt Git 分支。没有自身环境的分支会解析到连接的默认(生产)环境,并以 is_default_environment: true 报告。

第 2 步:验证和测试

每次写入 YAML,都必须在合并前进行验证和测试——字段即使是有效的 YAML,也可能产生错误结果或导致查询出错。

# 1. Validate — a NEW issue with is_warning:false that references your changed file is blocking; fix before proceeding
omni models validate <modelId> --branch-id <branchId>

# 2. Query the fields you changed — confirm no error, and cache_metadata.num_rows > 0
#    (the row count is at cache_metadata.num_rows; there is NO summary.row_count — see omni-query)
omni query run --body '{"query":{"modelId":"<modelId>","table":"your_view","fields":["your_view.new_dimension","your_view.new_measure"],"limit":10,"join_paths_from_topic_name":"your_topic"},"branchId":"<branchId>"}'

# 3. Read it back — confirm the field is present (and not duplicated at a second path)
omni models yaml-get <modelId> --file-name your_view.view --branch-id <branchId>

分类处理验证错误——大多数错误并非由你造成。 对于全新或继承而来的模型,阻塞性的 not found 错误(缺少表/视图/列)通常意味着架构已过时:先运行架构刷新(omni models refresh <modelId>)——刷新会清除这些错误;刷新后仍然存在的 not found 才是真正的问题(连接缺少数据库访问权限,或引用确实有误)。对于剩余问题,先在更改前建立基线(或按 yaml_path 筛选问题),仅将引用了你所修改文件的新增错误视为阻塞问题——报告预先存在的问题,不要追查它们。

抽查数值是否正确(sum 是否返回了 count;布尔值是否显示为 true/false)。如果字段引用了另一个视图,请同时加入两个视图中的字段,确认联接解析成功。有关联接路径测试、通过 omni ai job-submit 进行自然语言验证、完整结果检查清单和重复文件恢复,请参阅 references/validation-and-testing.md。

结果中类似窗口函数的列是表计算,不是模型字段。 累计总和、移动平均、环比/月环比百分比、占比或排名,都是针对每个查询的结果集计算的——应按照 omni-query → references/table-calculations.md,将其编写为表计算(query.calculations[]),而不是在此处编写为度量/维度。只有当窗口必须跨越结果集以外的行时,才将其建模为仓库中的字段。

第 3 步:发布分支

**重要:**发布前务必征求用户确认。对生产模型应用的更改不容易撤销。只有通过验证和测试(第 2 步)后,才能发布。

检查模型是否已连接 Git——omni models git-get <modelId>。配置中有 sshUrl / baseBranch → 已连接 Git → 路径 A(创建/更新 PR);返回 404/无配置 → 未连接 Git → 路径 B(直接在 Omni 中合并)。

路径 A——已连接 Git:创建或更新 PR

将分支内容推送到 Git。如果不存在对应的 Git 分支和 PR,则创建新的 Git 分支和 PR;否则更新现有 PR:

omni models commit <modelId> --body '{
  "branch_id": "<branchId>",
  "commit_message": "Add my_new_view with status dimension and count measure"
}'

向用户展示返回的 pr_url。审核者在你的 Git 托管平台中合并 PR;更改会在下次同步时流回 baseBranch。需要强制仅创建或仅更新时,请运行 omni models commit --help,查看可选的 body 标志(allow_branch_exists、require_branch_exists)。

路径 B——未连接 Git:在 Omni 中合并
omni models merge-branch <modelId> <branchName>
合并后——验证新增主题和视图(两种路径均适用)

合并后,Omni 会根据自身的(权威)模型状态重新生成默认分支——重新序列化为规范格式,并且仅当合并的 Git 内容与该状态不同时,才会添加规范化提交。通过 Omni API 编写的内容已经符合规范,因此干净的合并通常不会产生额外提交——不要特意查找;请通过解析验证。无论哪种情况——尤其是新增主题或视图时——都要确认文件能针对生产模型解析(不要使用 --branch-id):

# 1. Files resolve in production — the new view appears / the new topic resolves
omni models get-views <modelId>                 # new view is listed
omni models get-topic <modelId> <topicName>     # new topic resolves (base_view_name + join_via_map present)

# 2. Production model validates — no blocking errors (any is_warning:false is blocking)
omni models validate <modelId>

# 3. Run at least one semantic query against the new topic/view
omni query run --body '{"query":{"modelId":"<modelId>","table":"<base_view>","fields":["<base_view>.<field>"],"limit":10,"join_paths_from_topic_name":"<topicName>"}}'

在查询响应中,确认 summary.missing_fields 为 [](且 summary.invalid_calculations 为空——注意其返回值是 {},而不是 [])。missing_fields 非空表示某个字段未能解析——这是模型文件在合并期间被丢弃或重命名的迹象。如果有字段缺失,请通过 Omni 重新编写;切勿在 Git 中手动将其补回。

YAML 文件类型

类型扩展名用途
视图.view表的维度、度量和筛选条件
主题.topic将多个视图联接为可查询的单元
关系(特殊)全局联接定义
复合主题.composite_topic根据共享维度联接两个或更多主题(参见 composite-topics.md)

使用 mode: "extension" 编写(共享模型层)。如需删除文件,请发送空的 yaml。

编写视图

每个参与联接的视图都必须包含一个真实的 primary_key: true 维度。 如果没有真正能唯一标识每一行的主键,联接到此视图的查询可能会产生扇出错误或不正确的聚合。请使用表的自然唯一标识符(例如 id、order_id、user_id)。如果没有单个唯一列,请使用多个行级列构建复合键,这些列组合起来具有唯一性,例如 sql: ${order_id} || '-' || ${line_number}。如果无法定义唯一标识每一行的表达式,请暂时不要将维度标记为 primary_key: true;应先修正粒度,或避免在存在真实主键之前联接此视图。

主键在机制层面的重要性——对称聚合。 Omni 会通过基于主键去重,确保在一对多联接中 sum/count/avg 结果正确(对称聚合)。缺少主键的视图无法实现对称聚合,因此其中的行数 count 度量在联接导致行重复时会膨胀;count_distinct 度量则只因其本身会去重而不受影响。诊断经验法则: 当某个度量值超过了它本应是其子集的度量值(例如 “查看过某个产品的会话数” > “会话总数”)时,应怀疑具有缺失或不可靠主键的视图中的非去重计数发生了扇出——修复主键,不要通过调整数字来掩盖问题。请审查每个基础视图是否都有主键(缺少一个主键就足以导致一个可视化出错)。请注意,为满足字段需求,仪表板筛选器/控件可以通过全局 relationships 图引入主题未声明的联接——因此,即使是“单主题”图块也可能发生扇出;无论如何,主键都能确保聚合结果安全。

基本视图
dimensions:
  order_id:
    primary_key: true
  status:
    label: Order Status
  created_at:
    label: Created Date
measures:
  count:
    aggregate_type: count
  total_revenue:
    sql: ${sale_price}
    aggregate_type: sum
    format: currency_2
理解 Schema 层与扩展层

创建视图时,Omni 会将架构(数据库结构)与模型(业务逻辑)分开:

  • 架构层:自动生成基础维度,每列一个。类型来自数据库。只读,通过架构刷新同步。
  • 扩展层:自定义 YAML。可以覆盖基础维度、添加新维度/度量、隐藏列以及添加业务逻辑。

当两个层中都存在同名字段时,以你的扩展定义为准,但类型信息来自架构层。

示例:表中有 created_at(DATE)和 revenue(NUMERIC)两列。

# Schema layer (auto-generated)
dimensions:
  created_at: {}  # type: DATE, auto-generates timeframes
  revenue: {}     # type: NUMERIC

# Extension layer (your YAML)
dimensions:
  created_at:
    label: "Order Created"
    description: "When the order was placed"

  revenue:
    hidden: true  # Hide the raw column

measures:
  total_revenue:
    sql: ${revenue}
    aggregate_type: sum
    format: currency_2

结果:created_at 从架构层继承类型(DATE,并自动获得周/月/年粒度),但使用你设置的标签。原始 revenue 列会被隐藏,只通过 total_revenue 度量公开。

关键提示:如果你的扩展定义了维度,但架构层中没有可提供类型信息的基础维度,Omni 就无法推断粒度或类型。先触发架构刷新,自动生成架构层。

读取你写入的内容 — --mode。 默认情况下,yaml-get 返回合并后的结果:将架构基础与扩展合并。若要仅查看你编写的差异,而不包含自动生成的基础列,请使用 --mode extension 读取:

# What you authored (deltas only)
omni models yaml-get <modelId> --file-name your_view.view --branch-id <branchId> --mode extension

# What the model actually resolves to (schema + extension merged) — the default
omni models yaml-get <modelId> --file-name your_view.view --branch-id <branchId> --mode combined

使用 extension 确认你做了哪些更改,使用 combined 确认模型最终解析成什么样。模型与 git 集成时,combined 输出会与写入仓库的内容一致 — 这就是为什么已提交的 *.view.yaml 文件包含架构层的 table_name: 和基础列,而扩展层仅包含你的差异。对于同步到 git 的共享扩展模型,文件只包含扩展层内容,也就是 --mode extension 返回的内容。其他 --mode 值:staged 仅返回分支模型;merged 返回与共享模型合并后的分支模型,格式与 extension 相同;fully-resolved 返回展开 extends 链后的 combined(等同于 --mode combined --fully-resolved true)。

维度参数

完整的 35+ 个维度参数、格式值和时间范围,请参阅 references/modelParameters.md。

最常用的参数:sql、label、description(Blobby 也使用)、primary_key(唯一键 — 对聚合至关重要)、hidden(从选择器中隐藏,但仍可在 SQL 中使用)、format(number_2/currency_2/percent_2/id)、group_label、synonyms(用于 AI 匹配的别名)。 两个注意事项:原始列会按名称自动映射(无需 sql:);没有 ${TABLE} 结构 — ${TABLE}.column 会在验证/查询时触发 Column "__omni_scoped" not found 错误(通过 ${field}/${view.field} 引用字段)。

度量参数

请参阅 references/modelParameters.md(24+ 个参数,全部 13 种聚合类型)和 references/yaml-filter-syntax.md(筛选运算符和度量筛选示例)。对于筛选聚合,优先使用度量的 filters: 块,而不是在 sql 中使用 CASE WHEN/WHERE — 让 sql 专注于要聚合的值:

有关明细级聚合(维度和度量上的 fixed / always_include / always_exclude),请参阅 references/level-of-detail.md。

measures:
  completed_revenue:
    sql: ${sale_price}
    aggregate_type: sum
    filters:
      status:
        is: Complete
仅用于筛选的字段和动态字段

当一次用户选择应改变字段的计算方式而非保留哪些行时,使用仅用于筛选的字段:例如按创建日期或发货日期生成报告,在同一 KPI 中显示收入或订单数,或应用由查看者设置的阈值。这是一个视图级 filters: 条目,不包含列,其他字段通过 Mustache 读取它。要隐藏或显示字段,不要使用它(请使用主题 fields:);要筛选行,也不要使用它(请使用普通筛选器);如果两个变体各自都值得拥有独立主题,也不要使用它(参见“新建主题还是扩展主题”)。字段结构、绑定和验证方式,请参阅:templated-filters.md。

汇总表(汇总感知)

当同一事实汇总经常以比表粒度更粗的粒度进行查询(例如针对事件级事实表查询每日或每月汇总),且已有或可以构建预聚合表时,应使用汇总表。在视图上通过 materialized_query 块声明,每张表对应一个块,并基于基础视图进行描述;查询符合条件时,Omni 会读取该表,否则回退到事实表。以下情况不会带来帮助:查询需要表中没有的列;在其他粒度上使用非可加度量(average、count_distinct、median);或者查询粒度比表更细。使用以 -- Query rewritten to use materialized view 开头的 planOnly 查询进行确认。绝不要映射窗口函数字段(ROW_NUMBER、RANK、LAG、运行总计)或 *_distinct_on 度量,也绝不要在 materialized_query 中设置 limit 或 offset:两者都会导致结果错误或使 Omni 无法使用该表。将只保存部分历史数据的表限定在相应日期范围内,基础视图中保留所有行,并在模型 SQL 中使用 >= 和 < 编写日期边界。规则、筛选器限定、连接视图、不应声明的内容和已知问题:aggregate-awareness.md。

详细级别

当度量必须按不同于查询粒度的粒度计算时,使用 level_of_detail:在每个订单行上显示客户总额(fixed:)、汇总更细粒度的聚合(always_include:),或在报表包含更细维度时将金额保持在更粗粒度(always_exclude:)。指定报表实际使用的粒度——只有当列出的字段出现在报表中时,fixed: 才会在所有未列出字段上保持不变;列出报表未使用的字段会导致度量重新拆分。always_exclude 会根据当前存在的较粗粒度字段调整,并允许列出多余字段,但需要使用幂等的外层聚合(max,而非 sum)。它不能通过 always_include 限制层级,列表中也只能使用字段引用,不能使用通配符。对于在行明细中重复显示的表头金额,可考虑使用带有 unrelated_dimension_handling: repeat 的复合主题;它不需要列表。粒度匹配、外层聚合规则、模板复用和验证:level-of-detail.md。

视图中的跨视图字段

全局视图文件中的跨视图字段(其维度或度量的 sql 引用了 ${other_view.field})会在所有公开宿主视图的主题中进行解析。全局关系可以让依赖在继承全局连接图的主题中可用,但不会覆盖主题显式或冻结的 joins: 映射。如果该映射未包含此依赖,验证会返回阻断性 field_broken_in_topic 问题,使用该字段的查询也无法通过规划。

字段的放置位置取决于其预期可用范围和解析后的连接上下文,而非字段命名空间。 如果字段仅在特定主题中有意义,或依赖主题特定、别名化或冻结的连接,请在主题的 views.<host_view> 块中定义该字段(参见“主题范围的视图定义”)。该字段仍可通过 <host_view>.<field> 查询;主题范围限定的是字段的可用位置,而非用户请求的命名空间。

如果字段在所有场景中都有意义,且其依赖在所有公开宿主视图的主题中都能明确解析,请使用全局视图文件。选择之前,列出这些主题并检查每个主题解析后的 get-topic join_via_map——不要只检查全局关系文件——然后验证该分支,并在每种不同的连接上下文中至少查询一个主题。未显式定义连接的主题可能会继承全局关系;显式定义连接的主题则可能会将其排除。

回退:yaml-get 中缺少视图

在得出视图不存在的结论之前,始终执行以下两步检查。yaml-get 只会返回当前已加载架构中的视图——已卸载或未激活架构中的视图不会显示,但仍然可用。

# 1. List all schemas the connection knows about (loaded, offloaded, and inactive)
omni models get-schemas <modelId>
# → {"schemas": ["ANALYTICS", "PUBLIC", "STAGING", ...]}

# 2. If the target schema appears in the list, load it explicitly
omni models yaml-get <modelId> --include-schemas PUBLIC

--include-schemas 的规则:

  • 每次调用只接受一个架构名称——不接受逗号分隔的名称。每次加载一个架构。
  • 响应只包含该架构中的视图;与其他架构的关系会保留。
  • 要限定到某个分支,请在 yaml-get 或 get-schemas 中添加 --branch-id <id>。

如果 get-schemas 列表中完全没有该架构,则此连接可能无权访问该架构,或者该架构尚未同步——请联系连接管理员。

编写主题

编写主题之前,请确认你计划引用的所有视图确实存在。 运行 omni models yaml-get <modelId> 并确认其中出现了每个视图。如果缺少某个视图,在断定其不存在之前,请先运行上面的延迟加载回退步骤——它可能只是在已卸载的架构中。

新建主题还是扩展现有主题

如果现有主题无法回答某个查询,请先检查是否只需扩展现有主题,而不是新建主题。当请求的基础视图(FROM)与现有主题的基础视图一致时,通常应该扩展主题——例如,添加关系或连接,让所需视图变得可访问,或者添加字段或标签。当以下任一方面存在根本差异时,新建主题:

  • 主体/对象——不同的基础视图(FROM)。查询从根本上描述的是什么——订单?用户?时间(日期)?核心实体不同,就应建立独立主题。
  • 约束条件——始终应用的条件(例如,从订单数据中排除测试用户的 always_where)。始终生效的筛选条件不同,就应建立不同主题。
  • 受众——字段相同,但面向不同使用者时使用不同标签/术语;受众不同导致术语不同,足以成为单独建立主题的理由。

如果需要自行判断,请向请求者确认,并在分支上构建新主题(参见上文的安全开发工作流)。请注意,相较于直接查询基础视图,基于主题进行查询也能让受限查询者/查看者访问结果。

有关包含连接、字段和 ai_context 的完整 YAML 示例,请参阅 主题设置;有关所有可用选项,请参阅主题参数。

主题的关键元素:

  • base_view — 此主题的主要视图
  • joins — 用于连接链的嵌套结构(例如,users: {} 或 inventory_items: { products: {} })
  • ai_context — 指导 Blobby 进行字段映射(例如,“将‘收入’映射到 total_revenue”)
  • default_filters — 应用于所有查询,除非被移除
  • always_where_sql — 使用 SQL 表达式且不可移除的 WHERE 筛选条件(用户无法移除)
  • always_where_filters — 使用筛选条件规范且不可移除的 WHERE 筛选条件(用户无法移除)
  • always_having_sql — 使用 SQL 表达式且不可移除的 HAVING 筛选条件,在聚合后应用(用户无法移除)
  • always_having_filters — 使用筛选条件规范且不可移除的 HAVING 筛选条件,在聚合后应用(用户无法移除)
  • fields — 字段整理:[order_items.*, users.name, -users.internal_id]
主题的筛选表达式

在主题上配置 default_filters、always_where_filters 或 always_having_filters 时,请使用 YAML 筛选条件语法——与度量筛选器使用的语法相同。完整参考请参阅 references/yaml-filter-syntax.md。

如果不确定某个用例适合采用哪种筛选配置,请使用 Omni AI CLI 搜索文档:

omni ai search-omni-docs "how do I configure always_where_filters on a topic in Omni?"

编写模型 YAML 之前,请针对具体的筛选需求提出有针对性的问题,以获取精确的 YAML 示例。

复合主题

当一个查询必须放入来自两个共享维度但没有连接关系的事实表中的度量(按月统计销售额和退货量),或同一事实表在两种条件下的度量(本期和上期、按创建时间统计和按发货时间统计)时,使用复合主题。 基数决定处理方式:如果筛选表相对于汇总事实表是多对一关系,则它是维度,应进行连接;如果是一对多关系,则它是汇总到事实表粒度的查询视图;只有未连接的事实表才需要复合主题。如果一个带连接的主题就能回答问题,或第二个度量只是第一个度量的筛选变体,就不要使用复合主题。有关编写方式、双透镜 extends 模式和以 @ 寻址的查询格式,请参阅:composite-topics.md。

编写关系

全局关系

全局关系在共享关系文件中定义,可供所有主题使用。标准且可复用的连接应使用全局关系。

- join_from_view: order_items
  join_to_view: users
  on_sql: ${order_items.user_id} = ${users.id}
  relationship_type: many_to_one
  join_type: always_left
类型使用场景
many_to_oneOrders → Users
one_to_manyUsers → Orders
one_to_oneUsers → User Settings
many_to_manyTags ↔ Products(少见)

正确设置 relationship_type 可防止扇出和对称聚合错误。

主题级关系

定义前,请检查全局关系文件,确认其中是否已定义相同两个视图之间任一方向的连接。on_sql 相同 → 属于冗余,仅使用 joins:。on_sql 不同 → 默认使用下方的扩展视图模式,而不是静默覆盖。请与建模人员确认意图。

对于共享模型中未定义的一次性连接,或需要在不同条件下多次连接同一张表时,请使用主题级关系。

# .topic file
relationships:
  - join_from_view: order_items
    join_to_view: users
    on_sql: ${order_items.user_id} = ${users.id}
    relationship_type: many_to_one
    join_type: always_left

joins:
  users: {}

joins 与 relationships: joins 声明主题中包含哪些视图及其层级;relationships 定义连接条件。只使用全局关系的主题只需 joins。包含一次性连接的主题则两者都需要。

容易忽略的陷阱——两者缺一不可,而且省略 joins 也能通过验证。 如果添加了 relationships 条目,却忘记添加视图的 joins 条目,模型仍会通过验证(没有错误),但连接视图的字段会被静默地排除在主题之外(引用这些字段的查询或 Markdown 会返回空结果)。不要依赖 validate 来检查这一点;请使用 get-topic 确认字段确实已公开(参见 validation-and-testing.md)。(主题文件的结构各不相同:有些会在 joins: 映射下列出视图,有些则使用顶层 <view>: {} 条目——请沿用现有文件的格式。)

扩展视图:以多种方式连接同一张表

如果同一张表需要多种连接方式(例如,将 users 分别作为买家和卖家),请使用扩展视图模式,而不是 join_to_view_as。此模式有两种变体:

变体 1——全局(可复用): 创建一个独立的 .view 文件,使用 extends:、描述角色的名称以及 description:。在全局定义关系后,任何主题都可以像连接其他视图一样连接它。

变体 2——主题级(内联): 在主题的 views: 块中定义别名,并在同一文件中定义其关系。当别名不适用于其他主题时使用。

两种变体的完整 YAML 示例请参阅 references/topic-scoped-relationships.md。

如果看到 relationship alias duplicates view name 错误,请使用此模式解决。

主题级视图定义

主题可以使用 views: 块内联定义或覆盖视图 — 控制 display_order、覆盖 label、添加主题专属的筛选度量或派生维度、定义跨视图字段,以及通过各自的别名条件,以多种方式连接同一视图。

向现有视图添加任何主题级字段之前:

  1. 读取视图 YAML(omni models yaml-get),并确认该字段尚不存在。如果已存在且定义相同,则跳过。
  2. 如果存在同名字段,但使用了不同的 SQL,这属于覆盖。请与建模人员明确确认 — 通过此主题执行的查询将使用主题级定义;其他所有主题仍使用共享定义。
# Example: display order + topic-specific filtered measure
views:
  order_items:
    display_order: 0
    measures:
      us_revenue:
        sql: ${sale_price}
        aggregate_type: sum
        format: currency_2
        filters:
          users.country:
            is: US

如需查看完整的模式示例集(标签覆盖、派生维度、跨视图字段、多重连接生命周期、主题级查询视图),请参阅 references/topic-scoped-views.md。

views: 块中的跨视图字段: 在编写 ${view_name.field_name} 引用之前,请确认 joins: 块中声明了所有被引用的视图 — 对未连接视图的任何引用都会导致模型验证器报错。

以多种方式连接同一视图(例如,起始 / 当前 / 结束时的 ARR):在主题的 views: 块中使用 extends: 创建命名别名,并在 relationships: 中为每个别名指定各自的 on_sql。每个别名都会继承所有基础视图字段,并可独立覆盖标签。完整的 YAML 示例请参阅 references/topic-scoped-views.md。

主题级查询视图: 也可以在主题的 views: 块中定义查询视图,使其仅作用于该主题。适用相同的主键规则(primary_key: true 或 custom_compound_primary_key_sql)。为新视图添加 relationships: 条目和 joins: 条目 — 请参阅查询视图,以及 references/topic-scoped-views.md 中的完整示例。

查询视图

由已保存的查询定义的虚拟表。查询视图必须有主键,否则联接时会产生扇出错误。写入前,请确认哪个字段能唯一标识每一行——除非可以从查询本身和涉及的视图中明确推断出主键(例如,从 users 视图中选择 user_id,且已知 user_id 是主键的查询)。

定义主键有两种方式:

**选项 1 — 单个唯一字段:**在 dimensions: 块中将且仅将一个维度标记为 primary_key: true。

**选项 2 — 复合键:**如果没有单个字段唯一,但字段组合可以唯一标识行,则在视图级别设置 custom_compound_primary_key_sql: [field_a, field_b]——无需设置 primary_key: true 维度。

这两种方式都适用于 query: 块(字段映射虚拟表)或 sql: 块(原始 SELECT)。在 sql: 块中,使用 ${view_name} 引用视图的底层表,而不是硬编码 CATALOG.SCHEMA.TABLE 路径——这种方式更受推荐,且表移动后仍然有效。每种形式的完整 YAML 示例,请参见 references/query-view-examples.md。

如果用户不确定哪个字段唯一,请在编写视图前询问。没有主键的查询视图在联接时会触发“Joins fan out the data without a primary key”错误。参见:https://community.omni.co/t/why-am-i-getting-the-error-joins-fan-out-the-data-without-a-primary-key/37

如需将查询视图限定到单个主题,请参见主题范围的视图定义。

**汇总查询中的 sql: 与 query:。**字段引用(${view.field})可在 sql: 块中使用,但 Omni 会在文件保存时将其展开一次,转换为带别名的列,因此 FROM 必须使用视图的引用名称作为别名(FROM ${order_items} AS "order_items")。验证不会检测缺失的别名,但查询会报错。query: 形式(使用 fields: 将视图字段和度量映射到列名,并包含 base_view 和 topic)会在每次运行时通过模型编译,因此这些字段之后的更改也会生效。sql: 查询视图不会自动创建 count 度量;如有需要,请自行声明。示例请参见 references/query-view-examples.md。

常见验证错误

错误修复方法
"No view X"检查视图名称拼写
"No join path from X to Y"添加关系
"Duplicate field name"删除重复项或重命名(如果某个字段是自动生成的,也可以使用 hidden: true 将其隐藏)
"Invalid YAML syntax"检查缩进(使用 2 个空格,不要使用制表符)
联接时出现扇出 / 聚合不正确在被联接的视图中添加 primary_key: true — 参与联接的每个视图都必须有主键
列引用错误(例如,“Column X not found”)检查表是否存在,以及 Omni 连接是否有权访问
编辑后仓库根目录中出现重复视图你使用了不带路径的 fileName,而不是文件的完整路径。删除多余的根目录文件(向其发送空的 yaml),然后使用准确的 files 键重新写入,并包含文件夹前缀(例如 MARTS/)

故障排除:模型与数据库不同步

如果模型未反映数据库中的情况(缺少列/表、类型错误、引用失效),请触发架构刷新(参见上文“架构刷新”),然后运行 omni models validate <modelId>。字段名称冲突和列引用失效通常可以通过 hidden: true 或重命名来解决(参见“常见验证错误”);如果表持续缺失,则表示连接无权访问该数据库/架构。

文档参考

相关技能

  • omni-model-explorer — 修改前了解模型
  • omni-ai-optimizer — 构建主题后添加 AI 上下文
  • omni-query — 测试新字段

评论

支持 Markdown