4. 重复数据与格式清洗:完全重复与部分重复记录的去重逻辑、日期格式统一、金额字段标准化
成本台账数据在采集与合并过程中,极易因系统多次导出、人工补录、接口重传等原因产生重复记录。同时,日期格式不统一(如“2024-01-15”、“2024/01/15”、“2024.01.15”、“15-Jan-2024”)和金额字段携带千分符、货币符号(如“¥1,234.56”、“$1,234.56”、“1,234.56”)会直接导致后续预算占用分摊计算失败或精度偏差。本章聚焦这三类清洗场景,给出可落地的工程化处理逻辑。
4.1 完全重复与部分重复记录的去重逻辑
重复记录分为两类:完全重复(所有字段值均相同)和部分重复(关键业务字段相同,但非关键字段存在差异)。去重策略需根据业务语义选择。
| 重复类型 | 定义 | 典型场景 | 去重策略 |
|---|---|---|---|
| 完全重复 | 所有字段值完全相同 | 同一台账文件被重复导入两次 | 保留第一条,删除其余(DISTINCT 或 GROUP BY 所有字段) |
| 部分重复 | 关键字段(台账编号、成本中心、费用类型、发生日期)相同,但备注、附件名等非关键字段不同 | 同一笔费用在系统中被不同操作员分别录入,备注略有差异 | 基于业务规则保留最新或最完整的一条(如取最大更新时间、最长备注) |
工程实践要点: 部分重复去重必须定义“关键字段组合”。建议在清洗前与业务方确认唯一性约束(Unique Key),例如:
台账编号 + 成本中心 + 费用类型 + 发生日期。组合中任一字段为空时,需先填充或标记为待人工确认。
去重执行步骤:
- 排序与窗口函数:使用
ROW_NUMBER() OVER (PARTITION BY 关键字段组合 ORDER BY 更新时间 DESC, 记录长度 DESC)为每组重复记录生成序号。 - 标记删除:保留序号为 1 的记录,其余标记为“待删除”或直接过滤。
- 审计日志:将删除的记录写入去重审计表,记录原始数据、去重规则、操作时间,以备追溯。
-- SQL 示例:部分重复去重(保留更新时间最晚的记录)
WITH dedup_cte AS (
SELECT *,
ROW_NUMBER() OVER (
PARTITION BY 台账编号, 成本中心, 费用类型, 发生日期
ORDER BY 更新时间 DESC, LENGTH(备注) DESC
) AS rn
FROM 成本台账_原始
)
SELECT * FROM dedup_cte WHERE rn = 1;
避坑提示: 不要直接使用
DISTINCT 处理部分重复,它会丢失非关键字段的差异信息。务必先明确业务唯一键,再使用窗口函数精准保留目标行。
4.2 日期格式统一
成本台账中常见的日期格式包括:YYYY-MM-DD、YYYY/MM/DD、YYYY.MM.DD、DD-MM-YYYY、MM/DD/YYYY、DD-Mon-YYYY(如 15-Jan-2024)。统一目标为 ISO 8601 标准格式:YYYY-MM-DD。
日期清洗流程:
- 格式识别:通过正则表达式或日期解析库判断输入格式。
- 解析与转换:将非标准格式解析为日期对象,再格式化为
YYYY-MM-DD。 - 异常处理:无法解析的日期(如“2024-02-30”、“2024-13-01”)标记为“日期异常”,进入人工复核队列。
# Python 示例:日期格式统一
import pandas as pd
from dateutil.parser import parse
def normalize_date(date_str):
try:
# 自动推断并解析为 datetime 对象
dt = parse(date_str, dayfirst=False) # 根据业务调整 dayfirst 参数
return dt.strftime('%Y-%m-%d')
except (ValueError, TypeError):
return None # 标记为异常
df['发生日期_标准化'] = df['发生日期'].apply(normalize_date)
注意: 中文环境中“2024年1月15日”也常见。建议在解析前先做字符替换:将“年”、“月”、“日”替换为“-”或“/”,再统一解析。同时注意月份和日期的顺序歧义(如“01/02/2024”是1月2日还是2月1日),需与业务确认默认顺序。
4.3 金额字段标准化(去除千分符、货币符号)
金额字段在原始台账中常以字符串形式出现,包含千分位分隔符(逗号、空格)、货币符号(¥、$、€)或中文单位(元、万元)。标准化目标:纯数字浮点型(Decimal 或 Float),保留两位小数精度。
清洗规则:
- 去除货币符号:移除 ¥、$、€、¥ 等符号。
- 去除千分符:移除数字中的逗号(注意欧洲格式中逗号为小数分隔符,需根据区域判断)。
- 处理中文单位:如“万元”需乘以 10000,“元”直接保留。
- 空值与零值处理:空字符串转为 0.00 或 NULL(根据业务要求)。
# Python 示例:金额字段标准化
import re
def clean_amount(amount_str):
if pd.isna(amount_str) or amount_str == '':
return 0.00
# 去除货币符号和空格
amount_str = re.sub(r'[¥$€¥\s]', '', str(amount_str))
# 处理中文单位
if '万元' in amount_str:
amount_str = amount_str.replace('万元', '')
multiplier = 10000
elif '元' in amount_str:
amount_str = amount_str.replace('元', '')
multiplier = 1
else:
multiplier = 1
# 去除千分符(逗号)
amount_str = amount_str.replace(',', '')
try:
return round(float(amount_str) * multiplier, 2)
except ValueError:
return None # 标记为异常
df['金额_标准化'] = df['金额'].apply(clean_amount)
精度保障: 金额字段在数据库层面建议使用
DECIMAL(18,2) 类型,避免浮点运算误差。在 ETL 过程中,标准化后的金额应做四舍五入保留两位小数,并记录原始值与清洗后值的对照表,便于对账。
4.4 清洗流程总览
以下 SVG 图展示了从原始台账到标准化数据的完整清洗链路,涵盖去重、日期统一、金额标准化三个核心步骤,以及异常数据的处理分支。
4.5 清洗规则配置化建议
为提升清洗流程的可维护性,建议将去重键、日期格式列表、货币符号映射表等参数外置为配置文件或规则表,避免硬编码在代码中。
| 配置项 | 示例值 | 说明 |
|---|---|---|
| 去重唯一键 | ["台账编号", "成本中心", "费用类型", "发生日期"] |
部分重复去重时使用的字段组合 |
| 保留规则 | {"order_by": "更新时间", "direction": "DESC"} |
重复组内保留哪一条记录 |
| 日期格式列表 | ["%Y-%m-%d", "%Y/%m/%d", "%d-%b-%Y"] |
尝试解析的日期格式顺序 |
| 货币符号映射 | {"¥": "CNY", "$": "USD", "€": "EUR"} |
用于金额标准化和币种识别 |
| 中文单位映射 | {"万元": 10000, "元": 1} |
金额乘以对应倍数 |
工程建议: 将上述配置存储在数据库配置表或 YAML 文件中,清洗引擎启动时加载。当业务规则变化时(如新增去重字段),只需修改配置,无需重新部署代码。