MongoDB聚合查询:多数组对象的$lookup关联实现问题
解决MongoDB中关联数组元素并保留对应quantity的问题
问题场景
assemblies集合文档结构如下:
assemblies: { mechanicalItems: [{ mechanicalId: ObjectId("xxx"), // 关联mechanicalitems集合 quantity: 5 }], electricalItems: [{ mechanicalId: ObjectId("yyy"), // 关联electricalitems集合 quantity: 3 }] }
需要将数组中的mechanicalId替换为对应集合的完整文档,同时保留原quantity字段,期望输出:
assemblies: { mechanicalItems: [{ item: { _id: ObjectId("xxx"), size: '12', material: 'steel', style: 'thick' }, quantity: 5 }, ...], electricalItems: [{ item: { _id: ObjectId("yyy"), current: '15', rating: 'low voltage', insulation: 'PVC' }, quantity: 3 }, ...] }
原尝试的$lookup会导致quantity变成数组,使用unwind+group又会丢失另一个数组。
解决方案
方案一:拆分-关联-聚合回原结构(兼容低版本MongoDB)
通过分步骤处理每个数组,拆分后关联再聚合,同时保留其他数组字段:
db.assemblies.aggregate([ // 处理mechanicalItems数组 { $unwind: "$mechanicalItems" }, { $lookup: { from: "mechanicalitems", localField: "mechanicalItems.mechanicalId", foreignField: "_id", as: "mechanicalItems.item" } }, { $unwind: "$mechanicalItems.item" }, { $group: { _id: "$_id", mechanicalItems: { $push: "$mechanicalItems" }, electricalItems: { $first: "$electricalItems" }, // 保留assemblies其他原始字段,按需添加 name: { $first: "$name" }, createTime: { $first: "$createTime" } } }, // 处理electricalItems数组 { $unwind: "$electricalItems" }, { $lookup: { from: "electricalitems", localField: "electricalItems.mechanicalId", foreignField: "_id", as: "electricalItems.item" } }, { $unwind: "$electricalItems.item" }, { $group: { _id: "$_id", electricalItems: { $push: "$electricalItems" }, mechanicalItems: { $first: "$mechanicalItems" }, name: { $first: "$name" }, createTime: { $first: "$createTime" } } }, // 清理冗余字段 { $project: { "mechanicalItems.mechanicalId": 0, "electricalItems.mechanicalId": 0 } } ])
步骤说明
- 拆分数组:用
$unwind将目标数组拆分为单个元素文档,便于逐个关联。 - 关联集合:通过
localField指定当前元素的mechanicalId,匹配对应集合的文档,结果存入item字段。 - 展开关联结果:
$unwind将lookup返回的单元素数组转为对象格式。 - 聚合回原结构:
$group按文档_id分组,用$push重新组装目标数组,用$first保留其他数组和原始字段。 - 清理冗余:用
$project移除不再需要的mechanicalId字段。
方案二:用子管道匹配对应quantity(MongoDB 3.6+)
利用$indexOfArray和$arrayElemAt直接匹配数组中对应元素的quantity,避免拆分聚合操作:
db.assemblies.aggregate([ // 处理mechanicalItems { $lookup: { from: "mechanicalitems", let: { originItems: "$mechanicalItems" }, pipeline: [ { $match: { $expr: { $in: ["$_id", "$$originItems.mechanicalId"] } } }, { $addFields: { quantity: { $arrayElemAt: [ "$$originItems.quantity", { $indexOfArray: ["$$originItems.mechanicalId", "$_id"] } ] } } }, { $project: { item: "$$ROOT", quantity: 1 } } ], as: "mechanicalItems" } }, // 处理electricalItems { $lookup: { from: "electricalitems", let: { originItems: "$electricalItems" }, pipeline: [ { $match: { $expr: { $in: ["$_id", "$$originItems.mechanicalId"] } } }, { $addFields: { quantity: { $arrayElemAt: [ "$$originItems.quantity", { $indexOfArray: ["$$originItems.mechanicalId", "$_id"] } ] } } }, { $project: { item: "$$ROOT", quantity: 1 } } ], as: "electricalItems" } }, // 调整最终结构 { $addFields: { mechanicalItems: { $map: { input: "$mechanicalItems", as: "mi", in: { item: { $mergeObjects: ["$$mi.item", { _id: "$$mi.item._id" }] }, quantity: "$$mi.quantity" } } }, electricalItems: { $map: { input: "$electricalItems", as: "ei", in: { item: { $mergeObjects: ["$$ei.item", { _id: "$$ei.item._id" }] }, quantity: "$$ei.quantity" } } } } } ])
步骤说明
- 批量匹配关联文档:用
$in匹配所有关联的文档ID。 - 匹配对应quantity:通过
$indexOfArray找到当前文档_id在原数组中的位置,再用$arrayElemAt取出对应位置的quantity。 - 调整结构:用
$map重新组装数组元素,得到期望的item+quantity格式。
内容的提问来源于stack exchange,提问作者Grant
相关产品推荐
相关产品推荐

