MongoDB聚合框架填充数组文档缺失字段及关联匹配方法
MongoDB聚合框架填充字段的两种场景解决方案
针对你提出的两个MongoDB聚合填充需求,我整理了实际项目中常用的实现方案,直接上代码和解释:
场景1:填充仅含ID的文档数组
假设你的集合中有类似这样的文档,docs数组里只有每个文档的_id:
{ _id: ObjectId("xxx"), docs: [ { _id: ObjectId("60d21b4667d0d8992e610c85") }, { _id: ObjectId("60d21b4667d0d8992e610c86") } ] }
要把这些ID对应的完整字段填充进来,可以用两种方式实现:
方式一:传统unwind+lookup+group(兼容低版本MongoDB)
db.yourCollection.aggregate([ // 先把docs数组拆成单个文档 { $unwind: "$docs" }, // 关联目标集合,获取完整文档 { $lookup: { from: "targetCollection", // 替换成你的目标集合名 localField: "docs._id", foreignField: "_id", as: "fullDocument" } }, // 展开lookup返回的数组(因为lookup总是返回数组) { $unwind: "$fullDocument" }, // 重新合并成数组,同时把原ID对象和完整文档合并 { $group: { _id: "$_id", docs: { $push: { $mergeObjects: ["$docs", "$fullDocument"] } } } } ])
方式二:用lookup管道(MongoDB 5.0+推荐,更高效)
不用拆分数组,直接在lookup内部做匹配,然后用map填充:
db.yourCollection.aggregate([ { $lookup: { from: "targetCollection", let: { docIds: "$docs._id" }, pipeline: [ { $match: { $expr: { $in: ["$_id", "$$docIds"] } } } ], as: "fullDocuments" } }, { $project: { docs: { $map: { input: "$docs", as: "doc", in: { $mergeObjects: [ "$$doc", // 过滤出当前ID对应的完整文档 { $first: { $filter: { input: "$fullDocuments", cond: { $eq: ["$$this._id", "$$doc._id"] } } } } ] } } } } } ])
场景2:填充bundleArticles数组对应位置的文章对象
你说已经通过lookup拿到了全部4篇文章的数据,假设当前聚合阶段的数据结构是这样的:
{ _id: ObjectId("xxx"), bundleArticles: [ { _id: ObjectId("60d21b4667d0d8992e610c81") }, { _id: ObjectId("60d21b4667d0d8992e610c82") }, { _id: ObjectId("60d21b4667d0d8992e610c83") }, { _id: ObjectId("60d21b4667d0d8992e610c84") } ], lookedUpArticles: [ { _id: ObjectId("60d21b4667d0d8992e610c81"), title: "文章1", content: "..." }, { _id: ObjectId("60d21b4667d0d8992e610c82"), title: "文章2", content: "..." }, { _id: ObjectId("60d21b4667d0d8992e610c83"), title: "文章3", content: "..." }, { _id: ObjectId("60d21b4667d0d8992e610c84"), title: "文章4", content: "..." } ] }
核心是把lookup到的文章转成ID映射表,然后遍历原数组匹配填充:
db.yourCollection.aggregate([ { $addFields: { // 把lookedUpArticles转成以ID字符串为键的映射对象 articleMap: { $arrayToObject: { $map: { input: "$lookedUpArticles", as: "article", in: { k: { $toString: "$$article._id" }, // ObjectId转成字符串才能当对象键 v: "$$article" } } } }, // 遍历原bundleArticles数组,填充对应字段 bundleArticles: { $map: { input: "$bundleArticles", as: "item", in: { $mergeObjects: [ "$$item", // 从映射表中取出当前ID对应的文章 { $getField: { field: { $toString: "$$item._id" }, input: "$articleMap" }} ] } } } } }, // 移除临时的映射表字段 { $project: { articleMap: 0 } } ])
注意:
$getField是MongoDB 4.4+才支持的语法,如果你的版本更低,可以用$arrayElemAt配合$indexOfArray来取值,具体写法如下:{ $mergeObjects: [ "$$item", { $arrayElemAt: [ { $objectToArray: "$articleMap" }, { $indexOfArray: [ { $map: { input: "$lookedUpArticles", as: "a", in: { $toString: "$$a._id" } } }, { $toString: "$$item._id" } ]} ]}.v ] }
这样处理后,bundleArticles数组里的每个对象都会保留原位置,同时填充上对应的文章字段。
内容的提问来源于stack exchange,提问作者brechtvhb
相关产品推荐
相关产品推荐

