如何在MongoDB中将对象数组与ObjectId数组合并为唯一列表
MongoDB聚合:合并数组并去重(含字段不存在处理)
问题场景
现有如下文档:
// 文档1 { _id: ObjectId('232be4e2fd568670ec11d98b'), name: 'Discord', type: 'A', devices: [ ObjectId('642be505fd568670ec11d10c'), ObjectId('552be45cfd568670ec30d779') ], endpoints: [ { refId: ObjectId('641316fd596514e63a2121ca') }, { refId: ObjectId('642bd725c7e5ec334ee17ce7') }, { refId: ObjectId('552be45cfd568670ec30d779') } ] } // 文档2 { _id: ObjectId('642c073643aaf70eee741e05'), name: 'Twitter', type: 'A', devices: [ ObjectId('642c072b37e4e956dfed39a0'), ObjectId('642c0730aa06dc1f7906b9a3') ] } // 文档3 { _id: ObjectId('642c07b2fb3917c094e30734'), name: 'Facebook', type: 'A' }
需要转换为仅包含_id和去重devices数组的文档,devices需合并原devices数组与endpoints.refId数组,且需处理字段不存在的情况,最终结果如下:
// 结果1 { _id: ObjectId('232be4e2fd568670ec11d98b'), devices: [ ObjectId('642be505fd568670ec11d10c'), ObjectId('552be45cfd568670ec30d779'), ObjectId('641316fd596514e63a2121ca'), ObjectId('642bd725c7e5ec334ee17ce7') ] } // 结果2 { _id: ObjectId('642c073643aaf70eee741e05'), devices: [ ObjectId('642c072b37e4e956dfed39a0'), ObjectId('642c0730aa06dc1f7906b9a3') ] } // 结果3 { _id: ObjectId('642c07b2fb3917c094e30734'), devices: [] }
原代码问题
你之前尝试的$group写法存在两个问题:
- 当
endpoints不存在时,$addToSet: '$endpoints.refId'会产生null值,无法正确生成数组 $push: '$devices'会把整个devices数组作为单个元素存入,而非合并数组内的元素
正确聚合管道(强制使用$group)
以下是满足需求的完整聚合管道,每一步都处理了字段不存在的场景:
db.collection.aggregate([ // 步骤1:统一字段格式,处理不存在的情况 { $addFields: { // 确保devices始终是数组,不存在则设为空数组 devices: { $ifNull: ['$devices', []] }, // 提取endpoints中的refId,不存在endpoints则设为空数组 endpointRefs: { $map: { input: { $ifNull: ['$endpoints', []] }, as: 'ep', in: '$$ep.refId' } } } }, // 步骤2:合并devices和endpointRefs为一个数组 { $project: { combined: { $concatArrays: ['$devices', '$endpointRefs'] } } }, // 步骤3:拆分合并后的数组,为去重做准备 { $unwind: { path: '$combined', preserveNullAndEmptyArrays: true // 处理空数组的情况 } }, // 步骤4:按_id分组,用$addToSet去重收集元素 { $group: { _id: '$_id', devices: { $addToSet: '$combined' } } }, // 步骤5:确保devices为空数组时显示正确(可选) { $project: { _id: 1, devices: { $ifNull: ['$devices', []] } } } ])
步骤解释
- $addFields:将
devices和endpoints统一为数组格式,避免字段不存在导致的错误;同时把endpoints中的refId提取成独立数组endpointRefs - $project:用
$concatArrays合并devices和endpointRefs,得到包含所有待去重元素的数组combined - $unwind:拆分
combined数组的每个元素,开启preserveNullAndEmptyArrays确保空数组的文档不会被过滤 - $group:按
_id分组,用$addToSet自动去重并收集所有元素,生成最终的devices数组 - $project(可选):确保即使没有任何元素,
devices也显示为空数组而非缺失
内容的提问来源于stack exchange,提问作者TheStranger
相关产品推荐
相关产品推荐

