update by wensicheng on 0629

This commit is contained in:
tao.chen
2026-07-01 17:46:08 +08:00
parent 0fc1c702d9
commit 2f406bd64d
12 changed files with 1012 additions and 1574 deletions
+93 -104
View File
@@ -1,127 +1,116 @@
---
name: sql-review
description: 对执行前生成好的 PySpark SQL 字符串做静态评审时使用。触发词包括"review this SQL"、"SQL 评审"、"静态审查"、"risk level"、"check join cardinality"、"数据膨胀"、"重复聚合"、"分区裁剪"。消费 SQL 字符串及其产出的 SQL Context,产出按风险分级的评审结果(HIGH / MEDIUM / LOW)以及具体修复建议。**不修改 SQL** — 只报告发现
description: 当一条 Spark SQL 已经生成、通过 guardrails 并准备展示或执行前使用。它基于 sql_context 静态检查字段引用、alias、join key、数据膨胀重复聚合、时间过滤、转化顺序、GROUP BY、空值、类型和写入风险,只报告问题,不直接改 SQL
---
# SQL Review(SQL 评审)
# SQL ReviewSQL 静态评审
## 概述(Overview)
## 目标
对一条生成好的 PySpark SQL 字符串做执行前的静态评审。评审者**不**改写 SQL — 它只输出发现项、严重程度和建议修复。由 SQL 的作者决定采纳哪些
在 SQL 执行前做质量检查,发现可能导致数字错误、执行失败或性能风险的问题。评审只报告风险和建议,不负责直接改写 SQL
核心原则:**对照 SQL Context 评审,而不是凭直觉。** SQL 中引用的每一个字段、alias、join key 都必须存在于产出它的 SQL Context。如果不存在,这就是一项 `HIGH` 风险(SQL 绕过了契约)。
## 输入
## 何时使用(When to Use)
**使用场景:**
- 一条 SQL 字符串已经从 `sql_context` 生成出来,即将执行
- 用户粘贴一条 SQL 字符串,要求评审、优化或风险评估
- 代码改动为 PySpark 脚本新增了 `spark.sql(...)` 调用
**不要使用场景:**
- 这条 SQL 不是 Spark SQL(例如纯 Postgres、HiveQL 在 Spark 之外)— 至少要把 Spark 特有的检查替换成对应引擎的等价检查,并标注出来
- 是一次性、不会被复用的交互查询 — 用户可以主动选择跳过
## 输入(Inputs)
1. 要评审的 **SQL 字符串**
2. 产出这条 SQL 的 **SQL Context**(如果缺失,需要显式声明"该 SQL 是不可信 / 临时拼凑的")
如果 SQL Context 缺失,抛出一项 `HIGH` 风险:*"SQL 没有对应的 SQL Context;下游 skill 无法保证字段 / join 的正确性。"* 然后继续做结构性检查。
## 严重程度模型(Severity Model)
| 等级 | 含义 | 处置 |
|---|---|---|
| `HIGH` | 会导致数字错误、执行失败或违反策略 | 阻断执行,必须修。 |
| `MEDIUM` | 大概率对,但低效、脆弱或大规模场景有风险 | 上线前修,开发环境可放过。 |
| `LOW` | 风格 / 最佳实践小瑕疵 | 可选修。 |
按以下顺序输出发现项:`HIGH``MEDIUM``LOW`。任一 `HIGH` 让整体评审结论为 `FAIL`
## 工作流(Workflow)
```dot
digraph sql_review {
"解析 SQL(逻辑层面,不只是正则)" [shape=box];
"对照 SQL Context 交叉校验 alias、字段、join key" [shape=box];
"检测多语句 / 禁用 verb(防护栏)" [shape=box];
"分析 join 基数与膨胀风险" [shape=box];
"检测同一度量的重复聚合" [shape=box];
"检测全表扫描与缺失分区过滤" [shape=box];
"校验 GROUP BY 与 SELECT 一致" [shape=box];
"检查 join key 与度量的空值处理" [shape=box];
"检查字段类型与运算的匹配" [shape=box];
"汇总成评审结论" [shape=box];
"解析 SQL(逻辑层面,不只是正则)" -> "对照 SQL Context 交叉校验 alias、字段、join key";
"对照 SQL Context 交叉校验 alias、字段、join key" -> "检测多语句 / 禁用 verb(防护栏)";
"检测多语句 / 禁用 verb(防护栏)" -> "分析 join 基数与膨胀风险";
"分析 join 基数与膨胀风险" -> "检测同一度量的重复聚合";
"检测同一度量的重复聚合" -> "检测全表扫描与缺失分区过滤";
"检测全表扫描与缺失分区过滤" -> "校验 GROUP BY 与 SELECT 一致";
"校验 GROUP BY 与 SELECT 一致" -> "检查 join key 与度量的空值处理";
"检查 join key 与度量的空值处理" -> "检查字段类型与运算的匹配";
"检查字段类型与运算的匹配" -> "汇总成评审结论";
}
```yaml
sql: "待评审的 Spark SQL"
sql_context:
status: CONTEXT_READY
sql_guard:
status: PASS
logic_plan:
status: PLANNED
```
## 检查项(Checks,全部执行)
如果没有 `sql_context`,也可以继续做基础结构检查,但必须报告一个 HIGH 风险:缺少上下文,无法保证字段和 join 正确。
1. **引用完整性(Reference integrity)** — SQL 中的每个 `<alias>.<column>` 都必须存在于 SQL Context。不匹配即 `HIGH`
2. **Join key 匹配** — 每个 `ON` 谓词都必须对得上 SQL Context 中已固化的 join。凭空发明的 join 算 `HIGH`
3. **禁用 verb / 多语句** — 交给 `pyspark-sql-guardrails`。任何 DDL/DML/维护 verb,或任何 `;` 堆叠的语句,均为 `HIGH`
4. **Join 基数风险** — 对每个 join,如果 SQL Context 标记基数为 `1:N``N:N` 且没有去重,分别抛 `MEDIUM`(1:N)或 `HIGH`(N:N)
5. **重复聚合** — 同一度量被聚合两次(例如子查询里一次、外层再一次)且非有意重新聚合,抛 `HIGH`
6. **分区 / 时间过滤** — 对任何有已知时间字段的事实表,缺少对该字段的 `WHERE` 过滤即为 `HIGH`(全表扫描风险)
7. **GROUP BY 完整性** — SELECT 中每个未聚合的列都必须出现在 `GROUP BY` 中。不匹配为 `HIGH`
8. **空值处理** — 可空 join key 上做 `INNER JOIN` 会静默丢行(`MEDIUM`);可空度量未用 `COALESCE` 聚合,可能产出 `NULL` 进而破坏下游 `WHERE ... > 0`(`MEDIUM`)
9. **类型不匹配** — 字符串列未做 cast 就参与数值聚合(`MEDIUM`);日期字符串和 `TIMESTAMP` 字面量比较(`MEDIUM`)
10. **Spark 最佳实践**`COUNT(*)` vs `COUNT(1)` 属于风格(`LOW`);生产环境用 `SELECT *``MEDIUM`;数据倾斜 join 缺 `DISTRIBUTE BY` / `CLUSTER BY``LOW``MEDIUM`
## 检查项
## 输出契约(Output Contract)
1. 字段引用:每个 `alias.column` 是否存在于 `sql_context.fields`
2. aliasSQL 是否发明了 context 中没有的 alias。
3. join keyON 条件是否匹配 context 中固定的 join。
4. join 基数:1:N 或 N:N join 是否会造成数据膨胀。
5. 指标公式:聚合表达式是否符合 logic plan 和 context。
6. 重复聚合:同一指标是否被重复 sum/count。
7. 时间过滤:事实表是否缺少必要时间或分区过滤。
8. 时间边界:T-1 完整日是否使用左闭右开窗口。
9. 转化顺序:后置事件是否满足前置事件之后发生。
10. GROUP BY:非聚合输出列是否都在 group by 中。
11. 空值策略:join key 和度量字段是否需要 coalesce 或保留策略。
12. 类型匹配:字符串日期、字符串金额是否需要 cast。
13. 写入风险:INSERT 目标列、overwrite 确认、分区覆盖范围是否明确。
14. 执行成本:是否存在无分区裁剪的大表全扫、笛卡尔积、窗口无 partition。
## 时间边界检查
当需求为“近 N 天按 T-1 完整日”且字段是 timestamp/date-like
推荐:
```sql
event_time >= date_sub(current_date(), N)
AND event_time < current_date()
```
风险写法:
```sql
event_time BETWEEN date_add(current_date(), -N) AND date_add(current_date(), -1)
```
如果 SQL 使用风险写法,至少标为 `MEDIUM`;如果会明显漏掉结束日期白天数据,标为 `HIGH`
## 转化顺序检查
对注册、付费、激活、下单等漏斗指标,必须检查后置事件是否在前置事件之后。若口径是“注册后付费”,SQL 必须有等价约束:
```sql
pay_time >= register_time
```
如果 SQL 先按用户取 `MIN(pay_time)`,再和 `register_time` 比较,而没有在聚合前加入 `pay_time >= register_time`,标为 `HIGH``MEDIUM`,因为这可能漏算注册后仍然完成付费的用户。
## 输出格式
```yaml
sql_review:
verdict: <PASS | FAIL>
highest_severity: <HIGH | MEDIUM | LOW>
summary: "<一句话总结>"
verdict: PASS | FAIL
highest_severity: NONE | LOW | MEDIUM | HIGH
summary: "一句话结论"
findings:
- id: F1
severity: HIGH
check: reference_integrity
location: "<行/列号或 SQL 片段>"
message: "<错在哪>"
fix: "<具体的建议修改>"
- id: F2
severity: MEDIUM
check: join_cardinality
location: "JOIN customer_info"
message: "1:N join without pre-aggregation; risk of row inflation"
fix: "Aggregate loan_order by customer_id before joining"
- id: R1
severity: HIGH | MEDIUM | LOW
check: reference_integrity | join_cardinality | metric_formula | time_filter | time_boundary | conversion_order | group_by | type_check | write_risk | performance | other
location: "SQL 片段或行号"
message: "问题描述"
recommendation: "建议修复方向"
notes:
- "SQL Context provided: yes (sql_context.aliases = [...])"
- "Reviewed by: sql-review v1"
- "评审假设"
```
## 禁止行为(Forbidden Behaviors)
## 用户可见摘要
| 自我说服 | 现实 |
|---|---|
| "SQL 看起来没问题,不用对一遍 context" | 字段可能"看起来对"但源表里根本没有,必须做交叉校验。 |
| "我顺手在评审时把 SQL 修了" | 评审只报告,修是作者的事。职责分离。 |
| "没给 SQL Context,那字段检查就跳过" | 缺 context 本身就是 `HIGH`,绝不能悄悄跳过。 |
| "只是开发查询,分区检查跳过" | 这一项成本极低,跳过就会留下静默的全表扫描。 |
| "1:N join 是常态,没风险" | 在保留粒度的前提下 1:N 是 OK 的,但评审必须验证,不能假设。 |
| "用正则就够了,能查 GROUP BY" | GROUP BY 成员关系是结构性的,要用解析器,不要用正则。 |
除了 YAML,必须输出:
## 完成判定(Completion Criteria)
```markdown
## SQL Review
- 结论:PASS / FAIL
- 最高风险:NONE / LOW / MEDIUM / HIGH
- 已检查:字段引用、join、GROUP BY、时间窗口、转化顺序、空值、类型、性能
- 主要风险:...
- 是否可交付:...
```
评审完成的条件:
## 严重程度
- 全部 10 项检查都已评估
- 每一条 `HIGH` 风险都有 `fix`
- 当且仅当 `highest_severity``MEDIUM``LOW` 时,`verdict` 才为 `PASS`
- 输出的 YAML 可直接贴到 PR 评论,无需修改
- `HIGH`:会导致数字错误、执行失败、越权写入或违反策略,必须阻断。
- `MEDIUM`:大概率可跑,但有性能、稳定性或边界风险,需要用户接受。
- `LOW`:风格或可维护性问题。
只要存在 HIGH`verdict` 就是 `FAIL`。只有 LOW/MEDIUM 时,可以把风险交给用户决定是否接受。
## 禁止行为
- 不要直接改 SQL;只输出发现和建议。
- 不要凭直觉审字段,必须对照 `sql_context`
- 不要因为 SQL 能通过 guardrails 就跳过业务正确性审查。
- 不要忽略写入模式,尤其是 `INSERT OVERWRITE`