pandas merge 后行数变多:检查重复连接键与关系约束
pandas merge 后行数增多,按实际连接键检查多重匹配,用 validate 声明唯一性关系,结合 indicator 和缺失键规则核对结果,避免随意删除记录。
把订单表与客户表做 left 合并,结果却比订单表多出许多行,通常应先检查连接键是否重复。left 保留左侧的键,并不保证每条左侧记录只出现一次:同一个键在右侧匹配到多条记录时,左侧行会为每条匹配分别生成结果。
本文针对 pandas 按列做等值连接,示例可用于 pandas 2.x 与 3.0。行数变化本身不一定是错误;订单连接多条明细时,展开记录可能正是预期结果。
同一个键会产生多少条匹配
下面用虚构数据说明客户键重复的情况:
import pandas as pd
orders = pd.DataFrame({
"order_id": [101, 102, 103],
"customer_id": ["C1", "C1", "C2"],
})
customers = pd.DataFrame({
"customer_id": ["C1", "C1", "C2"],
"tier": ["basic", "premium", "basic"],
})
result = orders.merge(customers, on="customer_id", how="left")
C1 在左表有两行,在右表也有两行,匹配部分便产生 2 × 2 = 4 行;加上 C2 的一行,预期结果共五行。这个组合与双方是否存在完全相同的整行无关,应检查实际连接键。
显式指定 on 也很重要。按列合并时省略 on,pandas 默认使用两表共有的列名作为键;后来增加了一个同名字段,连接条件就可能改变。业务要求按客户 ID 连接时,应在代码里保留这个约定。
在合并前查看重复键,把预期关系写入 validate
对于前面的数据,可先查看客户键的全部重复记录:
keys = ["customer_id"]
duplicates = customers.loc[
customers.duplicated(subset=keys, keep=False)
]
生产排查可只统计重复键数量,再在受控范围查看必要字段,避免输出整份客户信息。组合键应把全部列放进 keys,不能只检查其中一列。
如果业务规定一名客户只能对应一条客户资料,订单可以有多条,则使用 many_to_one:
result = orders.merge(
customers,
on=keys,
how="left",
validate="many_to_one",
indicator=True,
)
当前示例右表客户键不唯一,这段合并应抛出 MergeError,让数据问题在结果展开前暴露。应先处理冲突的客户资料,再生成结果;不要捕获这个错误后继续把未完成的合并当成成功。
one_to_one 要求两侧键都唯一;one_to_many 要求左侧唯一;many_to_one 要求右侧唯一。many_to_many 允许双方重复,但不进行唯一性检查,不能用它修复意外增加的行数。
indicator 检查来源,不能代替唯一性约束
indicator=True 增加 _merge 列。左连接中的 left_only 表示左侧键没有匹配项,both 表示找到了匹配。它能帮助发现未关联的订单,但不会告诉你这条订单是否匹配了两条客户资料。
另外,双方连接键都缺失时,pandas 会把这些缺失键互相匹配,与通常的 SQL NULL 连接行为不同。检查重复键时也要核对缺失值:它们可能参与展开,不能一律解释为“没有客户匹配”。若业务禁止缺失客户 ID,先分离并报告这些记录;若允许缺失,则为它们制定明确的匹配规则,不能随手填成同一个占位 ID。
先确定重复记录的含义,再改数据
右表的两条客户资料可能是导入重复,也可能是不同生效日期的历史记录。前者需要确认哪些字段一致、哪条可保留;后者需要增加版本或日期条件,或先按业务规则选出目标资料。
不要直接对合并结果调用 drop_duplicates() 来把数量压回去。它可能删除合法订单,或者保留下错误的匹配。按键保留第一条也隐含了选择规则:若排序和业务优先级未定义,结果可能只是文件当前的偶然顺序。
如果业务实际要求同一键的第一个记录配第一个、第二个配第二个,仅靠键值连接无法表达这个关系,还需稳定的业务序号或其他识别字段。两张表恰好具有相同的行索引,不足以证明记录对应。
修改前保留原输入与转换代码。对于右侧唯一的左连接,结果行数应等于左表行数,再核对关键订单是否恰好匹配一次、未匹配和缺失键是否按约定处理。需要回退时重新从保留的输入生成结果;回退代码不会撤销已经导入下游的数据。


