# ODS 层删除 settlelist 冗余列 — 变更记录

> 结构性变更：删除 2 张表的 1 个 jsonb 列

## 溯源

- 日期：2026-02-14（Asia/Shanghai）
- Prompt：P20260214-023000 — 删除 ODS 层 settlement_records / recharge_settlements 的 settlelist jsonb 列
- Direct cause：`settlelist` 列与 `payload` 列数据完全重复（`payload` 存储完整 API 响应 JSON，已包含 `settleList` 对象），属于冗余存储。DWD 加载逻辑已改为从 `payload` 提取 `settleList`，该列不再被任何下游消费。

## 变更内容

| Schema | 表名 | 操作 | 列名 | 原类型 | 说明 |
|--------|------|------|------|--------|------|
| `ods` | `settlement_records` | DROP COLUMN | `settlelist` | `jsonb` | 存储原始 settleList 对象，与 payload 重复 |
| `ods` | `recharge_settlements` | DROP COLUMN | `settlelist` | `jsonb` | 存储原始 settleList 对象，与 payload 重复 |

### Before / After

**settlement_records**：
- Before：67 个业务列 + 5 个 meta 列（含 `settlelist jsonb`）
- After：66 个业务列 + 5 个 meta 列（`settlelist` 已移除）

**recharge_settlements**：
- Before：67 个业务列 + 5 个 meta 列（含 `settlelist jsonb`）
- After：66 个业务列 + 5 个 meta 列（`settlelist` 已移除）

## 变更原因

1. `payload` 列（jsonb）已存储完整的 API 响应 JSON，其中包含 `settleList` 对象
2. `settlelist` 列是 ETL 入库时从 `payload` 中额外提取并单独存储的副本，属于冗余
3. API vs ODS 比对报告（v3-fixed）中已标注 `settlelist` 为"ODS jsonb 列（存储原始 settleList 对象）"
4. DWD 加载逻辑已修改为直接从 `payload->'settleList'` 提取数据

## 影响范围

| 影响对象 | 影响程度 | 说明 |
|----------|----------|------|
| DWD 加载任务 | ⚠️ 需确认 | DWD 加载必须已改为从 `payload` 提取 settleList，不再读 `settlelist` 列 |
| ODS 抓取任务 | ⚠️ 需确认 | ODS 入库逻辑需移除对 `settlelist` 列的写入（INSERT/UPSERT 语句） |
| API 契约 | 无影响 | API 响应结构不变，仅 ODS 存储方式调整 |
| 小程序 | 无影响 | 小程序不直接读 ODS 层 |
| 比对报告 | 需更新 | `docs/reports/api_ods_comparison_v3.md` 和 `scripts/ods_columns.json` 中 settlement_records / recharge_settlements 的列清单需移除 `settlelist` |
| 比对脚本 | 需更新 | `scripts/run_compare_v3_fixed.py` 中 `classify_ods_only()` 对 `settlelist` 的特殊处理可移除 |

## 回滚策略

```sql
-- 1) 恢复列结构
ALTER TABLE ods.settlement_records ADD COLUMN settlelist jsonb;
ALTER TABLE ods.recharge_settlements ADD COLUMN settlelist jsonb;

-- 2) 从 payload 回填数据（回滚后列为 NULL，需重新提取）
UPDATE ods.settlement_records
SET settlelist = payload->'settleList'
WHERE payload IS NOT NULL;

UPDATE ods.recharge_settlements
SET settlelist = payload->'settleList'
WHERE payload IS NOT NULL;
```

**注意事项**：
- 回滚后 `settlelist` 列数据为 NULL，必须执行回填 UPDATE
- 若历史数据中存在 `payload IS NULL` 的行，这些行的 `settlelist` 将永久丢失（无法恢复）
- 回填前建议先统计 `payload IS NULL` 的行数，评估数据损失范围

## 验证 SQL

```sql
-- 1) 确认 settlelist 列已不存在
SELECT column_name
FROM information_schema.columns
WHERE table_schema = 'ods'
  AND table_name IN ('settlement_records', 'recharge_settlements')
  AND column_name = 'settlelist';
-- 预期：0 行

-- 2) 确认 payload 列仍存在且包含 settleList 数据
SELECT count(*)
FROM ods.settlement_records
WHERE payload IS NOT NULL AND payload->'settleList' IS NOT NULL;
-- 预期：> 0（确认 payload 中有 settleList 数据可用）

-- 3) 确认两表当前列数（排除 meta 列后应为 66）
SELECT table_name, count(*) AS total_columns
FROM information_schema.columns
WHERE table_schema = 'ods'
  AND table_name IN ('settlement_records', 'recharge_settlements')
GROUP BY table_name;
-- 预期：settlement_records = 71（66 业务 + 5 meta），recharge_settlements = 71

-- 4) 确认 DWD 加载可从 payload 正常提取 settleList
SELECT id, (payload->'settleList') IS NOT NULL AS has_settle_list
FROM ods.settlement_records
LIMIT 5;
-- 预期：has_settle_list = true（至少对 payload 非空的行）
```

## 迁移文件

`database/migrations/20260214_drop_ods_settlelist.sql`
