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),例如:台账编号 + 成本中心 + 费用类型 + 发生日期。组合中任一字段为空时,需先填充或标记为待人工确认。

去重执行步骤:

  1. 排序与窗口函数:使用 ROW_NUMBER() OVER (PARTITION BY 关键字段组合 ORDER BY 更新时间 DESC, 记录长度 DESC) 为每组重复记录生成序号。
  2. 标记删除:保留序号为 1 的记录,其余标记为“待删除”或直接过滤。
  3. 审计日志:将删除的记录写入去重审计表,记录原始数据、去重规则、操作时间,以备追溯。
-- 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 图展示了从原始台账到标准化数据的完整清洗链路,涵盖去重、日期统一、金额标准化三个核心步骤,以及异常数据的处理分支。

成本台账数据清洗流程总览 原始成本台账 步骤1:重复数据去重 完全重复 → DISTINCT / 部分重复 → ROW_NUMBER() 步骤2:日期格式统一 识别 → 解析 → 格式化为 YYYY-MM-DD 步骤3:金额字段标准化 去除货币符号、千分符,处理中文单位 标准化成本台账 异常数据队列 人工复核 / 回退修正 说明:虚线表示异常数据流向,异常记录不进入标准化台账,需人工处理后重新进入清洗流程。

4.5 清洗规则配置化建议

为提升清洗流程的可维护性,建议将去重键、日期格式列表、货币符号映射表等参数外置为配置文件或规则表,避免硬编码在代码中。

配置项 示例值 说明
去重唯一键 ["台账编号", "成本中心", "费用类型", "发生日期"] 部分重复去重时使用的字段组合
保留规则 {"order_by": "更新时间", "direction": "DESC"} 重复组内保留哪一条记录
日期格式列表 ["%Y-%m-%d", "%Y/%m/%d", "%d-%b-%Y"] 尝试解析的日期格式顺序
货币符号映射 {"¥": "CNY", "$": "USD", "€": "EUR"} 用于金额标准化和币种识别
中文单位映射 {"万元": 10000, "元": 1} 金额乘以对应倍数
工程建议: 将上述配置存储在数据库配置表或 YAML 文件中,清洗引擎启动时加载。当业务规则变化时(如新增去重字段),只需修改配置,无需重新部署代码。