mcpField
O
omni-query

omni-query

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

关于 omni-query

通过 Omni CLI 在 Omni 语义层上运行查询的技能,面向需要从模型字段获取数据的分析用户。用户指定维度、度量和筛选条件,由 Omni 转换为 SQL。

主要功能

  1. 01按语义字段构建查询
  2. 02指定维度、度量和筛选器
  3. 03支持排序、透视和限制行数
  4. 04执行表格计算
  5. 05处理查询结果

适用场景

  1. 01按主题汇总业务指标
  2. 02筛选并排序查询结果
  3. 03计算运行总计或百分比变化
  4. 04复现 SQL 查询意图

安装

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

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

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

包含的文件

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

evals/
evals.json
references/
filter-expressions.md
job-result-to-presentation.md
table-calculations.md

详细介绍

Omni 查询

通过 Omni CLI 查询 Omni 语义层。Omni 会将字段选择转换为优化后的 SQL——你只需指定所需内容(维度、度量、筛选条件),无需说明如何获取。

提示:如果不知道有哪些可用主题和字段,请先使用 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 key 或 OAuth 进行身份验证。如果 whoami(或任何调用)返回 401,请转交处理——让用户运行 ! omni config login <profile>(OAuth 2.1 浏览器流程;会在浏览器上阻塞约 2 分钟)。不要在无头/CI 会话中自行运行 config login(没有浏览器会导致超时);在本地交互式机器上则可以运行。有关配置文件设置(omni config init --auth oauth)以及如何使用 --schema 查找命令和请求正文的格式,请参阅 omni-api-conventions 规则。

你还需要一个 模型 ID,并了解有哪些可用的 主题和字段。

发现命令

omni query --help              # List query operations
omni query run --help          # Show flags for running a query
omni query run --schema        # Body schema + example (CLI ≥ 1.5.0 types the `query` object — `fields` and `modelId` are required, `calculations`/`filters`/`sorts`/`pivots` are enumerated; keys outside the typed set are still accepted but not shown)
omni query run --schema --field query  # Just the typed query sub-fields
omni ai --help                 # AI-powered query generation

提示:使用 -o json 强制输出结构化内容,以便程序解析;使用 -o human 输出易读表格。默认值为 auto(在 TTY 中输出人类可读格式,通过管道传输时输出 JSON)。

已知问题与安全默认值

  • 收到 SQL 时,默认应按主题复现其意图——先阅读 SQL 的作用(粒度、度量、筛选条件、联接),再检查主题是否能够表达它(omni-model-explorer;omni ai pick-topic / omni ai generate-query --run-query=false)。仅当没有合适的主题,或用户明确要求原样运行 SQL 时,才使用原始 userEditedSQL。参见 运行原始 SQL。不要条件反射式地直接透传 SQL(你不是文本转 SQL 生成器),也不要强行套用不匹配的主题。
  • 当用户要求计算列、表计算、运行总计、移动平均值、百分比变化、行总计、层级标签、VLOOKUP、SUMIF 或日期差值时,应使用在 query.fields 中选定的 calculations[] 表计算来满足需求。除非用户明确要求其他替代方案,否则不要改用现有模型字段、userEditedSQL、客户端计算或仅用文字说明。
  • 确保表计算的答案可审计。 只有最终答案列出计算列名称,并在 query.fields 和 calculations[] 中都展示相同的 calc_name,才算完成任务(即使是 OMNI_RUNNING_TOTAL 这样的简单运算符也一样)。附上精简的查询 JSON 片段——包括 query.fields、运算符和操作数完整的实际 calculations[] 对象、相关的 pivots[] / limit,以及验证结果——不要用 { "OMNI_OFFSET_MULTI over": "field" } 这样的转述,或“通过 OMNI_RUNNING_TOTAL 计算”这样的说法代替。较长的 CSV 可能会掩盖查询结构;请展示几行数据和可复用的结构,或说明为简洁起见省略了完整 JSON。
  • 如果计算查询成功,但计算列为空,在证明原因之前,应将其视为计算失败。重新检查操作数顺序、for_calc、日期截断、outside_pivot,以及 calc_name 是否出现在 query.fields 中。
  • 编写或验证计算时,不要吞掉错误。 保持 swallow_errors: false(默认值),这样错误的计算会明确失败并显示真实错误信息。设为 swallow_errors: true 时,该列会静默显示 #ERROR!,而查询仍返回 COMPLETE——很容易被误认为是数据、空白计算结果或引擎错误。如果看到 #ERROR!,请使用 swallow_errors: false 重新运行,以显示原因(通常是 query.fields 中缺少被引用的字段)。重新运行从文档、仪表板磁贴或 omni ai 任务中获取的计算查询时,请原样运行——删掉计算引用的字段会人为制造错误,而这并非计算本身的问题。仅在最终定稿的磁贴需要逐单元格容错时使用 swallow_errors: true,并先用 false 验证。参见 references/table-calculations.md §5.11 和 §6.5。
  • 若有模板,优先使用文档中介绍的 Omni 计算运算符,而不是较底层的原始 SQL / 窗口 AST。例如,当提示要求表计算时,应使用 Omni.OMNI_RUNNING_TOTAL、Omni.OMNI_PERCENT_CHANGE_FROM_PREVIOUS 和 Omni.OMNI_FX_AVERAGE(Omni.OMNI_OFFSET_MULTI(...)) 计算移动平均值,而不是手写 window_call / LAG。
  • query run 每次都要求在 query 对象内部包含 modelId。 独立请求体应为 {"query":{"modelId":"<uuid>", …}};省略后调用会返回 400,错误为 query.modelId: Invalid input: expected string, received undefined。这与 v2 仪表板磁贴查询(omni-content-builder)恰恰相反:磁贴查询绝不能携带 modelId(服务端会将磁贴绑定到文档的工作簿模型)。不要把磁贴规则误用到独立查询中——磁贴省略 modelId;query run 必须包含它。 对分支或工作簿 / 草稿模型运行查询时,应将 modelId 设为该模型的 id(分支模型 id 或草稿的 workbookModelId),而不是共享模型;仅在技能明确要求时才在顶层传入 branchId(分支自身的模型 id 已能解析分支字段)。
  • 读取结果行时,应在请求体的顶层设置 resultType:"json"(或 "csv")——不要放在 query 内部。 放错到 query 内会被静默忽略:你会收到默认的 base64-Arrow 流式封装(行内容不可读),白白多一次往返。参见 处理和验证结果。

围绕主题构建查询

优先选择围绕主题构建每个查询,而不是使用裸基础视图。主题包含受治理的联接、标签和访问权限,而且基于主题构建的查询,受限查询者/查看者无法访问(对你这样的建模者/管理员可用,但对受限角色会静默失败)。将查询的 table 设为主题的基础视图,并传入 join_paths_from_topic_name: <topic>。如果你是受限查询者(拥有 QUERY_TOPICS,但没有 QUERY_FULL_MODEL——请检查 whoami),那么基于主题构建不仅是首选,更是唯一的选项:裸基础视图查询或原始 SQL userEditedSQL 查询需要 QUERY_FULL_MODEL,因此你编写并放入内容的每个查询都必须基于主题构建。

联接映射如何解析已联接视图的字段。 table 始终是主题的基础视图;join_paths_from_topic_name 允许主题的联接映射从该视图访问已联接视图的字段——例如,要在基于 order_items 的主题中选择 users.state,table 仍设为 order_items,联接由主题提供;不要将 table 设为 users。省略 join_paths_from_topic_name(或将 table 指向非基础视图),已联接视图的字段可能无法解析,或可能联接错误。使用 omni models get-topic <modelId> <topic> 确认基础视图和所有可达联接——其中的 base_view_name 和 join_via_map 会显示基础视图,以及通往每个可达视图的联接路径。(这是规范的主题查询形式;omni-content-builder 磁贴和 omni-model-builder 验证查询也采用这种形式。)

决定查询应来自哪里:

  1. 现有主题可以回答该问题(其基础视图 + 通过联接可达的视图)→ 查询该主题。
  2. 字段位于通过联接可达的视图中,但主题未公开该字段/缺少相应联接 → 建议扩展主题(添加关系/联接),然后通过 omni-model-builder 构建。
  3. 主题、约束或受众有根本差异 → 建议创建新主题(参见 omni-model-builder 中的“新建主题还是扩展主题”标准)。先询问请求者,并在分支上构建。

回退方案——非主题查询路径。 有两种路径不使用任何主题:裸基础视图(table: + 用于联接的全局 relationships 文件)和原始 SQL(userEditedSQL,参见“运行原始 SQL”)。两者有相同的注意事项:不会应用主题范围内的控制项——访问筛选器(行级)和 always_where;在仪表板中,默认情况下 Viewer / Restricted Querier 角色看不到该磁贴(受限受众的处理属于内容权限问题——参见 omni-content-builder)。这两种路径在对象级访问授权方面有所不同:裸视图查询仍会执行这些授权,但原始 SQL 也会绕过它们(这是权限最宽松的路径)。如果主题适用,请优先使用主题;只有在其他方式都无法表达该查询时,才使用非主题路径。

如果结论是“构建或修改主题”,请交由 omni-model-builder 妥善处理。

复合主题。 复合主题(list-topics 中的 is_composite: true)没有基础视图:传入 join_paths_from_topic_name: <composite>,并且不传入 table。字段使用 @_shared_dimensions_.<name>[timeframe]、@_shared_views_.<view>.<field> 和 @<topic>.<view>.<measure> 的形式寻址;以 @<topic>. 开头的筛选键仅应用于该成员主题;@ 不可省略。在复合主题默认的 unrelated_dimension_handling 设置下,属于某个成员主题的维度可以用于筛选,但不能用于选择——如果需要按该维度分组,应将其设为共享维度;否则复合主题应声明 null_fill(其他成员的度量会显示在空值行中)或 repeat(这些度量会在每一行重复)。对复合主题执行 get-topic 可列出可寻址的字段范围。

运行查询

基本查询
omni query run --body '{
  "query": {
    "modelId": "your-model-id",
    "table": "order_items",
    "fields": [
      "order_items.created_at[month]",
      "order_items.total_revenue"
    ],
    "limit": 100,
    "join_paths_from_topic_name": "order_items"
  }
}'
查询参数
参数必填说明
modelId是 — 位于 query 内Omni 模型(或分支/工作簿模型)的 UUID。每次独立的 query run 都必须提供;省略会返回 400。对比:v2 仪表板的 图块 查询完全不包含此参数。
table有条件必填基础视图(FROM)。语义查询必须提供,除非设置了 join_paths_from_topic_name(基础视图来自主题),或使用了 userEditedSQL(忽略此参数)。
fields是view.field_name 引用数组
join_paths_from_topic_name建议提供用于解析联接的主题
limit否行数限制(默认值为 1000,最大值为 50000,null 表示不限制)。此处不会应用主题或模型的 default_row_limit:省略 limit 时始终为 1000
sorts否排序对象数组
filters否筛选对象
pivots否用于透视的字段名称数组
字段命名

字段使用 view_name.field_name。日期字段支持时间范围括号:

users.created_at[date]      — Daily
users.created_at[week]      — Weekly
users.created_at[month]     — Monthly
users.created_at[quarter]   — Quarterly
users.created_at[year]      — Yearly
排序
"sorts": [
  { "column_name": "order_items.total_revenue", "sort_descending": true }
]
筛选

query.filters 是一个映射,格式为 fieldName → 类型化筛选对象,与 Omni UI 生成的结构一致(已通过实时仪表板的 filterConfig 和端到端 query run 验证):

"filters": {
  "users.state":            { "type": "string", "kind": "EQUALS",       "values": ["California", "New York"] },
  "products.category":      { "type": "string", "kind": "CONTAINS",      "values": ["Jeans"] },
  "order_items.sale_price": { "type": "number", "kind": "GREATER_THAN",  "values": [100] },
  "order_items.created_at": { "type": "date",   "kind": "BEFORE",        "values": ["2024-01-01"] },
  "order_items.shipped_at": { "type": "date",   "kind": "TIME_FOR_INTERVAL_DURATION", "ui_type": "PAST", "left_side": "12 months ago", "right_side": "12 months" },
  "order_items.is_shipped": { "type": "boolean", "is_negative": false }
}
  • type — string / number / date / boolean。布尔筛选使用 is_negative(false = 为 true,true = 为 false),而不是 kind/values(以及可选的 treat_nulls_as_false)。
  • kind(运算符,适用于 string/number/date)— string:EQUALS、CONTAINS、STARTS_WITH、ENDS_WITH、IS_EMPTY、SQL_LIKE;number:EQUALS、GREATER_THAN、LESS_THAN、BETWEEN;date:BEFORE、ON_OR_AFTER、BETWEEN、TIME_FOR_INTERVAL_DURATION/TIME_FOR_UNIT_DURATION(滚动时间窗口)、IS_ON_DAY_OF_WEEK、…(QUERY_OFFSET 引用另一个查询)。
  • values — 操作数数组:单个值、列表(多选 EQUALS),或 BETWEEN 使用的 [lo, hi]。日期滚动时间窗口使用 left_side/right_side + ui_type(例如 PAST),而不是 values。
  • 确认筛选确实生效。 筛选对象中的属性与其类型不匹配时(例如,boolean 使用 kind/values,但它需要 is_negative),该筛选会被静默忽略——查询返回 COMPLETE,但筛选并未传入 SQL。可通过 cache:"SkipCache" → summary.display_sql(WHERE 子句)确认,或检查行数是否实际发生变化。准确的 kind/ui_type/布尔枚举值见 omni documents v2-create --schema(筛选对象部分);另请参阅 references/filter-expressions.md。

请勿使用裸字符串简写("order_items.status": "complete"、"last 90 days"、"not null")。查询 API 会拒绝此格式——截至 2026 年 8 月会返回 400 "Unable to parse data stream",较旧版本则会返回 500 "Cannot use 'in' operator to search for 'query_id' in <value>"。上述类型化对象是可靠的格式。(已在当前 Omni 上对值、日期、空值字符串、数字和布尔格式进行了复现。)

透视
{
  "query": {
    "fields": ["order_items.created_at[month]", "order_items.status", "order_items.count"],
    "pivots": ["order_items.status"],
    "join_paths_from_topic_name": "order_items"
  }
}

透视查询不接受 limit: null,除非请求体设置了顶层 resultType;否则请传入明确的数字限制(例如 5000)。

将度量转置为行(transposed_measures)

将一个宽行中的多个度量折叠为长格式,每个度量占一行,从而可以将这些度量作为类别进行图表绘制(例如,漏斗图或“度量作为坐标轴”的条形图)。transposed_measures 是要折叠的度量字段名称数组(与在 fields 中填写的名称相同),不是布尔值——传入 true 会被静默拒绝(z.array(z.string())),导致结果为空。

{
  "query": {
    "table": "order_items",
    "fields": ["order_items.units_sold", "order_items.shipped_items", "order_items.delivered_items"],
    "transposed_measures": ["order_items.units_sold", "order_items.shipped_items", "order_items.delivered_items"],
    "join_paths_from_topic_name": "order_items"
  }
}

结果会在第一个转置度量所在的位置新增三列合成列:

  • measure_name — 度量的字段名称;在可视化中显示为度量的友好标签(例如“销售数量”)。
  • measure_order — 按列出顺序排列的 0, 1, 2, …(即阶段顺序)。
  • measure_value — 该行中此度量的值。

这是通过多个度量构建漏斗图的受支持方式(Omni 的漏斗图需要一个阶段维度和一个度量,而不是 N 个度量):将 measure_name 作为阶段,将 measure_value 作为值绘制图表。请参阅 omni-content-builder → 配置对象:漏斗图。

表格计算

查询后的计算列(累计总计、占比、比率、条件判断)。以 AST 对象的形式编写在 calculations[] 中。查询 API 要求传入解析后的 AST,不接受工作簿前端的 {name, formula} 形式。

最简计算——一个 calculations[] 条目(其 calc_name 也必须出现在 query.fields 中):

{ "calc_name": "calc_pct", "label": "% of Total", "format": "0.0%",
  "sql_expression": { "type": "call", "operator": "Omni.OMNI_PERCENT_OF_TOTAL",
    "operands": [{ "type": "field", "field_name": "orders.total_revenue", "for_calc": true }] } }

最容易踩的坑:calc_name 也必须出现在 query.fields 中(对于仪表板图块,还必须出现在外层的 queryPresentation.fields 中)。在 calculations[] 中定义了计算,但未将其加入 fields,就会导致计算已执行,却永远不会渲染。

五个快速模板运算符(每个都接受一个带 for_calc: true 的 field 操作数): Omni.OMNI_PERCENT_OF_TOTAL、Omni.OMNI_PERCENT_OF_PREVIOUS、Omni.OMNI_PERCENT_CHANGE_FROM_PREVIOUS、Omni.OMNI_RUNNING_TOTAL、Omni.OMNI_RANK。

如果你已知道计算的形态,可使用 omni query run,并手动编写或复制 AST。要生成任何非简单内容——表格计算、同期对比、多步骤分析——请优先选择 Agentic 路径(omni ai job-submit):它能编写 generate-query 会悄悄丢弃的计算(例如环比变化百分比)。要从 Agentic 任务中获取可复用的 AST,请从任务的 actions[].generate_query 结果中提取结构化查询 / calculations(不要从 resultSummary 中提取,也不要使用 userEditedSQL / SQL 回退结果),然后使用 swallow_errors:false 重新运行你组装好的查询,并将数值与任务的 csvResult 进行差异比较。任务已经执行过该计算,因此重新运行并不是为了再次验证计算本身,而是为了检查你重组后的查询(字段被丢弃或重命名,就是一种仅凭“查询已运行并返回数据行”无法发现的转换失误;见 table-calculations.md §6)。仅在简单、确定性的单次查询以及不允许执行查询、只需起草查询形态时使用 generate-query。

除非用户明确要求更改模型,否则查询任务均为只读。如果某个字段似乎缺失,请检查主题 / 仪表板查询,并使用正确的模型 / 主题 / 分支,或报告缺失字段的阻塞问题——不要为了让查询生效而创建分支、添加度量或编辑 YAML。(也不要使用客户端数学运算或现有模型字段(如 users.tier_label)来满足计算请求;应构建并验证表格计算,见 Known Issues。)

常见请求 → 运算符(精确 AST 和各方案的注意事项见参考文档——不要自行猜写):

  • 占总计的百分比 → OMNI_PERCENT_OF_TOTAL · 累计总计 → OMNI_RUNNING_TOTAL(按时间升序排序,不要在 Omni 外部反转顺序) · 环比变化百分比 → OMNI_PERCENT_CHANGE_FROM_PREVIOUS(而非 omni_period_pivot / LAG) · 最近 N 期平均值 → 对 OMNI_OFFSET_MULTI 使用 OMNI_FX_AVERAGE
  • 透视表行合计 → OMNI_FX_SUM + OMNI_PIVOT_OFFSET(outside_pivot:true,数值型 limit) · 层级标签 → OMNI_FX_IFS(而非 CASE / 模型字段) · SUMIF → OMNI_FX_SUM_IF · VLOOKUP → OMNI_FX_VLOOKUP(如果字符串查找返回 400 错误 No referenced query…,则回退到 OMNI_FX_SUM_IF) · 日期差 → OMNI_FX_DATEDIF(操作数使用 [date])

各方案对应的精确 JSON AST、完整运算符目录(Omni.* / SqlStdOperatorTable.*)、节点类型、验证规则,以及陌生计算的往返策略,详见 references/table-calculations.md —— SKILL.md 负责工作流约束,参考文档则包含详细结构。

执行时,计算会编译为包裹基础聚合的外层 SELECT;窗口型运算符会在其中生成 ... OVER (...),因此共享数据模型无需窗口函数也能支持这些计算。在透视查询中,模板运算符会自动按透视列分区,以生成各分组的序列;对于横跨透视列的行汇总,请设置 outside_pivot: true,并在 OMNI_PIVOT_OFFSET 外包裹一个聚合器。

运行原始 SQL(userEditedSQL)

给定了 SQL?先通过主题复现它(参见 已知问题)。通过语义层表达 SQL 的意图;只有在没有主题能够表达时(SQL 优先迁移、数据仓库专属 SQL、一次性临时读取),或者用户要求原样运行时,才使用原始 userEditedSQL。如果忠实复现需要一个并不存在但本该存在的字段或主题,应提议对其进行建模(omni-model-builder),而不是默认使用原始 SQL。

读取 generate-query 的输出:当主题缺少指标所需的度量时,generate-query 会返回一个使用 ${} 模板的 userEditedSQL(例如,SUM(${view.sale_price}) AS sale_price_sum FROM ${Topic}),并在 fields 中列出 SQL 输出别名(如 sale_price_sum)。这些别名不是模型字段——不要去掉 SQL 并尝试以语义方式运行它们(它们无法解析)。而且,${Topic} 标记仅在 generate-query 自身的执行过程中解析——该模板化查询无法直接通过 query run 运行,也无法保存为仪表板磁贴(会报错 No such view "Order Items")。因此,不要原样重用它;应将其视为主题缺少度量的信号,并添加该度量(omni-model-builder),使该指标成为干净的语义字段。

userEditedSQL 是一种非主题查询路径——与裸视图查询属于同一类(参见“回退方案 — 非主题查询路径”)。它是语义层无法表达 SQL 时的后备方案;条件允许时,优先使用主题或语义字段。

omni query run --body '{
  "query": {
    "modelId": "<model-id>",
    "fields": [],
    "userEditedSQL": "select count(*) as cnt from ECOMM.ORDER_ITEMS"
  }
}'
  • 必须提供 fields(一个数组;可以为空 []);无需提供 table。SQL 具有权威性——设置 userEditedSQL 时,会忽略已填充的 fields/table。
  • 使用模型的连接运行——请编写数据仓库方言的 SQL,并使用完全限定名称,而非字段引用。
  • "rewriteSql": false 会逐字运行你的 SQL(默认会解析并重新生成 SQL——重新引用标识符,并将投影别名化到 view.field 命名空间中)。"dbtMode": true 允许使用 Jinja/dbt 模板。(两者均采用驼峰命名法;query 对象允许额外属性,因此拼错的键或蛇形命名键会被静默丢弃。)
  • 受权限控制:查询者的角色必须允许执行手写 SQL。否则作业会失败——error_type: "FORBIDDEN",错误信息为 "queries based on manually written SQL are restricted"——并以HTTP 200 响应返回在作业正文中的错误,而非 4xx。
  • 访问行为:原始 SQL 会绕过所有模型控制——对象级访问授权、行级访问筛选器和always_where——并且在仪表板中对 Viewer/Restricted Querier 角色不可见。这是权限最宽松的路径;仅用于特权用户的临时读取,并且从任何重用的查询或仪表板查询中移除它(参见“在仪表板中使用作业结果”)。
  • **行数上限:**无界原始查询最多返回 50,000 行(响应会返回 50,001 行——上限加上一行截断标记)。信封中的 limit 不会应用于原始 SQL;请在 SQL 中添加 LIMIT 来限制结果。

请求级选项(位于 query 外部)

这些键位于请求正文的顶层,与 query 并列,而不在其中。query 对象允许包含任意键——如果你把键放错位置,放进 query(例如 resultType、cache),它会被静默丢弃,而不是被拒绝,因此调用会“成功”,但你的选项会被忽略。branchId 是例外:放在 query 内会被拒绝。放错位置的 resultType 的明显迹象是:你请求 JSON/CSV,却收到 base64-Arrow 信封。

选项说明
resultType输出格式:csv、xlsx 或 json。仅限顶层——放在 query 内会被静默忽略,并返回 Arrow。省略时默认返回 base64 Arrow。
cache缓存策略:Standard(默认)、SkipRequery、SkipCache、SkipCacheAndRebuildExtracts(也会重建查询所读取的 extracts)。
userId以其他用户身份运行(组织范围的 API 密钥);也对应 --user-id 标志。
branchId针对模型分支运行(在实时数据上验证模型草稿更改)。必须是同一共享模型的分支。
planOnly返回执行计划,不运行查询(无需消耗数据仓库成本即可验证/调试)。不能与 resultType 组合使用。
formatResults导出时输出格式化值(例如 $1,234.56),而非原始值。需要指定 resultType;对 Arrow 无效。
timezone按请求覆盖时区(IANA ID)。需要连接设置 allowsUserSpecificTimezones 和组织设置 allowsDocumentCanUseTimezoneOverride;任一设置关闭时都会静默无效。
workbookUrl同时为查询创建临时工作簿,让用户获得“在 Omni 中打开”链接。在 CLI ≥ 1.3.0 中,传入 --workbook,而不是手动设置此项。链接会出现在响应标头中,而非正文中:CLI 会在人类可读输出下打印该链接,或在 JSON 模式下于 stderr 输出 {"workbookUrl": …}。如果(目标)用户没有该模型的工作簿权限,查询仍会成功,链接会被静默省略——没有链接表示没有权限,而不是重试失败。
确认是否使用了聚合表

当模型声明了聚合表(materialized_query)时,使用 planOnly: true 和 cache: SkipCache 规划查询,并读取 summary.display_sql。由聚合表提供结果的查询会以 -- Query rewritten to use materialized view "<view>" 开头,下面会注释掉原始 SQL;没有标头则表示使用了事实表。在复合主题上,每个成员查询会分别匹配,且标头会指出使用的第一个表,因此还要查看其他成员对应的 SQL。查询未命中聚合表的原因,见 omni-model-builder 的聚合感知参考文档。

向用户展示结果(CLI ≥ 1.3.0)

在人类可读模式下,query run 会使用模型的标签和数字格式渲染格式化表格,并自行轮询尚未完成的作业。--chart 会将相同结果绘制为终端条形表(--chart-value 选择度量,--chart-rows 限制行数);通过管道输出时仍会绘制,宽度为 80 列,因此适合放入聊天代码块。--chart 会从请求正文中移除 resultType,且不能与 -o json 一起使用。两种视图都不包含作业信封——先通过 JSON 模式运行(见下文)进行验证,再为用户渲染结果。

处理和验证结果

omni query run 会流式输出 NDJSON——它不是一个 JSON 对象。 在 JSON 模式下(解析时传入 -o json;即使通过管道传输,也会应用 config set-format 或 OMNI_OUTPUT_FORMAT 设置的人类可读默认格式,并输出表格),CLI 会打印多个 JSON 对象,每行一个:首先是 {"jobs_submitted":{…}} 行,然后是一行或多行 {"job_id":…,"status":"COMPLETE","summary":{…}} 任务行(如果指定了 resultType,还会包含结果负载)。直接对整个标准输出调用 json.loads() 会抛出 JSONDecodeError: Extra data。不要编写只解析单个对象的解析器——逐行遍历并选取所需行,或使用 jq -s 读取全部内容,或读取最后一个非空行。--compact 会将每个对象输出为一行整洁的内容。

默认响应:Base64 编码的 Apache Arrow 表。Arrow 结果是二进制数据——无法从原始响应中解析单行数据。行数位于 cache_metadata.num_rows(不是 summary.row_count)。summary 对象包含验证元数据:invalid_calculations、missing_fields、display_sql(编译后的 SQL)、omni_sql_parse_failed。(--schema 不会显示这些内容——它只描述请求正文;响应结构需从实时响应中获取。请参阅 omni-api-conventions 规则。)

如需自行读取行数据,始终在正文的顶层设置 resultType: "json"(或 "csv")——这样标准输出就是干净、可直接解析的 JSON 数组(或 CSV),无需拆解 Arrow/NDJSON 封装。 这是抽查数值唯一可靠的方法;尝试走默认的 Arrow 路径并从中解码行数据,是最常见的时间浪费。

{ "query": { ... }, "resultType": "csv" }

resultType: "xlsx" 也有效,但它会返回一个二进制 .xlsx 文件(基于 zip)——和默认的 Arrow 二进制数据一样,没有电子表格应用或库就无法直接读取。仅在需要向他人交付文件时使用,不要用它来检查结果。供 Agent 端读取时,请使用 csv/json。

结果验证

在信任结果或向用户展示结果之前,都应检查每个查询响应。

检查错误:

  • 如果响应包含 error 键,则查询失败。常见原因:字段名错误、缺少联接路径、过滤表达式格式错误、权限错误。
  • 如果响应包含 remaining_job_ids,则查询仍在运行——请使用 omni query wait 轮询,然后再检查结果。

检查行数(字段为 cache_metadata.num_rows):

  • cache_metadata.num_rows == 0——查询没有返回数据。这可能是正常情况(例如,过滤范围内没有数据),但值得提醒用户。常见原因:过滤条件过于严格、日期范围错误、字段与任何行都不匹配。
  • cache_metadata.num_rows 等于你设置的 limit——结果可能已被截断。如果用户需要完整数据,请使用更高的限制重新运行,或将其设为 null 表示不限制。

使用 CSV 抽查数据:

当准确性很重要时,请请求 CSV 并检查输出:

omni query run --body '{
  "query": { ... },
  "resultType": "csv"
}'

请检查:

  • 列标题与请求的字段相符
  • 数值处于预期范围内(例如,收入不为负数,日期不在未来)
  • 聚合结果合理(例如,计数返回的不是总和)

验证过滤行为:

如果查询包含过滤条件,请确认这些条件确实生效:

# Run the same query without filters
omni query run --body '{ "query": { ... (no filters) ... }, "resultType": "csv" }'

# Compare row counts — filtered should be <= unfiltered

如果两个查询返回的行数相同,过滤条件可能没有生效——请检查字段名,并确认过滤对象与字段类型相匹配(boolean 需要 is_negative,而不是 kind/values——不匹配时会被静默忽略)。确认条件出现在 summary.display_sql 中(cache:"SkipCache")。

验证清单
检查项方法时机
响应中无错误检查是否存在 error 键每次查询
计算/字段有效summary.invalid_calculations 和 summary.missing_fields 为空每次查询(尤其是涉及计算时)
已返回数据cache_metadata.num_rows > 0每次查询
结果未截断cache_metadata.num_rows < limit需要确保完整性时
列正确CSV 列标题与请求的字段一致构建仪表板或报告时
值合理抽查 CSV 输出向用户展示时
已应用筛选条件比较筛选前后的行数使用筛选条件时
长时间运行的查询已完成最终响应中没有 remaining_job_ids查询大型表时
解码 Arrow 结果
import base64, pyarrow as pa
arrow_bytes = base64.b64decode(response["data"])
reader = pa.ipc.open_stream(arrow_bytes)
df = reader.read_all().to_pandas()
长时间运行的查询

如果响应包含 remaining_job_ids,请轮询直至完成:

omni query wait --job-ids job-id-1,job-id-2

JSON 模式不会为你轮询:数据流会在等待窗口结束时终止,页脚中的 remaining_job_ids 是结果未完成的唯一提示。(CLI ≥ 1.3.0 的 Human 模式输出会进行轮询。)

分块流式传输大型结果(CLI ≥ 1.5.0)

如果结果太大,无法放在单个响应中,或者下载过程中连接断开,可以使用 --completion-only true 将结果与完成状态分开,并在之后通过 omni query results 获取结果。此时,query run / query wait 返回的任务行会省略 result;Arrow IPC 字节会以 base64 切片形式返回,你可以将这些切片拼接起来,并在 stream_stats.next_offset 为 null 时进行解析。任务完成后,结果大约一小时内仍可读取。

# 1. Wait for completion without downloading the payload
omni query wait --job-ids "$JID" --completion-only true

# 2. Read the result in slices (resume on drop by passing the previous next_offset)
omni query results --job-id "$JID" --offset 0

在 omni query run 中,将 "completionOnly": true 设为请求正文顶层的值即可达到相同效果。此选项不能与 resultType 同时使用。

从仪表板运行查询

提取并重新运行现有仪表板所用的查询:

# Get all queries from a dashboard
omni documents get-queries <dashboardId>

# Run as a specific user
omni query run --body '{ "query": { ... }, "userId": "user-uuid-here" }'

# Cache policy (valid values: Standard, SkipRequery, SkipCache, SkipCacheAndRebuildExtracts)
omni query run --body '{ "query": { ... }, "cache": "SkipCache" }'

按保存时的状态运行已保存的查询(CLI ≥ 1.5.0)。omni documents run-query <identifier> <queryId> 会在服务端运行文档中保存的一个查询,并返回单个 JSON / CSV / XLSX 文档 — 查询正文会在服务端读取,因此调用方只能为本次运行替换或添加筛选条件。仪表板上的图块(或包含应用的文档中的任意查询)可由任何有权查看它的人运行;隐藏图块或不在仪表板上的标签页则需要工作簿访问权限。<queryId> 是图块的 queryIdentifierMapKey,或来自 omni documents get-queries 的查询 presentation id。每次运行的 filters 以字段名称为键;无法应用的筛选条件会导致运行失败并返回 400,而不会在不应用筛选条件的情况下继续运行。使用组织范围的密钥时,--user-id 会以该成员的身份运行,也适用于 get-queries。如果你希望保留已保存的行为,请优先使用此命令,而不是重建完整的 query run 请求正文。

AI 驱动的查询生成

你可以用自然语言描述需求,让 Omni 的 AI 生成查询,而不必手动构建查询 JSON。

生成查询(同步)

最快的方式 — 同步返回生成的查询 JSON。传入 --run-query false 可仅获取查询结构而不执行(默认会运行查询)。

# Just generate the query JSON (no execution)
omni ai generate-query your-model-id "Show me revenue by month" --run-query false

响应:

{
  "query": {
    "fields": ["order_items.created_at[month]", "order_items.total_revenue"],
    "table": "order_items",
    "filters": {},
    "sorts": [{"column_name": "order_items.created_at[month]", "sort_descending": false}],
    "limit": 500
  },
  "topic": "order_items",
  "error": null
}
# Generate and execute in one call
omni ai generate-query your-model-id "Top 10 customers by lifetime spend"

可选标志:

  • --branch-id — 针对特定模型分支进行测试
  • --current-topic-name — 将主题选择限制为特定主题
选择主题

查看 AI 会为某个问题选择哪个主题,而不生成完整查询:

omni ai pick-topic your-model-id "How many users signed up last month?"
智能体查询(异步)

体验完整的 Blobby——包括多步分析、工具使用和主题选择,体现 AI 在生产环境中的实际行为。这是异步流程:提交任务、轮询状态,然后获取结果。

# 1. Submit a job
JID=$(omni ai job-submit your-model-id "Analyze revenue trends; identify our fastest growing category" -o json \
  | python3 -c 'import json,sys;print(json.load(sys.stdin)["jobId"])')

# 2. Poll with EARLY EXIT on any terminal state — break on success AND on failure
while :; do
  S=$(omni ai job-status "$JID" -o json | python3 -c 'import json,sys;print((json.load(sys.stdin).get("state") or "").lower())')
  case "$S" in
    complete*)             break ;;                       # done → go fetch the result
    fail*|cancel*|error*)  echo "job $S"; break ;;        # terminal failure → STOP, do not keep sleeping
    *)                     sleep 5 ;;                      # only the non-terminal branch sleeps
  esac
done

# 3. Get the result (resultSummary = narrative; actions[] = structured output)
omni ai job-result "$JID" -o json

轮询循环必须在每种终止状态下提前退出——切勿使用固定次数的 for … sleep … done 循环,并且只在成功时退出。 如果成功过滤条件有误(status 与 state 混淆,或 COMPLETE 与 COMPLETED 混淆),或者任务出错,这种循环就会一直运行到结束。请使用上面的 while :; … case … break 形式:仅在默认分支中休眠;状态一旦终止(complete* 或 fail/cancel/error),立即退出。容错读取字段(state ?? status,转为小写;startswith("complete") = 完成),这样一个循环就能兼容下文不同任务的拼写差异。

任务状态的结构。 轮询 state——omni ai job-status 没有 status 字段。状态依次为:QUEUED → EXECUTING → DELIVERING → 终止状态 COMPLETE / FAILED / CANCELLED。注意,它是 COMPLETE,不是 COMPLETED——模型刷新(completed)和 models jobs-get-status(COMPLETED)流程中的拼写各不相同,因此跨任务类型复用的轮询循环需要进行容错终止状态检查:读取字段 state ?? status,将其转为小写;将 startswith("complete") 视为已完成,将 {failed, cancelled, error} 视为失败。回答文本位于 resultSummary;结构化输出位于 actions[] 中(type: "generate_query" → result.query)。

结果包含一个 actions 数组,其中列出了 AI 执行的每个步骤——查找 type: "generate_query" 的操作以提取生成的查询。响应还包含 resultSummary,其中是 AI 对结果的叙述性解读。

在呈现异步任务的答案之前,请检查 actions[] 条目。任务状态可能已变为 COMPLETE,但某个 generate_query 操作的 status 仍为 "pending",或者没有 csvResult;此时,叙述内容可能描述了已生成但尚未执行的查询。每个操作的 status 表示该步骤的结束情况——complete、partial(完成了请求中的部分内容,并说明缺少什么)、skipped 或 failed;它与 result.status(success / error)相互独立,后者仅表示生成的查询是否运行成功。result.status: "success" 的 partial 操作仍然只回答了部分请求。如果必要操作处于待处理、部分完成或失败状态,不要将任务摘要视为最终结果。运行或重新生成该查询,或者通过另一个异步任务继续同一分析,然后只呈现经过验证的结果。

其他任务命令:

  • omni ai job-cancel <jobId> — 取消正在运行的任务
  • omni ai job-visualization <jobId> — 获取可视化输出
  • omni ai job-feedback-submit <jobId> --body '{"rating":"good"}' — 对已完成的任务记录赞成或反对反馈(CLI ≥ 1.2.2)。rating 为必填项(good / bad);comment 是可选的自由文本。只有在轮询报告 COMPLETE/FAILED 后才能提交——其他任何状态都会返回 409。反馈仅追加且无法读回,因此每个任务只提交一次。使用组织范围的密钥时,请传入 --user-id,将反馈关联到提交该任务的用户。
在仪表板中使用作业结果

作业结果中的查询对象不能直接用作仪表板的 queryPresentation——需要先进行转换。关键规则:

  • 始终移除 userEditedSQL——它会使磁贴成为非主题查询,因此绕过所有模型控制(对象级访问授权、行级访问筛选器和 always_where),并且在仪表板中对受限角色不可见。它包含的 ${Order Items} 主题名称标记在作业执行上下文之外也会失效。
  • 当 calculations[] 非空时,移除 userEditedSQL 即可——结构化计算会正确呈现。
  • 当 calculations[] 为空时,Blobby 将计算编写为内联 SQL。解析后的 AST 位于 csvResultFields 中(位于 result 层级,而非 result["query"] 内),可将其重建为正规的 calculations[] 条目。顶层 expr 运算符是聚合函数(SUM、COUNT 等)的字段无法重建为表计算——应将其作为带筛选条件的度量添加到模型中。

完整的转换算法、判别逻辑、字段引用注入、聚合跳过处理和健全性检查方法,请参阅 references/job-result-to-presentation.md。

如何选择合适的方法
方法最适合
omni query run你明确知道需要哪些字段、筛选器和排序
带 calculations[] 的 omni query run明确的表计算请求,且你知道或可以复制 AST 结构
omni ai generate-query --run-query=false起草简单的查询 AST 以供检查或手动编辑;或仅生成结构,不执行查询(无法运行 Agentic 作业时的备用方法)
omni ai generate-query --run-query=true希望获得同步响应的简单维度/度量查询
omni ai job-submit任何非简单任务——多步骤分析,或生成表计算/可复用的查询 AST。从 actions[].generate_query 中提取结构化查询/calculations,然后用 query run 验证

**明确目标结构时,引导提示词:**当表计算是预期或已知正确的输出时,在提示词中说明这一点——追加“……作为表计算”,或指出语义(“累计总计”/“占总量的百分比”/“移动平均值”)——这样 Agentic 作业就会在 actions[].generate_query 中生成真正的计算,而不是回退到 userEditedSQL/SQL。(Agentic 与 generate-query 的分工,以及“从 actions[].generate_query 中提取 AST,再用 query run 验证”的规则,见上表和表计算部分。)

多步骤分析模式

对于复杂分析,可串联查询:

  1. 宽泛查询——了解数据的整体形态
  2. 检查结果——识别有趣的细分群体或模式
  3. 有针对性的后续查询——根据发现添加筛选条件
  4. 综合归纳——将结果整合成一段叙述

常见查询模式

时间序列:字段 + 日期维度 + 升序排序 + 日期筛选

Top N:字段 + 指标 + 降序排序 + 限制条数

细分聚合:多个维度 + 多个度量 + 按关键指标降序排序

已知错误

  • 不带类型的字符串筛选表达式会被拒绝。 "filters": { "field": "complete" | "last 90 days" | "not null" } 自 2026 年 8 月起会返回 400 "Unable to parse data stream"(较早的构建版本会返回 500 "Cannot use 'in' operator to search for 'query_id' in <value>"——即查询引用/field_name_in_query 探测)。请使用带类型的筛选器对象(见上文筛选器部分)。已针对值、日期、空值字符串、数字和布尔形式复现此问题。

链接到结果

查询是临时的,不会为查询结果生成永久 URL。要向用户提供可分享的链接:

  • 对于现有仪表板:{OMNI_BASE_URL}/dashboards/{identifier}(identifier 来自文档 API 响应)
  • 对于新的分析:使用查询作为 queryPresentation,通过 omni-content-builder 创建文档,然后分享 {OMNI_BASE_URL}/dashboards/{identifier}

文档参考

相关技能

  • omni-model-explorer — 在查询前发现字段和主题
  • omni-content-explorer — 查找可提取查询内容的仪表板
  • omni-content-builder — 将查询结果转换为仪表板
  • omni-ai-eval — 对 AI 查询生成的准确性进行基准测试和验证

评论

支持 Markdown