Files
opencode-build/skills/sql-context-builder/SKILL.md
T
2026-06-22 10:08:52 +08:00

7.2 KiB

name, description
name description
sql-context-builder 当需要组装 SQL 生成器与评审器共同消费的、唯一可信的 SQL Context 时使用。触发词包括"build SQL context"、"固化 join key"、"table aliases"、"列出来自哪张表"、"准备写 SQL"、"context 已经齐了"。消费已校验的 Logic Plan 与 Field Mapping,产出带有稳定 alias、固化 join key、显式 fact/dimension 标记的标准化 SQL Context。SQL 生成器**不得**再重新推导其中任何一项。

SQL Context Builder(SQL 上下文构建器)

概述(Overview)

把 SQL 生成器本来要重复发明的所有决策一次性固化下来:alias、join key、字段来源、类型、角色标签(fact / dimension / filter)、时间列,都在这里只此一次地钉死。输出是 sql-review 及任何 SQL 输出步骤的唯一可信输入

核心原则:任何不在 SQL Context 中的字段,下游 skill 视同不存在。 如果下游 skill 还需要新内容,请求必须先回退到 metadata-validatorlogic-planner

何时使用(When to Use)

使用场景:

  • logic-planner 返回状态 PLANNED
  • 用户即将写 Spark SQL,需要一份规范化的 context(alias、join key、字段来源)
  • 同一份逻辑要产出多版 SQL 草稿 — context 保证它们保持一致

不要使用场景:

  • logic-plan.statusNEED_USER_CONFIRMATION — 回到 logic-planner
  • SQL 极简单(单表无 join) — 仍然建议构建 context,除非用户明确跳过
  • 任务是把 plan 翻译成 SQL — 那是另一回事,不是本 skill 的职责

输入(Inputs)

  1. 来自 logic-plannerlogic_plan(状态 PLANNED)
  2. 来自 metadata-validatorfield_mapping(状态 VALIDATED)
  3. 原始 validation_result(用于表级元数据:类型、来源)

工作流(Workflow)

digraph sql_context_builder {
    "读取 logic_plan + field_mapping + validation_result" [shape=box];
    "为每张表分配稳定 alias" [shape=box];
    "固化 join key(left_alias.field = right_alias.field)" [shape=box];
    "把每个字段解析为 (alias, column, type, source_file)" [shape=box];
    "为每个字段打标签:dimension | metric | filter | time | join_key" [shape=box];
    "标记事实表 / 维度表" [shape=box];
    "校验 Logic Plan 的字段在 context 中全部存在" [shape=box];
    "所有必需字段都齐全?" [shape=diamond];
    "输出 SQL Context" [shape=box];
    "输出 SQL Context + missing_fields" [shape=box];

    "读取 logic_plan + field_mapping + validation_result" -> "为每张表分配稳定 alias";
    "为每张表分配稳定 alias" -> "固化 join key(left_alias.field = right_alias.field)";
    "固化 join key(left_alias.field = right_alias.field)" -> "把每个字段解析为 (alias, column, type, source_file)";
    "把每个字段解析为 (alias, column, type, source_file)" -> "为每个字段打标签:dimension | metric | filter | time | join_key";
    "为每个字段打标签:dimension | metric | filter | time | join_key" -> "标记事实表 / 维度表";
    "标记事实表 / 维度表" -> "校验 Logic Plan 的字段在 context 中全部存在";
    "校验 Logic Plan 的字段在 context 中全部存在" -> "所有必需字段都齐全?";
    "所有必需字段都齐全?" -> "输出 SQL Context" [label="yes"];
    "所有必需字段都齐全?" -> "输出 SQL Context + missing_fields" [label="no"];
}

规则(Rules)

Alias 分配

  • 一张表一个 alias,绝不重用
  • 事实表(承载主指标的表)优先拿到短 alias(oft1)
  • 维度表用有意义的 alias(c 代表 customer,p 代表 product,ch 代表 channel)
  • alias 在此处固化,SQL 生成器与评审器不得再发明新 alias

Join Key 固化

  • 对每条 logic_plan.steps[kind=join],固化精确的谓词:
    • left_alias.col1 = right_alias.col2
  • 包含 join 类型(inner / left / right / full / semi / anti)
  • 沿用 Logic Plan 中的 cardinality 假设
  • 如果两张表有多种可能的 join key,在此处固化为所选的那一个,其他候选不再纳入 SQL 范畴

字段解析(Field Resolution)

对 Logic Plan 用到的每个字段,记录:

  • alias(字段所在表)
  • column(物理列名)
  • type(取自 validation_result)
  • source_file(元数据文件来源 — 用于追溯)
  • role:取自 dimension | metric | filter | time | join_key | derived 之一

一个字段可以有多个 role(例如 customer_id 同时是 join_keydimension),全部列出。

Fact vs Dimension 标记

  • 承载主要事件/度量的表为 fact
  • 起丰富作用的表(customer、product、channel、city)为 dimension
  • 一条 query 只能有一个 fact。如果 Logic Plan 隐含多张事实表,作为 pending_question 抛出 — 那是规划问题,不是 context 问题

覆盖度校验

  • logic_plan.steps 中的每一步引用的字段都必须存在于本 context
  • 每条 field_mapping 都必须出现在本 context
  • 每个 group_byorder_by 字段都必须有标签
  • aggregate 中的每个指标都必须有 metric 标签
  • filter 中的每条谓词都必须引用 filtertime 标签的字段

输出契约(Output Contract)

sql_context:
  aliases:
    - alias: o
      table: loan_order
      role: fact
      source_file: metadata/loan_order.json
    - alias: c
      table: customer_info
      role: dimension
      source_file: metadata/customer_info.md
  joins:
    - left: { alias: o, column: customer_id }
      right: { alias: c, column: customer_id }
      type: left
      cardinality_assumption: 1:N
  fields:
    - alias: o
      column: customer_id
      type: string
      role: [join_key]
      source_file: metadata/loan_order.json
    - alias: o
      column: loan_amount
      type: decimal(18,2)
      role: [metric]
      source_file: metadata/loan_order.json
    - alias: o
      column: apply_time
      type: timestamp
      role: [time, filter]
      source_file: metadata/loan_order.json
    - alias: c
      column: city
      type: string
      role: [dimension]
      source_file: metadata/customer_info.md
  metrics:
    - alias: total_loan
      expr: "SUM(o.loan_amount)"
      agg: sum
    - alias: customer_cnt
      expr: "COUNT(DISTINCT o.customer_id)"
      agg: count_distinct
  dimensions: ["c.city"]
  time_grain:
    column: o.apply_time
    granularity: day
  filters:
    - "o.status = 'SUCCESS'"
  missing_fields: []
  status: <READY | NEED_USER_CONFIRMATION>

禁止行为(Forbidden Behaviors)

自我说服 现实
"alias 让 SQL 生成器自己选" alias 就在此处固化。下游重新起 alias 会破坏 sql-review
"join key 太显然,跳过固化" 整个 skill 的意义就在于此 — 把 join key 钉死。
"字段类型无所谓,SQL 里都是字符串" 类型决定了聚合、cast、分区裁剪的策略。
"派生列到 SQL 阶段再发明就行" 每个派生列都必须在 Logic Plan 里就出现。
"多张事实表?全 join 上就行" 多事实是 Logic Plan 的问题,要暴露并停止。

完成判定(Completion Criteria)

仅当以下全部成立时,status: READY:

  • Logic Plan 的每一步在本 context 中都有对应条目
  • missing_fields 为空
  • 每个指标都有 aggexpr
  • 每个 join 都有固化的 key 和 type
  • 时间粒度已设定