遗留数据的迁移与一致性校验
目标
把杂乱的旧 CSV 加载到 staging,完成 profiling → 清洗 → 去重 → 代码映射 → 一致性验证 → 排除项管理 → 签署确认的完整迁移周期。
为什么重要
口头说“迁移成功”不能证明任何事。必须记录数量比较和样本验证;只看数量和总和会漏掉一条缺失、另一条翻倍的情况。若不保留排除项清单,上线后便无法快速回答数据为何缺失。保留后则可立即说明具体排除原因与重迁计划。
步骤
- 源文件:
/opt/lab/fixtures/dbo/legacy/CUST_LEGACY.csv(1,200 行) 代码映射表:/opt/lab/fixtures/dbo/legacy/grade_map.csv - 创建
/root/db/etl.db,把源 CSV 不经处理加载到STG_CUST。所有列为 TEXT,必须有 1200 行。 - 创建
/root/db/profile.csv,首行为column,nulls,spaces,distinct。针对STG_CUST的每列,依次记录nulls(空值或 trim 后为NULL/null)、spaces(首尾空格)、distinct(原始字符串不同值数),并保持表列顺序。 - 创建
TGT_CUST并清洗:去首尾空格;NULL/null转真 NULL;把REG_DATE统一为 8 位YYYYMMDD(输入含YYYYMMDD、YYYY-MM-DD、YY/MM/DD,其中YY解释为20YY);去掉TOT_AMT逗号后转整数。完成后TGT_CUST中REG_DATE非 8 位的行数为 0。 - 同一
CUST_ID多行时,只保留UPD_DT最大的一行;UPD_DT相同时保留源顺序靠后的行。把删除数保存到/root/db/dedup.txt,格式为removed=<건수>。 - 使用
grade_map.csv把GRADE转换为GRADE_CD。未映射时把GRADE_CD设为99,并将该行CUST_ID与原值保存到/root/db/unmapped.csv。首行为cust_id,legacy_grade,按cust_id升序。 - 创建
/root/db/recon.csv,首行为item,source,target,diff,result。记录count;amount(源TOT_AMT去逗号转整数后与目标比较);distinct_id(源中不同CUST_ID数与目标行数比较)。result相同为OK,否则为NG。 - 把未迁移行的
CUST_ID保存到/root/db/excluded.csv。首行为cust_id,reason,reason为duplicate。 - 编写
/root/db/etl-signoff.md,包含## 이관 대상、## 제외 사유、## 검증 결과、## 재이관 대상、## 확인五个 h2 标题,并准确包含:source_rows=<원천 행 수> target_rows=<대상 행 수> excluded=<제외 건수> unmapped=<코드 미매핑 건수>
参考
- CSV 加载:
.mode csv/.import --skip 1 <파일> <테이블> - 字符串处理:
trim()、replace()、substr() - 去重:
ROW_NUMBER() OVER (PARTITION BY ... ORDER BY ...)或GROUP BY+MAX() - 常见错误:在 staging 加载时同时清洗;把
YY/MM/DD解释为19YY;只统计排除项而不保留清单。
加载源数据
创建 /root/db/etl.db,把源 CSV 不经处理加载到 STG_CUST 表。
所有列为 TEXT,行数必须为 1200。
源数据应原样进入 staging。此时清洗会导致以后无法与原始数据核对。
数据 profiling
创建 /root/db/profile.csv。首行为 column,nulls,spaces,distinct。
针对 STG_CUST 的所有列:
nulls:值为空,或去首尾空格后为字符串NULL/null的数量spaces:首尾含空格的数量distinct:不同值数量(以原始字符串为准) 按表列顺序记录。
清洗规则应来自 profiling 结果。先查看每列有多少 NULL、多少种不同值。
应用清洗规则
创建 TGT_CUST 并应用清洗规则:
- 去除所有字符串首尾空格
- 字符串
NULL/null→ 真正的 NULL - 把日期(
REG_DATE)统一成 8 位YYYYMMDD(源数据混有YYYYMMDD、YYYY-MM-DD、YY/MM/DD;YY视为20YY) - 去除金额(
TOT_AMT)中的逗号并转为整数 加载后,TGT_CUST中REG_DATE不是 8 位的行数必须为 0。
日期格式混杂。先决定如何识别和转换每种格式,再编写代码。
去重
同一 CUST_ID 有多行时,只保留 UPD_DT 最大的一行。
UPD_DT 相同时,保留源顺序靠后的行。
把删除数量保存到 /root/db/dedup.txt,格式为 removed=<건수>。
保留哪条属于业务判断,而不是技术判断。规则确定后应准确实现。
代码映射
使用 grade_map.csv 把 GRADE 转换为新代码并写入 GRADE_CD。
映射表中不存在的值将 GRADE_CD 设为 99,
并把该行 CUST_ID 和原值保存到 /root/db/unmapped.csv。
首行为 cust_id,legacy_grade,按 cust_id 升序。
必须定义遇到未映射值时如何处理,并把这些对象保留为清单。
一致性验证
创建 /root/db/recon.csv。首行为 item,source,target,diff,result。
填写三行:
count:源行数 vs 目标行数amount:源TOT_AMT总和 vs 目标总和 (源金额去逗号转整数后比较)distinct_id:源中不同CUST_ID数 vs 目标行数result一致时为OK,否则为NG。
只看数量和总和,可能让一条缺失与一条重复相互抵消。请使用多个指标。
排除项清单
把未迁移行(因去重被排除)的 CUST_ID 保存到
/root/db/excluded.csv。
首行为 cust_id,reason,reason 为 duplicate。
结果不仅是“1,200 条中迁移了 1,187 条”,还必须能回答剩余 13 条具体是什么。
迁移结果签署书
编写 /root/db/etl-signoff.md。
必须有 ## 이관 대상、## 제외 사유、## 검증 결과、## 재이관 대상、## 확인
五个 h2 标题,并原样包含以下四行:
source_rows=<원천 행 수>
target_rows=<대상 행 수>
excluded=<제외 건수>
unmapped=<코드 미매핑 건수>
这是为了上线后有人询问“数据怎么没有”时能立即回答。必须同时包含数字和原因。