Skip to content
Back to skills

Sql Generation Helper

ASecurity

SQL 生成——把一句话的取数需求写成只读 SQL:先确认表结构与口径,再用 JOIN、聚合、窗口函数、日期处理的常用模式写查询。当用户说「帮我写个 SQL」「这个指标怎么查」「按月统计一下」时使用。

  • 2 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 13, 2026
ai-agentspythonbashsqldatabase

Security analysis

A100/100

Pro scans all 4 files and shows the line behind each finding

Scanned October 13, 2026

npx -y skills add po-et/workbuddy-skills --skill sql-generation-helper --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Sql Generation Helper?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Sql Generation Helper
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/po-et-sql-generation-helper/badge)](https://www.skillsdirectory.com/skills/po-et-sql-generation-helper)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: sql-generation-helper
description: "SQL 生成——把一句话的取数需求写成只读 SQL:先确认表结构与口径,再用 JOIN、聚合、窗口函数、日期处理的常用模式写查询。当用户说「帮我写个 SQL」「这个指标怎么查」「按月统计一下」时使用。"
author: Captain
metadata:
  openclaw:
    requires:
      bins: [python3]
version: 0.1.1
display_name: "SQL 生成"
display_name_en: "SQL Generation"
description_zh: "把自然语言的取数需求写成 SQL:先确认表结构与字段口径,再写查询;给出 JOIN、聚合、窗口函数、日期处理的常用模式,每条 SQL 附「它回答什么问题」与「可能的坑」(NULL、重复行、时区)。只写只读查询,改数据、改表先给风险提示并等确认。"
description_en: "Turn plain-language data questions into read-only SQL: confirm schema and metric definitions first, use proven JOIN, aggregation, window-function and date patterns, and ship every query with the question it answers and its pitfalls (NULLs, duplicate rows, time zones)."
tags:
  - "SQL生成"
  - "SQL"
  - "写SQL"
  - "取数"
  - "数据查询"
  - "MySQL"
  - "PostgreSQL"
  - "窗口函数"
  - "text2sql"
  - "数据分析"
examples_zh:
  - "帮我写个 SQL:查上个月每个城市的下单用户数"
  - "这个指标怎么查?复购率,表结构我贴给你"
  - "按月统计一下各渠道的支付金额,库是 MySQL 8"
examples_en:
  - "Write me a SQL query for the number of ordering users in each city last month."
  - "How do I calculate the repeat purchase rate? I'll paste the table schema."
  - "Sum paid amounts by channel for each month. The database is MySQL 8."
---

# SQL 生成

定位一句话:**SQL 出错的大头不在语法,在口径:同一句「上个月的活跃用户」,三个人能写出三个数。先钉死口径,再写查询。**

**文件导航**:本页是主流程。五个常用模式的完整 SQL 见 [references/patterns.md](references/patterns.md);复购率的完整交付(贴表结构 → 澄清口径 → SQL → 坑 → 对账)见 [examples/repurchase-rate.md](examples/repurchase-rate.md);交付前的机械检查脚本是 `scripts/sql_guard.py`。

## 何时使用

把一句话的取数需求写成 SQL;按月、按渠道统计;取每组最新一条、算环比;已有 SQL 结果对不上,要查哪一步多算或漏算了。用户常这样说:

- 「帮我写个 SQL:查上个月每个城市的下单用户数」
- 「这个指标怎么查?复购率,表结构我贴给你」
- 「按月统计一下各渠道的支付金额,库是 MySQL 8」
- 「取每个用户最近一笔订单」
- 「这条 SQL 算出来比报表多,帮我看哪里重复了」
- 「这句 MySQL 的 SQL 改成 PostgreSQL 怎么写」

不适用:

- 要直接连库执行、导出数据:本技能只写 SQL,不连数据库、不代为执行;执行和权限申请走你们自己的流程。
- 索引设计、慢查询深度调优、库表设计:交给 DBA;这里只指出明显问题(函数包字段、缺连接条件)。
- 在 Excel 里做汇总、做报表版式:转交 Excel 或报表类技能。

## 先确认:表结构与口径

动笔前拿齐五样,缺哪样问哪样,一次最多问 3 个:

```
方言    MySQL 8 / PostgreSQL / Hive……日期函数、窗口函数的写法各不相同
表结构  建表语句或字段清单;拿不到就请用户跑下面这条只读查询
粒度    每张表一行是什么:一个订单?订单里的一件商品?一个用户一天的快照?
口径    指标 = 分子 / 分母;按哪个时间字段(下单 / 支付);排除什么(测试单、取消单、退款)
时区    时间存的是 UTC 还是本地时间;报表按哪个时区切天
```
```sql
-- 回答:orders、users 各有哪些字段、什么类型、能否为空(MySQL、PostgreSQL 通用)
-- 坑:多个库有同名表时结果会混在一起,加 table_schema 条件
SELECT table_name, column_name, data_type, is_nullable
FROM information_schema.columns
WHERE table_name IN ('orders', 'users')
ORDER BY table_name, ordinal_position;
```
口径用一句话复述给用户确认,例如「月活跃用户 = 该自然月(北京时间)内至少 1 笔已支付订单的去重用户数,不含测试账号」。确认了再写。这条查询的结果导出成 CSV,还能交给 `scripts/sql_guard.py --columns` 核对 SQL 里的字段名。

## 从一句话到 SQL:五个步骤

```
1 改写    「对每个 [分组],算 [指标],条件 [过滤],时间 [范围]」,填不满说明还没问清
2 定粒度  结果一行是什么(一个城市一个月),据此写 GROUP BY
3 查连接  每个 JOIN 先问一对一还是一对多;一对多先聚合再连接,否则金额被放大
4 写查询  用 WITH 分步,每步一句注释;只选要用的列,不写 SELECT *
5 对账    中间结果查 COUNT(*) 与 COUNT(DISTINCT 主键) 是否相等;合计与已知报表比一比
```

## 常用模式:每条附「它回答什么问题」与「可能的坑」

五个模式的完整 SQL(含 PostgreSQL 写法注释)在 [references/patterns.md](references/patterns.md),用之前先看它的「坑」:

| 模式 | 它回答什么问题 | 最要命的坑 |
|---|---|---|
| JOIN 先聚合再连接 | 每个用户的注册渠道和累计支付金额,没下过单的记 0 | 一对多连接后 SUM,金额按明细行数重复累加;LEFT JOIN 后在 WHERE 过滤右表等于 INNER JOIN |
| 条件聚合 | 各渠道的订单数、支付订单数和支付率 | COUNT(列) 不数 NULL;PostgreSQL 整数相除取整,要乘 1.0 |
| 窗口:每组最新一条 | 每个用户最近一笔订单 | 同一时刻两笔时次序不定,排序加主键兜底;并列都要保留用 RANK() |
| 窗口:环比 | 每月支付金额及环比 | 某月没单那一行不存在,LAG 会拿更早的月来比;UTC 存储要先换时区 |
| 日期:左闭右开 | 北京时间某月的支付订单数 | BETWEEN 漏掉最后一天;函数包在字段上用不上索引 |

## 只读闸门:改数据、改表之前

默认只写 SELECT。用户要 INSERT、UPDATE、DELETE、ALTER、DROP、TRUNCATE 时,先回下面的风险提示,等用户明确回复「确认执行」再给语句:

```
影响范围  先跑同条件的 SELECT COUNT(*),报出会动多少行
可回退    有没有备份;回滚是反向语句还是从备份恢复
执行方式  DML 放进事务,先 ROLLBACK 演练一次;大表分批;MySQL 的 DDL(含 TRUNCATE)会隐式提交,事务包不住
时机      避开业务高峰;DDL 可能锁表,先问 DBA
```

交付前的机械检查(它跳过注释和字符串,REPLACE() 函数、update_time 这类字段名不会误报成写操作):

```bash
python3 scripts/sql_guard.py query.sql --dialect mysql
python3 scripts/sql_guard.py query.sql --dialect postgresql --columns columns.csv
```

结果码:B = 写操作、DDL、SET/VACUUM 或可执行注释(必须过上面的闸门);W = 常见坑(SELECT *、BETWEEN 日期、NOT IN 子查询、LIMIT 没配 ORDER BY、函数包字段、JOIN 缺条件、LEFT JOIN 后 WHERE 过滤右表、整数相除、和 NULL 用等号比较、FOR UPDATE);D = 所选方言不支持的写法;C = 字段名在字段清单里找不到。MySQL `/*! ... */`(含版本号)和 MariaDB `/*M! ... */` 会先阻断:展开成普通 SQL 后再查,不把它们当成可忽略的注释。执行只读查询时还可以加一道保险:MySQL 用 `START TRANSACTION READ ONLY;`,PostgreSQL 用 `BEGIN READ ONLY;`,事务里的写入会直接报错。

## 信息不全或出错时

| 情况 | 怎么处理 | 对用户说的话(模板) |
|---|---|---|
| 没给表结构 | 先请用户跑上面那条 information_schema 查询;等不到就按常见命名写,表名、字段名标【待确认】并说明是假设 | 「我先按常见命名写了,表名和字段名都标了【待确认】;跑一下这条只读查询把字段清单贴给我,我换成真实的。」 |
| 口径不清,一个指标有两种算法 | 列出两种理解让用户选,确认前不交最终 SQL | 「复购率常见两种:A 当月买 2 次及以上的人 ÷ 当月购买人数;B 上月买过、本月又买的人 ÷ 上月购买人数。按哪种?」 |
| 方言没说 | 默认 MySQL 8 并标【待确认】;日期函数、窗口函数处注明 PostgreSQL 写法 | 「先按 MySQL 8 写,标了【待确认】;如果是 PostgreSQL 或 Hive,告诉我,我换日期函数。」 |
| 需求自相矛盾(如「上个月」却给了本月的日期范围;分组字段在给的表里不存在) | 指出矛盾,给两种理解让用户选 | 「你说上个月,但给的是 9 月 1 日到今天——按 8 月整月,还是 9 月至今?」 |
| 用户贴来的 SQL 跑不通或结果对不上 | 先用 `sql_guard.py --dialect` 查方言和常见坑;再按五步第 5 步对账:行数、去重主键数、合计、时间字段、剔除规则、时区 | 「DATE_FORMAT 是 MySQL 的函数,你们是 PostgreSQL,要换成 to_char;另外先跑这两条对账查询看差在哪一步。」 |
| 要写操作(INSERT/UPDATE/DELETE/DDL) | 走只读闸门:先回风险提示,等明确回复「确认执行」 | 「这是写操作,先确认四件事:会动多少行、怎么回滚、事务里先演练、避开高峰。回复『确认执行』后我再给语句。」 |
| 超出范围:连库执行、绕过权限、拼注入语句、导出个人信息明细 | 不做;执行走用户自己的流程;个人信息只给聚合或脱敏版本 | 「我不连库,也不写绕过权限的查询;手机号这类字段我只给脱敏或聚合的写法。」 |
| 时间紧,只要能跑的最小版 | 最小版:一条 SQL+一句口径+一条对账查询;坑只写最要命的一条 | 「先给你能跑的最小版:一条查询、一句口径、一条对账;其他坑回头补。」 |
| 用户坚持在生产库直接跑写操作 | 守住:只给影响范围查询、回滚语句和演练步骤,执行由有权限的人按变更流程做 | 「生产库变更要由有权限的人按变更流程执行;我把影响范围查询和回滚语句先准备好。」 |
| 回复太长被截断或中途断了 | 按输出契约的固定顺序分段交付;用户说「继续」就从下一段接着写 | 「上一条停在 SQL。回复『继续』,我接着给可能的坑和对账查询。」 |

检查脚本的退出码与报错:

| 退出码 / 报错 | 原因 | 修正办法 |
|---|---|---|
| 0 | 只读;可能带 W、D、C 提醒 | 逐条确认提醒,改完再跑一次 |
| 1「不认识的方言」「字段清单缺 table_name 或 column_name 列」「SQL 是空的」「参数有误」 | 方言不在 mysql、mysql57、postgresql、sqlite 里;字段清单没有表头;没给 SQL | 其他库先不加 `--dialect`;字段清单用上面的 information_schema 查询导出并保留表头 |
| 2「……不存在」「编码认不出来」 | 路径不对,或文件不是 UTF-8/GBK | 核对路径;另存为 UTF-8 |
| 3 | 写操作、DDL、SET/VACUUM 或可执行注释触发闸门 | 按只读闸门先回风险提示;可执行注释先展开成普通 SQL 再核对 |
| 130 | 按了 Ctrl+C | 重新运行即可,脚本不改任何文件 |

## 输出契约

每条 SQL 按固定顺序交付,五项都要有:

```
方言与假设      MySQL 8;orders 一行一个订单;paid_at 为 UTC【待确认】
SQL             WITH 分步写,每步一句注释
它回答什么问题  一句业务话
可能的坑        2–3 条,针对这条 SQL,不是泛泛的清单
自检            一条对账查询:行数、合计与已知报表是否一致
```

占位符:`【待确认】` 表示按常见命名或默认值做的假设(表名、字段名、方言、时区),用户核对后删掉;`【待补】` 表示缺了就写不下去、必须用户给的值(如统计区间、某个状态的取值)。SQL 里不用方括号当占位——SQL Server 用方括号引用标识符,容易混淆。没见到表结构时,表名、字段名一律标【待确认】并说明是按常见命名假设的,不把猜测当事实。

交付前自检(逐条判断是/否,有一条「否」就改完再交):

1. 口径已用一句话复述并得到确认,或标了【待确认】?
2. 每个 JOIN 都确认了一对一还是一对多,一对多已先聚合再连接?
3. 日期区间是左闭右开,并说明了时区?
4. `python3 scripts/sql_guard.py` 结论为只读(退出码 0),W、D、C 提醒逐条处理过?
5. 附了至少一条对账查询?
6. 没见到表结构的表名、字段名都标了【待确认】?

## 示例

(示例)

**用户**:帮我写个 SQL:查上个月每个城市的下单用户数。

**助手先问**:① 库是 MySQL 8 吗?② 城市用 users 表里的城市,还是订单上的收货城市?③「下单」是创建订单就算,还是只算支付成功?时间存的是北京时间吗?

**用户**:MySQL 8;用 users.city;下单就算,不管支付;时间是北京时间。

**助手交付**:

方言与假设:MySQL 8;orders 一行一个订单,users 一行一个用户;orders.created_at 存北京时间;「上个月」按 2026 年 8 月;不排除测试账号【待确认:有没有测试账号标记字段】。

```sql
-- 回答:2026 年 8 月(北京时间)每个城市有多少个下过单的用户,一个人下多单只算一次
WITH aug_buyers AS (            -- 第 1 步:8 月下过单的用户,去重
  SELECT DISTINCT user_id
  FROM orders
  WHERE created_at >= '2026-08-01 00:00:00'
    AND created_at <  '2026-09-01 00:00:00'
)
SELECT COALESCE(u.city, '未知') AS city,   -- 第 2 步:连用户表取城市,按城市计数
       COUNT(*) AS order_users
FROM aug_buyers a
JOIN users u ON u.user_id = a.user_id
GROUP BY COALESCE(u.city, '未知')
ORDER BY order_users DESC, city;
```

它回答什么问题:8 月每个城市有多少人下过单(取消的单也算,一人多单算一次)。

可能的坑:① users.city 是用户资料里的城市,不是下单时的收货城市;② orders 里有、users 里没有的 user_id 会被 JOIN 丢掉;③ city 为空的归到「未知」,不会多出一行空白。

自检(对账):

```sql
-- 对账:8 月下过单的去重用户数,应等于上面各城市 order_users 之和;不相等说明有订单用户在 users 表里找不到
SELECT COUNT(DISTINCT user_id) AS buyers
FROM orders
WHERE created_at >= '2026-08-01 00:00:00'
  AND created_at <  '2026-09-01 00:00:00';
```

两条都用 `python3 scripts/sql_guard.py --dialect mysql` 检查过,结论为只读、没有提醒。复购率的完整交付见 [examples/repurchase-rate.md](examples/repurchase-rate.md)。

## 常见问题(FAQ)

**Q:没有表结构能写吗?**
A:能先写,按常见命名假设,表名、字段名标【待确认】;最好跑一下 information_schema 那条只读查询,把字段清单贴过来。

**Q:能帮我直接连数据库跑吗?**
A:不能。本技能只写 SQL,不连库、不执行;执行和权限申请走你们自己的流程。

**Q:MySQL 和 PostgreSQL 的写法主要差在哪?**
A:日期格式化(DATE_FORMAT 与 to_char)、类型转换(CAST 与 ::)、整数相除(PostgreSQL 会取整)、LIMIT m, n 的写法。用 `sql_guard.py --dialect` 能查出常见的差异。

**Q:结果和报表对不上,怎么查?**
A:先对账再改 SQL:比 COUNT(*) 和 COUNT(DISTINCT 主键)、比合计,再逐项核对时间字段、剔除规则和时区。多数差异出在一对多 JOIN 和切天的时区上。

**Q:能帮我写 UPDATE、DELETE 吗?**
A:能,但先过只读闸门:报出影响行数、回滚方式、事务演练和执行时机,等你明确回复「确认执行」再给语句。

**Q:查询太慢怎么办?**
A:这里只指出明显问题:函数包在字段上、缺连接条件、SELECT *。索引设计和深度调优交给 DBA。

**Q:窗口函数、WITH 在我们库里用不了?**
A:MySQL 5.7 不支持窗口函数和 WITH,8.0 起支持;改写成子查询或自连接,或者先确认数据库版本。

**Q:为什么每条 SQL 都要写「它回答什么问题」?**
A:把 SQL 翻译回业务话,用户一眼就能看出口径对不对;同一句需求,三个人能写出三个数。

## 常见错误(反模式)

| 错误做法 | 为什么错 | 正确做法 |
|---|---|---|
| 用 `col <> 'x'` 想只排除 x | col 为 NULL 的行也被排除 | 要保留 NULL 就写 `(col <> 'x' OR col IS NULL)` |
| `NOT IN(子查询)` | 子查询里只要有一个 NULL 就一行都不返回 | 改用 NOT EXISTS |
| 把 `SUM` 的结果直接当 0 用 | 全是 NULL 时得到 NULL 而不是 0 | `COALESCE(SUM(x), 0)` |
| 用 AVG 却没想到它跳过 NULL | 分母比你以为的小 | 明确分母,需要时先 COALESCE |
| 一对多 JOIN 后直接 SUM | 金额按明细行数重复累加 | 先聚合再连接 |
| 拿 DISTINCT 盖住重复行 | 在藏问题,口径更难核对 | 用 COUNT(*) 对 COUNT(DISTINCT 主键) 找出放大的那一步 |
| 该用 UNION ALL 时用了 UNION | UNION 会去重,合并掉本该保留的行 | 只合并不去重用 UNION ALL |
| 存 UTC、按本地时间切天却不换算 | 切天边界差 8 小时 | 先换算时区再分组,或把边界写成 UTC 常量 |
| 默认服务器、数据库会话、客户端时区一致 | 三处时区可能各不相同 | 查询前显式设置会话时区 |
| LIMIT 不配 ORDER BY | 每次返回的行不固定 | 加 ORDER BY,并用主键兜底 |
| MySQL 关了 ONLY_FULL_GROUP_BY,SELECT 非聚合列 | 取值不确定 | 非聚合列都进 GROUP BY 或包上聚合函数 |
| LEFT JOIN 后在 WHERE 里过滤右表 | 等于变回 INNER JOIN | 条件放进 ON 或子查询 |
| `BETWEEN '2026-09-01' AND '2026-09-30'` | 漏掉 30 日 00:00 以后的数据 | 左闭右开:`>= '2026-09-01' AND < '2026-10-01'` |
| 没见表结构就把猜的字段名写成确定的 | 猜测被当成事实,跑不通或算错 | 标【待确认】,拿到字段清单再替换 |

## 边界与不做什么

- 只写 SQL,不连数据库、不代为执行;执行和权限申请走你们自己的流程。
- 写操作必须过只读闸门;生产库变更由有权限的人按变更流程执行,本技能不建议「直接跑」。
- 不写绕过权限的查询,不拼 SQL 注入语句。
- 涉及个人信息只选需要的列,手机号、证件号在 SQL 里就脱敏或聚合,不导出明细。
- 索引设计、慢查询深度调优交给 DBA;这里只指出明显问题(函数包字段、缺连接条件)。
- 数字背后的业务判断(为什么涨跌、要不要调策略)由用户和业务方自己做。

Files in this skill

  • SKILL.md18.6 KB
  • examples/repurchase-rate.md4.8 KB
  • references/patterns.md3.8 KB
  • scripts/sql_guard.py20.1 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.