MongoDB中移除addToSet导致的数组重复对象(保留最新条目)
清理MongoDB数组中字段顺序不同的重复对象(保留最新条目)
问题背景
使用addToSet更新集合时,MongoDB会将字段顺序不同但内容完全一致的文档判定为唯一对象,导致数组中出现重复条目。需要清理这些重复项,仅保留每个重复组里的最后(最新)一条,且不能通过全删重加的方式破坏关联关系。
示例重复文档片段:
{ "_id":"doc_123456789", "relations": [ { "groupId": "59fab2dd-a3eb-4bb0-8fb5-1a2c4d0209c7", "basis": "HOME", "memberRole": "OWNER" }, { "groupId": "ccd882a9-c3e1-4ba9-bdd7-13928fd9dd56", "basis": "FAMILY", "memberRole": "PARENT" }, { "basis": "HOME", "groupId": "59fab2dd-a3eb-4bb0-8fb5-1a2c4d0209c7", "memberRole": "OWNER" }, { "groupId": "634012ca-5d91-4691-ae1b-1890613cd5e2", "basis": "HOME", "memberRole": "OWNER" } ] }
解决方案1:使用更新管道(MongoDB 4.2+)
直接在更新操作中处理数组,无需额外聚合步骤:
db.your_collection.updateMany( {}, // 可添加过滤条件,只处理特定文档 [ { $set: { relations: { $reduce: { input: { $reverseArray: "$relations" }, // 倒序遍历,优先保留最新条目 initialValue: { uniqueMap: {}, result: [] }, in: { $let: { vars: { // 将对象转为排序键后的JSON字符串,作为唯一标识 uniqueKey: { $function: { body: function(obj) { const sortedKeys = Object.keys(obj).sort(); return JSON.stringify(sortedKeys.reduce((acc, key) => { acc[key] = obj[key]; return acc; }, {})); }, args: ["$$this"], lang: "js" } } }, in: { uniqueMap: { $mergeObjects: [ "$$value.uniqueMap", { $arrayToObject: [[["$$uniqueKey", true]]] } ] }, result: { $cond: [ { $in: ["$$uniqueKey", { $objectToArray: "$$value.uniqueMap" }] }, "$$value.result", { $concatArrays: ["$$value.result", ["$$this"]] } ] } } } } } } } }, // 把结果倒序还原,保持原数组的顺序结构 { $set: { relations: { $reverseArray: "$relations.result" } } } ] )
解决方案2:使用聚合+Merge(MongoDB 4.4+)
适合需要先预览处理结果再更新的场景:
db.your_collection.aggregate([ // 展开数组并记录每个元素的原索引 { $unwind: { path: "$relations", includeArrayIndex: "originalIndex" } }, // 生成每个对象的唯一标识(排序键后的JSON) { $addFields: { uniqueKey: { $function: { body: function(obj) { const sortedKeys = Object.keys(obj).sort(); return JSON.stringify(sortedKeys.reduce((acc, key) => { acc[key] = obj[key]; return acc; }, {})); }, args: ["$relations"], lang: "js" } } } }, // 按文档ID和唯一标识分组,保留最后出现的元素(最新)和最大索引 { $group: { _id: { docId: "$_id", key: "$uniqueKey" }, latestRelation: { $last: "$relations" }, maxIndex: { $max: "$originalIndex" } } }, // 按原索引排序,保证数组顺序和原结构一致 { $sort: { "_id.docId": 1, maxIndex: 1 } }, // 重组每个文档的清理后数组 { $group: { _id: "$_id.docId", cleanedRelations: { $push: "$latestRelation" } } }, // 将结果合并回原集合 { $merge: { into: "your_collection", // 替换为你的集合名 on: "_id", whenMatched: [ { $set: { relations: "$cleanedRelations" } } ] } } ])
关键说明
- 唯一标识生成:通过将对象的键排序后转为JSON字符串,确保字段顺序不同但内容一致的对象生成相同的唯一键,解决
addToSet的判定问题。 - 保留最新条目:倒序遍历或使用
$last操作符,确保重复项中最后出现的(最新)条目被保留。 - 兼容性:如果你的MongoDB版本低于4.4,可替换
$function为$concat+固定字段顺序拼接的方式生成唯一键,例如:uniqueKey: { $concat: ["$relations.groupId", "-", "$relations.basis", "-", "$relations.memberRole"] }
内容的提问来源于stack exchange,提问作者mike
相关产品推荐
相关产品推荐

