--- name: sql-context-builder description: 当需要组装 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-validator` 和 `logic-planner`。 ## 何时使用(When to Use) **使用场景:** - `logic-planner` 返回状态 `PLANNED` - 用户即将写 Spark SQL,需要一份规范化的 context(alias、join key、字段来源) - 同一份逻辑要产出多版 SQL 草稿 — context 保证它们保持一致 **不要使用场景:** - `logic-plan.status` 为 `NEED_USER_CONFIRMATION` — 回到 `logic-planner` - SQL 极简单(单表无 join) — 仍然建议构建 context,除非用户明确跳过 - 任务是把 plan 翻译成 SQL — 那是另一回事,不是本 skill 的职责 ## 输入(Inputs) 1. 来自 `logic-planner` 的 `logic_plan`(状态 `PLANNED`) 2. 来自 `metadata-validator` 的 `field_mapping`(状态 `VALIDATED`) 3. 原始 `validation_result`(用于表级元数据:类型、来源) ## 工作流(Workflow) ```dot 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(`o`、`f`、`t1`) - 维度表用有意义的 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_key` 和 `dimension`),全部列出。 ### Fact vs Dimension 标记 - 承载主要事件/度量的表为 `fact` - 起丰富作用的表(customer、product、channel、city)为 `dimension` - 一条 query 只能有一个 fact。如果 Logic Plan 隐含多张事实表,作为 `pending_question` 抛出 — 那是规划问题,不是 context 问题 ### 覆盖度校验 - `logic_plan.steps` 中的每一步引用的字段都必须存在于本 context - 每条 `field_mapping` 都必须出现在本 context - 每个 `group_by`、`order_by` 字段都必须有标签 - `aggregate` 中的每个指标都必须有 `metric` 标签 - `filter` 中的每条谓词都必须引用 `filter` 或 `time` 标签的字段 ## 输出契约(Output Contract) ```yaml 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: ``` ## 禁止行为(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` 为空 - 每个指标都有 `agg` 和 `expr` - 每个 join 都有固化的 key 和 type - 时间粒度已设定