mcpField
Q
query-semantic-model

query-semantic-model

github.com/revosai数据与数据库0158 次安装5 天前更新
代码仓库

关于 query-semantic-model

该 Skill 使用 `revos cubes` 查询组织的语义模型,并在聊天中呈现业务问题的结果。它涵盖成员发现、关联路径选择、查询执行以及表格或图表展示。

主要功能

  1. 01查看立方体和可查询成员
  2. 02从本地 YAML 梳理关联路径
  3. 03构建带关联提示的 Cube.js 查询
  4. 04通过 `revos` 命令行执行查询
  5. 05将结果呈现为表格和图表

适用场景

  1. 01查看按月份或类别划分的收入
  2. 02比较语义模型维度中的指标
  3. 03避免关联路径不明确导致结果错误

安装

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

$npx skills add https://github.com/revosai/skills --skill query-semantic-model

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

详细介绍

查询语义模型

通过查询组织的语义模型来回答业务问题,并直接在聊天中呈现结果——无需切换到笔记本或界面。CLI 已提供所需的一切:revos cubes meta 会列出可用的数据立方体 / 度量 / 维度,revos cubes query 会运行 Cube.js 查询并返回行数据。

这项技能是 Bronze → Silver → Gold → Cubes 构建并应用后的“最后一公里”。它让你能够实际查看语义模型。


第 1 步:发现可查询的内容

在猜测成员名称之前,先列出组织的语义模型实际提供了哪些内容:

revos cubes meta --json

响应包含一个数据立方体数组,每个数据立方体都包含 measures、dimensions、segments 和一个 name。数据立方体成员的寻址方式为 <CubeName>.<member>,例如 gold_order_items_enriched.count 或 gold_order_items_enriched.order_date。

选择与用户问题匹配的数据立方体和成员。如果现有成员无法回答问题,请如实说明——不要编造成员名称。

从本地数据立方体文件中梳理联接图

revos cubes meta 会告诉你有哪些成员;本地 cubes/ 文件夹会告诉你数据立方体如何连接。读取 cubes/ 中的每个 *.yml 文件,并提取每个数据立方体的 joins: 块——用户定义的语义模型部分就是通过这些块连接起来的。例如:

# cubes/gold_order_items_enriched.yml
apiVersion: revos/v1
kind: Cube
metadata:
  name: gold_order_items_enriched
spec:
  name: gold_order_items_enriched
  joins:
    gold_users_with_order_stats:
      relationship: many_to_one
      sql: "${CUBE}.user_id = ${gold_users_with_order_stats}.user_id"
    gold_product_performance:
      relationship: many_to_one
      sql: "${CUBE}.product_id = ${gold_product_performance}.product_id"

根据这些信息,你可以计算任意一对数据立方体之间有多少条不同的联接路径。这样,你就能在第 2 步中选择正确的路径,而不是让 Cube.js 为你猜测。

注意——系统数据立方体不在 cubes/ 中。 RevOS 会在服务端生成一些数据立方体(评分、分群、模型覆盖层、预聚合覆盖层)。这些数据立方体不会以 YAML 的形式出现在项目中。完整的成员列表只能通过 revos cubes meta 查看。因此:

  • 使用 cubes/ 文件夹查看用户定义的联接图。
  • 使用 revos cubes meta 查看所有实际可查询的成员,包括系统数据立方体。

当用户的问题涉及系统数据立方体时(例如“按分群查看用户”“得分最高的实体”),cubes/ 中的联接图会不完整。不要假定不存在路径——列出元数据并在那里查找。


第 2 步:构建查询——必要时明确指定联接路径

Cube.js 查询是一个 JSON 对象。最常用的结构如下:

{ "measures": ["gold_order_items_enriched.count"] }
{
  "measures": ["gold_order_items_enriched.count"],
  "dimensions": ["gold_order_items_enriched.traffic_source"],
  "order": { "gold_order_items_enriched.count": "desc" },
  "limit": 10
}

时间序列(最适合绘制图表的结构):

{
  "measures": ["gold_order_items_enriched.revenue"],
  "timeDimensions": [
    {
      "dimension": "gold_order_items_enriched.order_date",
      "granularity": "month",
      "dateRange": "last 12 months"
    }
  ]
}

经验法则:

  • 一个度量 + 一个维度 → 按类别绘制柱状图。
  • 一个度量 + 一个带 granularity 的 timeDimensions → 按时间绘制柱状图。
  • 多个度量或多个维度 → 仅以 ASCII 表格呈现;不要尝试绘制图表。
  • 始终使用 limit 设置上限(默认值为 20),以免意外查询返回一百万行。
联接路径——以及如何用提示固定路径

Cube.js 会根据 measures / dimensions / filters 中出现的立方体推断联接路径。当有多条路径可以连接它们时,Cube.js 会替你选择一条——而结果会因选择的路径而异。 这是语义模型查询中导致“数字看起来不对”这类问题的最大来源。

你可以在成员前加上希望 Cube.js 遍历的立方体名称,并用点号分隔,以明确指定路径。最后一段是实际成员;前面的所有部分都是联接提示。示例:

# no hint — Cube.js picks a path
gold_users_with_order_stats.lifetime_revenue

# hint: reach lifetime_revenue via the fact spine, then the users mart
gold_order_items_enriched.gold_users_with_order_stats.lifetime_revenue

提示语法对度量、维度和筛选成员的用法相同。

需要提示时的危险信号:

  1. 查询中的两个立方体在 cubes/ 中由多条路径连接。 常见情况:事实主干联接到两个维度立方体,而其中一个维度立方体_也_联接到另一个维度立方体(例如,订单 → 用户、订单 → 商品、商品通过 last_purchaser_id → 用户)。如果没有提示,Cube.js 可能会选择不明显的路径,并悄悄地多算或少算。
  2. 用户的问题隐含指定了路径(“按用户所在国家/地区统计每个订单项的收入”与“按用户所在国家/地区统计每个用户的收入”)——粒度不同,路径不同,数字也不同。
  3. 返回的计数小于最小立方体所能达到的值,或大于最大事实表所能达到的值。这说明 Cube.js 可能通过错误的联接进行了扇出。
  4. 链路中有多对多关系。务必固定路径。
  5. 你添加了一个度量,且数字发生了显著变化(超过约 10%),而维度集合没有变化——Cube.js 很可能重新选择了联接路径。

示例——相同的问题,两种答案。

立方体(来自 cubes/):

gold_order_items_enriched  ──many_to_one──▶  gold_users_with_order_stats
gold_order_items_enriched  ──many_to_one──▶  gold_product_performance
gold_product_performance   ──many_to_one──▶  gold_users_with_order_stats   (last_purchaser_id)

问题:“按用户所在国家/地区统计总收入。”

// Path A — order items → users directly (CORRECT for per-item revenue)
{
  "measures": ["gold_order_items_enriched.revenue"],
  "dimensions": [
    "gold_order_items_enriched.gold_users_with_order_stats.country"
  ]
}
// Path B — order items → product → last_purchaser (WRONG: revenue gets
// attributed to the last buyer's country, not the actual buyer's)
{
  "measures": ["gold_order_items_enriched.revenue"],
  "dimensions": [
    "gold_order_items_enriched.gold_product_performance.gold_users_with_order_stats.country"
  ]
}

这两个查询返回的数字截然不同。务必在图表下方的说明中指出所用路径以及原因,以便用户发现错误的选择。

示例——通过特定路径进行筛选。

{
  "measures": ["gold_order_items_enriched.revenue"],
  "dimensions": ["gold_order_items_enriched.traffic_source"],
  "filters": [
    {
      "member": "gold_order_items_enriched.gold_users_with_order_stats.country",
      "operator": "equals",
      "values": ["DE"]
    }
  ]
}

选择路径的规则:

  • 默认选择经过问题自然对应粒度的事实主干的最短路径(每个订单的收入 → 主干 = gold_order_items_enriched;拥有 X 的用户 → 主干 = gold_users_with_order_stats)。
  • 绝不要悄悄采用备用路径。如果两条路径都合理,请在运行查询前询问用户想要哪种粒度。
  • 如果 revos cubes meta 返回的成员名称本身包含点号(例如 cubeA.cubeB.foo),那就是限定形式——原样粘贴,不要去掉前缀。

第 3 步:运行查询

revos cubes query --query '<INLINE_JSON>' --json

如果内容超过一行,请将 JSON 写入临时文件,并使用 @ 传入:

cat > /tmp/q.json <<'EOF'
{
  "measures": ["gold_order_items_enriched.revenue"],
  "timeDimensions": [
    {
      "dimension": "gold_order_items_enriched.order_date",
      "granularity": "month",
      "dateRange": "last 12 months"
    }
  ]
}
EOF
revos cubes query --query @/tmp/q.json --json

响应是原始的 Cube.js 负载——data 是行数组,每一行都是一个扁平对象,键为查询中使用的相同成员名称。

如果 API 返回错误,请原样显示错误消息。常见原因包括:成员名称拼写错误(重新检查 revos cubes meta)、立方体尚未应用(revos status / revos apply),或 dateRange 指向的列不是时间维度。


第 4 步:在聊天中呈现结果

在助手的回复中输出两个代码块,并都使用围栏代码,以便以等宽字体显示:

4a. ASCII 表格——始终显示

显示所有返回的行。数值列右对齐,其他所有内容左对齐。数字使用千位分隔符;非整数四舍五入到 2 位小数。将较长的字符串值截断为 32 个字符,并在末尾加上 …。

traffic_source       count
─────────────────  ───────
Search             142,318
Organic             88,204
Email               41,907
Facebook            22,015
Display              9,471
4b. ASCII 条形图——形状允许时

如果查询恰好返回一个度量值和一个维度(类别或时间),就在表格下方绘制水平条形图。根据结果集中的最大值,将条形缩放至 40 个字符宽;填充单元格使用 █ 块字符,并在末尾加一个空格。

Search        ████████████████████████████████████████  142,318
Organic       ████████████████████████▊                  88,204
Email         ███████████▊                               41,907
Facebook      ██████▏                                    22,015
Display       ██▋                                         9,471

时间序列示例(从最早到最新,从上到下):

2025-06  ███████████▊                                   €  41,200
2025-07  █████████████████▏                             €  59,800
2025-08  ████████████████████▋                          €  72,400
…

以下情况跳过图表(仅显示表格):

  • 结果集为空。
  • 查询包含 2 个或更多度量值、2 个或更多维度,或仅有一个标量值(没有维度)。
  • 所有度量值都为零或 null。

对于单个标量答案(一行一列),只需在正文中说出数字,然后附上一行表格——不绘制图表。


步骤 5:简要说明结果

输出渲染结果后,用 1–3 个简短的英文句子说明:测量了什么、最高/最低的分组,以及任何明显的异常(例如缺少某个月份、某个类别占据总量的大部分)。只陈述表格中可见的事实——不要推测原因。

如果用户可能希望进一步细分查询,可以建议他们接下来提出 一个 具体的后续查询(“想按国家查看相同数据吗?”)。不要列出多个——保持对话简洁。


规则

  • 切勿硬编码 cube 或成员名称。编写查询前,始终通过 revos cubes meta 确认名称。
  • 编写多 cube 查询前,检查 cubes/*.yml 中的 joins: 区块。如果所需的 cube 之间存在多条连接路径,请用点分隔的 join 提示明确选择一条路径,不要让 Cube.js 自行判断。
  • 请记住,系统 cube(scoring、segments、model overlays)只存在于 revos cubes meta 中,不在 cubes/ 里。它们的 join 在 YAML 中不可见——只能在 meta 输出中查看。
  • 始终渲染表格;仅当数据形状支持时才渲染图表。
  • 保持设置 limit(默认值为 20)。对于“前 N 个”问题,将 limit 设为 N,并为度量值添加 order。
  • 在 shell 中安全地引用查询 JSON——优先使用临时文件 @/tmp/q.json,而不是包含嵌套引号的长内联字符串。
  • 不要将查询结果写入磁盘,也不要调用 Python 绘图工具——此技能仅用于聊天中。若要生成 cube 图本身的静态 PNG 可视化,请使用 visualize-semantic-model。
  • 如果 revos cubes meta 未返回任何 cube,请停止并告知用户先运行 revos apply。

评论

支持 Markdown