# KSQL 数据修复 / 批量数据变更

## 目录

- [强制流程](#强制流程)
  - [默认数据库语法](#默认数据库语法)
  - [SQL 风格偏好](#sql-风格偏好)
  - [0. 拆解用户自然语言意图](#0-拆解用户自然语言意图)
  - [1. 元数据字段确认（依赖 `$ok-cosmic`，必须带 `--sql`）](#1-元数据字段确认依赖-ok-cosmic必须带---sql)
  - [2. 字段全部确认后才进入 KSQL](#2-字段全部确认后才进入-ksql)
  - [3. 常见主键/关联约定](#3-常见主键关联约定)
- [推荐输出结构](#推荐输出结构)
- [最终生成模板（给 AI）](#最终生成模板给-ai)
- [生成 SQL 示例](#生成-sql-示例)
- [安全红线](#安全红线)
- [最小模板](#最小模板)

## 强制流程

### 默认数据库语法

- 默认使用 PostgreSQL 语法生成 SQL。
- 除非用户明确指定其他数据库方言，否则不要输出 Oracle / SQL Server / MySQL 等方言写法。
- 备份仍统一使用 PostgreSQL 支持的 `select * into <备份表> from <原表>;`。
- 生成前若用户给出的语法明显不是 PostgreSQL，要先说明将按 PostgreSQL 改写。

### SQL 风格偏好

- SQL 关键字统一大写，表别名保持简短且语义稳定（如主表 `h`、分录 `e`、备份表 `b`）。
- 成员关系/半连接条件默认使用 `IN`（值列表或子查询），避免使用 `EXISTS`；只有 `IN` 会改变语义或无法表达时才保留 `EXISTS`，并说明原因。
- 使用 `NOT IN` 前必须确认子查询结果不含 `NULL`；无法确认时，应在子查询内过滤 `IS NOT NULL` 或改用更安全的反关联写法，并说明原因。
- 多表更新默认使用 PostgreSQL 的 `UPDATE ... SET ... FROM ... WHERE ...`，不要使用 MySQL 风格的 `UPDATE ... JOIN ...`。
- `NULL` 判断必须使用 `IS NULL` / `IS NOT NULL`；需要 NULL 安全比较时优先使用 PostgreSQL 的 `IS DISTINCT FROM` / `IS NOT DISTINCT FROM`。
- 条件值较多时使用多行 `IN (...)`，每行一个或少量值，避免把长值列表挤在一行。
- 批量更新/删除前除样本查询外，应增加 `COUNT(1) AS affected_count` 影响行数查询，便于执行前确认。

### 0. 拆解用户自然语言意图

先把用户需求拆成：

| 项 | 必须明确的内容 |
|---|---|
| 目标对象 | 涉及哪些单据/基础资料/分录，是否已知 `formId` 或 `billName` |
| 操作类型 | 查询影响范围 / 更新 / 回填 / 删除 / 插入 / 备份 / 回滚 |
| 目标字段 | 要改哪些字段，用户给的是中文名还是英文标识都不能直接信 |
| 条件字段 | 用哪些字段限定影响范围 |
| 新值来源 | 固定值 / 来源字段 / 关联表 / 计算表达式 / 枚举值 |
| 风险边界 | 是否跨组织、跨期间、跨分录、多表关联、大批量 |

如果目标单据/表单不明确，先问用户确认；不要直接生成 KSQL。

### 1. 元数据字段确认（依赖 `$ok-cosmic`，必须带 `--sql`）

本 Skill 不自带元数据脚本；用 `$ok-cosmic` 的元数据查询能力精确确认每个单据的表名和每个数据库字段名。具体命令以 `$ok-cosmic` 的脚本路由为准，但必须满足：

- 调用 `$ok-cosmic` 的表单元数据查询能力；
- 查询参数必须带 `--sql`；
- 多单据/多对象优先一次性合并查询；
- 用户给中文字段名或英文字段标识，都必须查询确认。

必须从脚本结果中整理；数据库名只在“表所在数据库”里记录，字段明细不再逐行记录数据库名。分录/子分录表数据库名通常与单头表 `dbName` 一致，除非元数据明确返回不同库名：

表所在数据库：

| 单据/实体 | 所属实体 | 表名(dbTableName) | 数据库名(dbName) | 备注 |
|---|---|---|---|---|

字段明细：

| 业务含义 | 单据/实体 | 所属实体 | 表名(dbTableName) | 字段Key | 数据库字段(dbKey) | 字段类型 | 备注 |
|---|---|---|---|---|---|---|---|

### 2. 字段全部确认后才进入 KSQL

所有参与 KSQL 的数据库对象必须确认：

- 主表表名
- 主表数据库名 `dbName`
- 分录表表名
- 分录/子分录表数据库名（一般同单头表 `dbName`）
- 每个查询字段的 `dbKey`
- 每个更新字段的 `dbKey`
- 每个条件字段的 `dbKey`
- 枚举/状态字段真实值
- 基础资料字段应确认实际落库列和业务含义

写 SQL 前必须先做 `dbName` 一致性判断：

- 收集所有参与表的 `dbName`；
- 如果所有 `dbName` 相同，按普通单库 SQL 生成；
- 如果存在多个不同 `dbName`，就是分库/跨库场景；
- 分库/跨库场景不得直接生成普通更新 SQL，必须先让用户确认使用 `dblink`、`postgres_fdw`、导出导入临时表或其他方案；
- 在用户确认跨库方案前，只输出待确认项，不生成最终 SQL，不生成桌面 `.txt` 文件。

任何一个字段、表名或枚举值未确认时：

1. 不生成最终 KSQL；
2. 输出“待用户确认项”；
3. 等用户补充或允许人工确认后再继续。

### 3. 常见主键/关联约定

- 单据主表主键一般是 `fid`。
- 分录表主键一般是 `fentryid`。
- 分录表一般通过 `fid` 字段与单据主表关联。
- 即使符合上述约定，涉及多表更新/删除前仍要用影响范围查询验证关联结果。

## 推荐输出结构

生成数据修复 KSQL 时固定按以下结构输出：

1. **意图拆解**
2. **元数据确认结果表**
3. **待确认项**（如果为空才继续给最终 KSQL）
4. **影响范围查询**
5. **备份语句**（统一整表备份；备份表名必须以 `bak_` 开头、以当前生成时间 `yyyyMMddHHmm` 结尾，精确到分钟）
6. **更新前确认查询**（每条 `update` 前必须先写对应 `select`）
7. **正式执行语句**
8. **执行后验证语句**
9. **回滚语句**
10. **风险点与执行顺序**
11. **桌面文件产物**（所有确认卡片均为 `✔️` 后，在用户桌面生成 SQL `.txt` 文件）

## 最终生成模板（给 AI）

字段、表名、枚举值全部确认后，按下面模板输出最终结果；如果“待确认项”非空，只输出到第 3 节并停止，不生成最终 KSQL。

````markdown
## 确认卡片 1：意图拆解

- 确认状态：✔️/✖️
- 目标对象：
- 操作类型：
- 目标字段：
- 条件字段：
- 新值来源：
- 风险边界：
- 自检结论：是否足以判断需要查询哪些元数据和字段？

## 确认卡片 2：元数据确认

- 确认状态：✔️/✖️
- 已通过 `$ok-cosmic` 元数据能力确认：`kd_cosmic_metadata sql=true`
- 实际查询命令/依据：
- 确认结果：

表所在数据库：

| 单据/实体 | 所属实体 | 表名(dbTableName) | 数据库名(dbName) | 备注 |
|---|---|---|---|---|

字段明细：

| 业务含义 | 单据/实体 | 所属实体 | 表名(dbTableName) | 字段Key | 数据库字段(dbKey) | 字段类型 | 备注 |
|---|---|---|---|---|---|---|---|

- dbName 一致性检查：
  - 参与表：
  - dbName 集合：
  - 判断结果：同库 / 分库跨库
  - 跨库处理方式：不涉及 / 待用户确认 / dblink / postgres_fdw / 导出导入临时表
- 自检结论：`dbName`、表名、`dbKey`、字段类型、枚举/状态值、基础资料落库字段是否全部确认？分录/子分录表数据库是否已注明（一般同单头表 `dbName`）？若 `dbName` 不一致，是否已按分库/跨库场景停止生成或确认跨库方案？

## 确认卡片 3：待确认项

- 确认状态：✔️/✖️
- 待确认项：
  - 无
- 自检结论：若存在未确认字段、表名、枚举值或基础资料落库字段，必须在本卡片列出并停止，不输出后续 SQL。

## 确认卡片 4：影响范围查询

```sql
-- 只查询必要字段，禁止 select *
```

- 确认状态：✔️/✖️
- 自检结论：是否已限定 `where`，且查询字段足以让用户确认影响范围？

## 确认卡片 5：备份语句

备份表：`bak_<原表或业务缩写>_<当前生成时间yyyyMMddHHmm>`

```sql
-- 备份必须使用 select * into 做整表备份，不使用 create table as
-- 备份语句不加 where
-- 备份表名必须以 bak_ 开头、以当前生成时间 yyyyMMddHHmm 结尾
```

- 确认状态：✔️/✖️
- 自检结论：备份语句是否使用 `select * into` 做整表备份、是否没有 `where`、备份表名是否使用当前生成时间精确到分钟？

## 确认卡片 6：更新前确认查询

```sql
-- 每条 update 前必须先写对应查询语句
-- 查询条件和关联范围必须与 update 保持一致
```

- 确认状态：✔️/✖️
- 自检结论：是否展示主键、单据编号、待更新字段旧值和新值来源？

## 确认卡片 7：正式执行语句

```sql
-- 禁止无 where 的 update/delete
```

- 确认状态：✔️/✖️
- 自检结论：`where` 条件和关联范围是否与更新前确认查询一致？

## 确认卡片 8：执行后验证语句

```sql
-- 验证更新结果是否符合预期
```

- 确认状态：✔️/✖️
- 自检结论：是否能验证更新结果、记录数和关键字段值？

## 确认卡片 9：回滚语句

```sql
-- 基于备份表回滚
```

- 确认状态：✔️/✖️
- 自检结论：是否完全基于备份表恢复，且回滚范围可控？

## 确认卡片 10：风险点与执行顺序

- 确认状态：✔️/✖️
- 风险点：
  - 
- 执行顺序：
  1. 先执行影响范围查询，确认记录数和样本数据。
  2. 再执行整表备份语句，确认备份表记录数与原表一致。
  3. 再执行更新前确认查询，确认旧值和新值来源。
  4. 最后执行正式更新，并用验证语句复核。
  5. 如结果异常，使用回滚语句恢复。
- 自检结论：是否已说明执行顺序、风险点和回滚方式？

> 最终自检：所有确认卡片均为 ✔️ 后，才允许输出最终 KSQL；任一卡片为 ✖️ 时，停止并说明需要补充的信息。

## 确认卡片 11：桌面文件产物

- 确认状态：✔️/✖️
- 目标文件：`~/Desktop/ksql_<业务缩写>_<当前生成时间yyyyMMddHHmm>.txt`
- 文件内容：完整 SQL 执行脚本（影响范围查询、整表备份、更新前确认查询、正式执行、执行后验证、回滚）
- 自检结论：文件名时间戳是否取当前生成时间，且与备份表名时间戳一致？

> 文件生成规则：只有所有确认卡片均为 ✔️ 时，才在用户桌面生成 `.txt` 文件；任一卡片为 ✖️ 或待确认项非空时，不生成文件。
````

## 生成 SQL 示例

> 示例仅用于展示输出形态。示例里的表名、字段名、枚举值均假设已通过 `$ok-cosmic` 元数据能力（`kd_cosmic_metadata sql=true`）确认，真实生成时不得照搬。示例假设当前生成时间为 `202604301148`。

### SQL 风格约定

- 使用 `-- ============================================` 分隔章节。
- 文件开头写业务标题、关键条件、执行前提醒。
- SQL 关键字统一大写。
- 成员关系/半连接条件默认使用 `IN`（值列表或子查询），避免使用 `EXISTS`；只有 `IN` 会改变语义或无法表达时才保留 `EXISTS`，并说明原因。
- 多表更新使用 PostgreSQL `UPDATE ... FROM`；`NULL` 判断使用 `IS NULL` / `IS NOT NULL`。
- 每个更新块前必须有对应的“更新前确认查询”块。
- 文件末尾补充“字段映射（来自元数据）”。

场景：将应付单主表 `t_ap_paybill` 中指定单据的同步状态字段 `f_abc_syncstatus` 从 `FAIL` 修复为 `WAIT`。

```sql
-- ============================================
-- 应付单同步状态修复
-- 单据编号：AP202604300001、AP202604300002
-- 执行前请确认目标环境数据库
-- 备份表时间戳：202604301148
-- ============================================

-- ============================================
-- 1. 影响范围确认
-- ============================================
SELECT
    h.fid,
    h.fbillno,
    h.f_abc_syncstatus AS old_syncstatus,
    'WAIT' AS new_syncstatus
FROM t_ap_paybill h
WHERE h.fbillno IN (
    'AP202604300001',
    'AP202604300002'
)
AND h.f_abc_syncstatus = 'FAIL';

-- ============================================
-- 2. 备份
--    整表备份，不加 WHERE
-- ============================================
SELECT * INTO bak_t_ap_paybill_202604301148 FROM t_ap_paybill;

-- ============================================
-- 3. 更新前确认查询
--    条件和关联范围必须与 UPDATE 对应
-- ============================================
SELECT
    h.fid,
    h.fbillno,
    h.f_abc_syncstatus AS old_syncstatus,
    'WAIT' AS new_syncstatus
FROM t_ap_paybill h
WHERE h.fbillno IN (
    'AP202604300001',
    'AP202604300002'
)
AND h.f_abc_syncstatus = 'FAIL';

-- ============================================
-- 4. 正式更新
--    同步状态：FAIL -> WAIT
-- ============================================
UPDATE t_ap_paybill h
SET
    f_abc_syncstatus = 'WAIT'
WHERE h.fbillno IN (
    'AP202604300001',
    'AP202604300002'
)
AND h.f_abc_syncstatus = 'FAIL';

-- ============================================
-- 5. 执行后验证
-- ============================================
SELECT
    h.fid,
    h.fbillno,
    h.f_abc_syncstatus
FROM t_ap_paybill h
WHERE h.fbillno IN (
    'AP202604300001',
    'AP202604300002'
);

-- ============================================
-- 6. 回滚
--    从整表备份恢复同步状态
-- ============================================
UPDATE t_ap_paybill h
SET
    f_abc_syncstatus = b.f_abc_syncstatus
FROM bak_t_ap_paybill_202604301148 b
WHERE b.fid = h.fid
AND h.fbillno IN (
    'AP202604300001',
    'AP202604300002'
);

-- ============================================
-- 字段映射（来自元数据）
-- ============================================
-- 应付单主表: t_ap_paybill
--   fid               <- fid               (单据主键)
--   fbillno           <- billno            (单据编号)
--   f_abc_syncstatus  <- abc_syncstatus    (同步状态)
-- ============================================
```

## 安全红线

- 默认使用 PostgreSQL 语法；用户未明确指定其他数据库时，不要混用其他数据库方言。
- 禁止无 `where` 的 `update` / `delete`。
- 查询/验证语句禁止 `select *`，只查必要字段；备份语句例外，必须使用 `select * into` 做整表备份且不加 `where`。
- 禁止字段名、表名未确认就生成最终 KSQL。
- 禁止在多个参与表 `dbName` 不一致时按普通单库 SQL 直接生成更新；这属于分库/跨库场景，必须先确认跨库处理方式。
- 禁止猜枚举值、状态值、基础资料实际落库字段。
- 批量更新前必须先给影响范围查询和备份语句；备份必须使用 `select * into` 做整表备份且不加 `where`，备份表名必须以 `bak_` 开头、以当前生成时间 `yyyyMMddHHmm` 结尾（精确到分钟），例如 `bak_t_ap_paybill_202604301148`。
- 每条 `update` 语句前必须先写对应的查询语句，查询条件和关联范围应与 `update` 保持一致，并尽量展示主键、单据编号、待更新字段旧值和新值来源。
- 最终 SQL 必须写入用户桌面的 `.txt` 文件，文件名时间戳取当前生成时间，并与备份表名时间戳一致；待确认项非空时禁止生成文件。
- 修改分录表时必须说明与主表的 `fid` 关联条件。
- 涉及删除时必须优先给“逻辑作废/状态修复”替代方案；用户坚持删除时才给 `delete`。

## 最小模板

```sql
-- ============================================
-- <业务标题>
-- <关键条件说明>
-- 执行前请确认目标环境数据库
-- 备份表时间戳：<当前生成时间yyyyMMddHHmm>
-- ============================================

-- ============================================
-- 1. 影响范围确认
-- ============================================
SELECT
    h.fid,
    h.fbillno,
    h.<字段> AS old_value,
    <新值> AS new_value
FROM <主表> h
WHERE <条件>;

-- ============================================
-- 2. 备份
--    整表备份，不加 WHERE
-- ============================================
SELECT * INTO bak_<原表或业务缩写>_<当前生成时间yyyyMMddHHmm> FROM <主表>;

-- ============================================
-- 3. 更新前确认查询
--    条件和关联范围必须与 UPDATE 对应
-- ============================================
SELECT
    h.fid,
    h.fbillno,
    h.<字段> AS old_value,
    <新值> AS new_value
FROM <主表> h
WHERE <条件>;

-- ============================================
-- 4. 正式更新
-- ============================================
UPDATE <主表> h
SET
    <字段> = <新值>
WHERE <条件>;

-- ============================================
-- 5. 执行后验证
-- ============================================
SELECT
    <必要字段>
FROM <主表> h
WHERE <条件>;

-- ============================================
-- 6. 回滚
--    从整表备份恢复
-- ============================================
UPDATE <主表> h
SET
    <字段> = b.<字段>
FROM bak_<原表或业务缩写>_<当前生成时间yyyyMMddHHmm> b
WHERE b.fid = h.fid
AND <稳定回滚范围条件>;

-- ============================================
-- 字段映射（来自元数据）
-- ============================================
-- <业务表说明>: <表名>
--   <数据库字段> <- <字段Key>  (<字段中文名>)
-- ============================================
```
