去重与合并:八十六行重复、两种判据与一次误合并

客户重跑两次导出,同一批任务进来三遍。本文拆两种去重判据:外部 ID 精确命中五十二条、标题加日期推定三十四条,给合并策略四问、误合并三行的代价与可回滚的软合并设计,并复算八十六行重复的 6.9%、模糊判据 8.8% 与抽查 5.0%。

去重不是把看起来一样的行删掉,而是替每一行确认它唯一该在的位置。

一、第三次导出把同一批任务又送了一遍

唐亦然收到客户消息时,对方已经自己导了三遍。第一遍只导「最近三个月」,发现归档项目不在;第二遍改成「全部项目」,忘了清空日期范围;第三遍把两个条件都设对,才拿到完整的一份。三个文件都在,客户一并传过来,问能不能只留一份。

以下情境为教学假设,人物、引语和数字不代表经核实的真实案例。

导入器不看文件名。第一次运行写了 1,240 行里的前一批,第二次又写一遍,第三次再写一遍。等他打开任务列表,同一个「修复登录超时」出现三份,评论各挂各的,附件散在三个任务下面。

客户的第一反应是「删掉两条就行」。这句话听起来简单,做起来有四层麻烦:删哪两条、被删行上的评论怎么办、附件归谁、删错了能不能回来。

更麻烦的是「看起来一样」不等于「真的一样」。有些任务是复制出来另开一条的,标题相同、日期差一天,业务上本来并行;有些是同一个任务在另一次导出里改了标题,日期相同、标题差两个字。前一种不能合,后一种必须合,只看标题分不出来。

他去数了一遍:1,240 行里 86 行是重复,占 6.9%。这个比例不算高,但 86 行背后是 86 次「要不要合」的判断,任何一次判错,客户看到的都是「你把我的数据弄乱了」。

更现实的压力是时间。客户在等一个能用的工作区,他不能花两天逐条问「这两条是不是同一个任务」。能在半天里做完的,是把判断分成「机器能定的」和「必须人看的」两堆,前者自动走,后者批量确认。

去重的第一原则是不删。 重复行保留原样,只打上「已并入某行」的标记;合并是建立指针,不是执行删除。这样即使判错,拆开就能回到原状。

第二原则是判据分级。 能确定的分高,不能确定的分低;分高的自动走,分低的进队列等人看一眼。两种判据混在一起自动执行,误合并就是这样产生的。

第三原则是留下账。 每条合并记录保留行 ID、被合并行 ID、字段来源与执行时间。没有这条记录,后面所有「能不能撤销」的讨论都不成立。

二、八十六等于五十二加三十四

两种判据来自两个不同的字段。外部 ID 是导入时按「来源 + task_id」生成的哈希,同一个任务在三次导出里哈希相同;标题加日期是退而求其次的组合,用于那些没有外部 ID 的行,比如客户手工整理的两条。

外部 ID 命中 52 行,标题加日期命中 34 行,合计 86 行。两者不是并列关系,而是优先级关系:先跑外部 ID,命中的直接合并;剩下的再跑标题加日期,命中的进队列。

判据命中置信度处理动作
外部 ID 精确命中52高直接合并,不打扰客户
标题 + 日期推定34中进待确认队列,批量勾选后合并
合计86—合并后 1,240 行变 1,154 行

为什么外部 ID 可以自动执行?因为它是导入时自己生成的,同一来源同一 task_id 必然同值,不存在「碰巧一样」。唯一要防的是生成规则变更:如果某次导入换了哈希输入,老行的外部 ID 就对不上,重复会被漏掉,而不是误判。

为什么标题加日期只能进队列?因为两个字段都不可靠。标题会被改,日期会被时区偏移一天,客户还可能真的建了两条同名任务。34 条候选里只要有一条是「真重复但不是同一任务」,自动合并就会删掉一条有效数据。

队列要能批量确认。34 条一条条问,客户会烦;正确做法是按「标题完全一致」「标题相近」「仅日期相同」三档排好,前两档默认勾选,第三档默认不勾,让客户只处理真正需要判断的部分。

还有一条辅助判据:附件指纹。同一个文件在三次导出里文件名相同、大小相同,基本可以认定是同一份;但它只能辅助,不能单独作为合并依据,因为客户可能上传了两份同名不同内容的版本。

比较标题之前先做一次 Unicode 归一化,是这组判据里最容易被跳过的一步。全角括号、组合字符与不可见空格会让两个视觉相同的标题在字节层面不相等;归一化之后再比,34 条候选里能少一批假阴性。

判据的置信度要写进报告,不只写结果。「合并了 86 行」是一句结论;「52 行由外部 ID 确定、34 行由标题加日期推定」才是客户能复核的账。

三、合并一次要回答四个问题

判定重复只是第一步。两条行合成一条,要回答四个问题:保留哪条、字段取谁、评论怎么拼、附件归谁。四个问题各有默认规则,也各有例外,关键是每个决定都留下记录。

问题默认规则例外记录字段
保留哪条最早创建、外部 ID 稳定者优先早期行缺必填字段时保留较新一条kept_id
字段取谁非空优先,其次取最新修改状态与负责人一律取最新field_source
评论怎么拼按时间升序拼进同一线程正文完全相同的副本只留一条comment_count
附件归谁全部挂到保留行同名同大小视为同一文件attachment_ids

保留哪条:默认保留最早创建的那条,因为外部 ID 最稳定、下游引用最多。例外是早期行缺必填字段,这时保留较新的一条,但要把早期行的创建时间写进备注,别让客户以为任务凭空晚建了。

字段取谁:先取非空,再取最新修改。这两条规则会打架——一条行的负责人为空但修改时间新,另一条负责人有值但旧。默认取有值的那条,因为空值不携带信息;状态与负责人例外,取最新,因为它们反映当前事实。

评论怎么拼:按时间升序拼进一个线程,保留原作者与时间戳。正文完全相同的评论只留一条,这是唯一允许的「删」——删的是重复副本,不是内容。

附件归谁:全部挂到保留行,并在附件记录上写原属行。同名同大小的视为同一文件只留一份,其余按原名保留。附件不做内容去重,因为不同版本可能同名。

还有一个顺序问题:先合并评论与附件,再合并字段。反过来做的话,字段一旦被覆盖,后面判断「这条评论原本属于谁」就失去了依据;顺序错了,撤销时也拼不回原样。

四个问题的答案必须写进合并记录,而不是留在执行脚本里。 一条记录至少包含:保留行 ID、被合并行 ID 列表、每个字段的来源、评论条数、附件 ID 列表、执行时间与操作人。有这条记录,撤销才有依据;没有它,「合并」在技术上就是一次不可逆的覆盖。

四、八十六行的账与三行的代价

把账摊开:1,240 行进来,86 行判为重复,合并后 1,154 行。86 行里 52 行是高置信度、34 行是推定;推定的 34 行里有 3 行判错,占 8.8%。

3 行的代价不是 3 行数据。每一次错合并,意味着一批评论挂到了另一条任务上、附件指向了错误对象、状态被另一条的值覆盖。客户看到的是「我的任务乱了」,不是「你错了三行」。

撤销怎么设计:合并前对涉及的行做快照,合并时写 kept_id 与 merged_ids 映射,撤销时按映射反向拆——评论与附件按原属行放回,字段恢复成快照里的值。整个过程是一次反向批次,不是手工改数据。

撤销要演练过才算有。第一次演练时他发现,评论能拆回去,附件却因为「同名只留一份」丢了一个版本;改成保留附件 ID 列表后,第二次演练才完整。没演练过的回滚,只是一句承诺。

还有一笔成本要算进去:合并后的抽查。抽查 60 行、错 3 行,5.0% 的错误率是抽样成本,不是质量问题;它必须像清洗和执行一样排进工时,而不是等客户发现再补。

复算:

1,240 行;重复 52(外部 ID)+ 34(标题+日期)= 86 = 6.9%
合并后 1,240 − 86 = 1,154 行
模糊判据误合并 3 行 = 3 ÷ 34 = 8.8%
抽查 60 行错 3 行 = 5.0%

两个比例的分母不同,报告里要分开写:8.8% 是模糊判据内部的错误率,5.0% 是合并后抽查的整体错误率。只写一个数,客户无法判断风险落在哪一段流程。

6.9% 的重复率也不该被当成小事。1,240 行里 86 行重复,看起来只占一小截;但如果这 86 行都带着评论和附件,合并的工作量远大于行数比例。去重的成本从来不按行数线性增长。

最后是口径:1,154 行是合并后的行数,不是「删掉了 86 行」。客户的工作区里仍然是 1,154 条任务,只是其中 86 条带着「已并入」标记。这个区别决定了客户会不会信任这套流程。

五、行动清单与复算验收

这八条按「先分级、再合并、最后演练撤销」的顺序做,一轮能在半天内跑完。

  • 先统计候选:按外部 ID 与标题加日期各跑一遍,得到 52 与 34 两组。
  • 把模糊命中的 34 条放进待确认队列,不自动合并。
  • 为每条合并写 kept_id 与 merged_ids,保留可回溯映射。
  • 字段冲突按「非空优先、其次最新修改」定序,状态与负责人取最新。
  • 评论按时间升序拼接,附件全部挂到保留行并记录原属行。
  • 合并后抽查 60 行,记录错误数与错误类型。
  • 演练一次撤销:从 kept_id 反向拆回 merged_ids,确认评论与附件都能回去。
  • 把误合并率与模糊命中率分开写进导入报告,不要只报一个总数。

清单的价值不在八条本身,而在它们能压成一句判断:每一条被合并的行,都能说清凭什么判重、保留了哪条、拆回去会回到哪里。

验收标准:任取一条合并记录,都能指出它凭哪条判据被判为重复、保留的是哪一行、执行撤销后评论与附件分别回到哪一行;说不清的那条不许合并。

使用边界与实操验收

边界一:去重不做硬删除。 所有重复行以软合并处理,原行保留标记与映射;真正的删除只发生在客户明确提出时,并按隐私组的删除流程执行。本文的方法不能替代删除权实现。

边界二:模糊判据不自动执行。 34 条候选全部进队列,宁可让客户多点几下,也不让机器替客户决定两条任务是不是同一条。自动化的边界在这里,不在技术能力。

边界三:不覆盖跨表与跨系统去重。 本文只讲同一 schema 下 1,240 行的行级合并,不涉及评论表、附件表的独立去重,也不涉及 CDC 或双向同步的冲突消解。

边界四:与架构组划界。 schema 变更、字段类型迁移归架构组;本文假设表结构已经确定,只处理同一结构内的重复行。

边界五:可回滚的前提是快照与映射都在。 没有快照、没有 merged_ids 记录,所谓「可撤销」只是一句设计意图,不能写进对客户的承诺。

关键参考与核验范围

以上链接于 2026-10-26 实测可公开访问;未核验版本、价格与配额。核验日期:2026-10-26。

本文讨论的是同一 schema 内、单表 1,240 行的行级去重与合并,不涉及跨系统同步、CDC 与双向写冲突消解;两种判据的阈值与队列规模按本组基线设定,换产品需重新取样。数据库行为以各项目官方文档为准,本文不构成对任何具体版本的实现承诺。

系列导航

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「Indie」更多文章

  1. 开源项目验收:十项能力、四维评分与三种不该开源的项目
  2. 治理、fork 与退出:17 位贡献者、82% 的提交与一次改协议的代价
  3. 双许可与企业版:九家询价、两家成交与 4,800 美元