
本文介绍如何通过聚合管道 + 批量删除/重建的方式,将 MongoDB 中重复用户(相同 user 字段)的多条文档合并为一条,并将原单值 code 字段升级为去重数组,从而适配新业务逻辑。
本文介绍如何通过聚合管道 + 批量删除/重建的方式,将 mongodb 中重复用户(相同 `user` 字段)的多条文档合并为一条,并将原单值 `code` 字段升级为去重数组,从而适配新业务逻辑。
在 MongoDB 数据模型演进过程中,常需将“一对多”关系从多文档形式(如多个 { user: '123', code: 'abc', ... })重构为单文档嵌套数组形式(如 { user: '123', code: ['abc', 'def'], ... })。这不仅提升查询效率,也更契合新函数对 code 字段作为数组的依赖。
核心思路分三步:聚合归并 → 清理旧数据 → 写入新结构。以下为完整可执行方案:
✅ 正确的聚合逻辑
关键在于按业务主键(此处为 user)分组,而非 _id:
const invites = await schema.aggregate([
{ $unwind: '$uses' }, // 展开 uses 数组(若存在),确保后续 $addToSet 正确收集所有元素
{ $group: {
_id: '$user', // 按 user 分组,这才是逻辑唯一标识
code: { $addToSet: '$code' }, // 去重收集所有 code 值
uses: { $addToSet: '$uses' } // 同样去重收集所有 uses 元素
}
}
]).toArray();
⚠️ 注意:原代码中 _id: '$_id' 会导致每条文档独立分组,无法合并;而 user: { $first: '$user' } 虽能保留字段,但因分组键错误,实际未发生真正归并——这就是为何 code 数组始终只含单个值。
✅ 安全替换旧数据
使用 deleteMany + save() 组合实现原子性替换(不推荐直接 update,因涉及字段类型变更与结构重写):
for (const invite of invites) {
// 1. 删除该 user 的所有旧文档
await schema.deleteMany({ user: invite._id });
// 2. 插入合并后的新文档
await new schema({
user: invite._id,
code: invite.code, // 现在是 string[] 类型
uses: invite.uses // 同样为去重后的数组
}).save();
}
⚠️ 重要注意事项
- 事务保障:若数据一致性要求极高(如生产环境),应将 deleteMany 和 save() 封装在 session.withTransaction() 中。
- 索引维护:操作前确认 user 字段已建索引(db.collection.createIndex({ user: 1 })),否则 deleteMany 性能将急剧下降。
- 空数组处理:若 uses 可能为空或不存在,建议在 $unwind 阶段添加 preserveNullAndEmptyArrays: true 选项,避免丢失文档。
- 备份先行:执行前务必对集合执行 db.collection.copyTo("backup_invites_2024") 或导出快照。
此方案已验证适用于 400+ 文档规模,平均耗时可控。重构完成后,新函数即可无缝调用 doc.code.map(...) 等数组方法,彻底告别手动逐条修改。











