Mongoose.aggregate(pipeline) 使用 $unwind, $lookup, $group 链接多个集合 [英] Mongoose.aggregate(pipeline) link multiple collections using $unwind, $lookup, $group

查看:25
本文介绍了Mongoose.aggregate(pipeline) 使用 $unwind, $lookup, $group 链接多个集合的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我是 mongodbmongoose 聚合功能的新手,在通过我的管道传递数据后一直难以获得所需的结果.

下面我使用虚构的示例模型进行了简化

场景

我有 3 个模型(ShipYatchSailboat),它们共享接口并从基类扩展.第 4 个模型,Captain,它有一个数组 watercraftContexts,其中包含用于引用与每个 Captain<关联的 watercrafts 类型的对象/代码>.

Mongo 数据/架构设置示例

//模型名称:'Captain',集合名称:'captains'{name: 'Jack Sparrow',//船长姓名license: 'SYS-123',//许可证号classes: ['sail', 'yatch', 'ship'],//允许操作的船只类型数组船只上下文:[{_id: ObjectId('xxxxxxxxxxxxxxx'),//国外模型IDtype: 'Sailboat',//型号名称参考:帆船".//集合名称},{_id: ObjectId('xxxxxxxxxxxxxxx'),//国外模型IDtype: 'Yatch',//型号名称参考:游艇".//集合名称},{_id: ObjectId('xxxxxxxxxxxxxxx'),//国外模型IDtype: 'Ship',//型号名称参考:船舶".//集合名称}]}

如您所见,对象数组已设置为使用带有 ref_idmongoose.populate() 方法字段,我已经实现了一个 virtual getter watercrafts 以使用 populate() 功能(代码未发布)来补充.

当使用 mongoose.Model 查询时,一个新字段被创建为 watercrafts,其中包含来自 3 个不同关联集合的所有对象的数组.

问题

我还需要一种方法来对这些数据进行聚合以产生类似的结果,因为 aggregate pipline 中没有 Model 方法.

这是从我的程序化 mongo 聚合生成的查询:

[ { '$match':{ _ID:{'$in':[ ObjectId('5f77bc653887221a703415e1'),ObjectId('5f77bc653887221a703415df'),ObjectId('5f77bc653887221a703415e0'),ObjectId('5f77bc653887221a703415e5') ] } } },{ '$unwind': '$watercraftContexts' },{'$查找':{ 来自:'船',localField: 'watercraftContexts._id',外国字段:'_id',如:'watercrafts.ships' } },{ '$unwind': '$watercraftContexts' },{'$查找':{来自:'游艇',localField: 'watercraftContexts._id',外国字段:'_id',如:'watercrafts.yatches' } },{ '$unwind': '$watercraftContexts' },{'$查找':{来自:'帆船',localField: 'watercraftContexts._id',外国字段:'_id',如:'watercrafts.sailboats' } },{'$组':{_id: '$_id',船只:{ '$addToSet':{'$concatArrays':['$watercrafts.ships','$watercrafts.yatches','$watercrafts.sailboats'] } }

我正在构建一个像这样的猫鼬聚合:

const Captain = mongoose.model('Captain')const 聚合 = Captain.aggregrate()//在另一个函数中动态创建聚合管道const 船长 = 等待船长.find({})const captIds = capts.map(capt => capt._id)//匹配文档子集(实际项目中)聚合.匹配({_id:{$in:captainIds}})//应用 $lookup 聚合的集合名称const collectionNames = ['sailboats', 'yatches', 'ships']//展开和查找每个多态子类的集合collectionNames.forEach(collection => {//将 watercraftContexts 分成单独的记录以供查找aggregate.unwind('watercraftContexts')//记录内联收集数据聚合查找({出自:收藏,localField: '$watercrafContexts._id',外国字段:'_id',//对象以集合名称和集合记录数组为键//避免覆盖先前的集合聚合查找如:`watercrafts.${collection}`})})//根据船长对象 ID 重新分组记录const aggregateAssociationPaths = collectionNames.map(collection =>//每个集合的 Mongo 路径 $lookup`$watercrafts.${collection}`)//根据船长的 ObjectId 重新组合 $unwind 和 $group聚合.组({_id: '$_id',$addToSet: {//$concatArrays:aggregateAssociationPaths}})/*** !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!**** ** 接下来我该怎么做才能通过聚合的船只"获取所有船长数据* **** !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!***///执行聚合const captiansWithWatercraftsAssociations = 等待聚合

到目前为止,我的数据看起来像这样,该小组没有使用猫鼬:

[ { _id: 5f77bc653887221a703415df,船只:[ { _id: 5f77bc653887221a703415d3,类:'帆',name: '飘',__v: 0 } ] },{_id:5f77bc653887221a703415e0,船只:[ { _id: 5f77bc653887221a703415d4,班级:'游艇',name: '液体黄金',__v: 0 } ] },{_id:5f77bc653887221a703415e1,船只:[ { _id: 5f77bc653887221a703415d5,类:船",name: '珍妮',__v: 0 } ] },{_id:5f77bc653887221a703415e5,船只:[ { _id: 5f77bc653887221a703415dd,班级:'游艇',name: '奥黛丽',__v: 0 } ] },{_id:5f77bc653887221a703415e5,船只:[ { _id: 5f77bc653887221a703415dc,类:'帆',name: '膨胀粉碎机',__v: 0 } ] },{_id:5f77bc653887221a703415e5,船只:[ { _id: 5f77bc653887221a703415de,类:船",name: '珍妮四世',__v: 0 } ] } ]

感谢支持

解决方案

对于刚接触 MongoDbaggregate 的人来说,这是一个棘手的问题.我将把我的答案分解成几个步骤,向其他尝试通过引用多个集合来聚合数组的尝试进行演示.

第 1 步 - $match 过滤集合

$match 接受与 db.collection.find({}) 相同的查询,并在下面的案例中返回匹配结果的数组,我在这里选择 4 个特定记录

<预><代码>{ '$匹配':{ _ID:{'$in':[ObjectId('5f7bdb3eea134b5a5c976285'),ObjectId('5f7bdb3eea134b5a5c976283'),ObjectId('5f7bdb3eea134b5a5c976284'),ObjectId('5f7bdb3eea134b5a5c976289')]}}}

$match 结果<预><代码>[{ _id: ObjectId('5f7be0b37e2bdf5b19e4724d'),name: 'CAPTAIN_SAIL',课程:['帆'],许可证:'WC-1',船只上下文:[ { _id: ObjectId('5f7be0b37e2bdf5b19e47241'),watercraftType: '帆船',参考:'帆船'}],__v: 0 },{ _id: ObjectId('5f7be0b37e2bdf5b19e4724e'),name: 'CAPTAIN_YATCH',课程:['游艇'],许可证:'WC-2',船只上下文:[ { _id: ObjectId('5f7be0b37e2bdf5b19e47242'),watercraftType: '游艇',参考:'游艇'}],__v: 0 },{ _id: ObjectId('5f7be0b37e2bdf5b19e4724f'),name: 'CAPTAIN_SHIP',类:['船'],许可证:'WC-3',船只上下文:[ { _id: ObjectId('5f7be0b37e2bdf5b19e47243'),watercraftType: '船',参考:'船'}],__v: 0 },{ _id: ObjectId('5f7be0b37e2bdf5b19e47253'),name: 'CAPTAIN_SAIL_YATCH_SHIP',课程:['sail', 'yatch', 'ship'],许可证:'WC-7',船只上下文:[ { _id: ObjectId('5f7be0b37e2bdf5b19e4724a'),watercraftType: '帆船',参考:帆船"},{ _id: ObjectId('5f7be0b37e2bdf5b19e4724b'),watercraftType: '游艇',参考:'游艇'},{ _id: ObjectId('5f7be0b37e2bdf5b19e4724c'),watercraftType: '船',参考:'船'}],__v: 0 }]

第 2 步 - $unwind 这样我们就可以用 $loopup 进行迭代

在这个结果集中有一个对象数组,带有 { _id: <ObjectId>, watercraftType: <ModelName>} 要遍历数组并将这些对象中的每一个与各自的集合记录连接起来,我们必须将数组分解为单独的独立记录.$unwind 特性将为下一个聚合阶段创建一个新的数据集

 { '$unwind': '$watercraftContexts' },

$unwind 结果

如您所见,$unwind 现在使用单个 watercraftContext 创建记录,我们现在设置为使用 $lookup

[ { _id: ObjectId('5f7be2231da37c5b5915bf9b'),name: 'CAPTAIN_SAIL',课程:['帆'],许可证:'WC-1',船只上下文:{ _id: ObjectId('5f7be2231da37c5b5915bf8f'),watercraftType: '帆船',参考:帆船"},__v: 0 },{ _id: ObjectId('5f7be2231da37c5b5915bf9c'),name: 'CAPTAIN_YATCH',课程:['游艇'],许可证:'WC-2',船只上下文:{ _id: ObjectId('5f7be2231da37c5b5915bf90'),watercraftType: '游艇',参考:'游艇'},__v: 0 },{ _id: ObjectId('5f7be2231da37c5b5915bf9d'),name: 'CAPTAIN_SHIP',类:['船'],许可证:'WC-3',船只上下文:{ _id: ObjectId('5f7be2231da37c5b5915bf91'),watercraftType: '船',参考:船"},__v: 0 },{ _id: ObjectId('5f7be2231da37c5b5915bfa1'),name: 'CAPTAIN_SAIL_YATCH_SHIP',课程:['sail', 'yatch', 'ship'],许可证:'WC-7',船只上下文:{ _id: ObjectId('5f7be2231da37c5b5915bf98'),watercraftType: '帆船',参考:'帆船' },__v: 0 },{ _id: ObjectId('5f7be2231da37c5b5915bfa1'),name: 'CAPTAIN_SAIL_YATCH_SHIP',课程:['sail', 'yatch', 'ship'],许可证:'WC-7',船只上下文:{ _id: ObjectId('5f7be2231da37c5b5915bf99'),watercraftType: '游艇',参考:'游艇'},__v: 0 },{ _id: ObjectId('5f7be2231da37c5b5915bfa1'),name: 'CAPTAIN_SAIL_YATCH_SHIP',课程:['sail', 'yatch', 'ship'],许可证:'WC-7',船只上下文:{ _id: ObjectId('5f7be2231da37c5b5915bf9a'),watercraftType: '船',参考:船"},__v: 0 } ]

第 4 步 $lookup - 连接外部集合中的每条记录

需要注意的是,我们必须在为每个需要加入的不同集合调用 $lookup 之前$unwind.由于我们要加入多个集合,我们需要将结果存储在一个以集合为键的对象中,以便以后聚合.

//只对 'ships' 集合执行 $lookup{'$查找':{ from: 'ships',//集合名称 - 注意:对每个集合重复localField: 'watercraftContexts._id',//要链接的id的字段ForeignField: '_id',//要匹配的外部集合上的字段as: 'watercrafts.ships'//存储查找结果的路径}}

步骤 5 - 对其他连接重复 $unwind 和 $lookup

对附加连接重复上述步骤,并按集合名称键入.我结合了聚合阶段来演示重复.

 { '$unwind': '$watercraftContexts' },{'$查找':{来自:'游艇',localField: 'watercraftContexts._id',外国字段:'_id',如:'watercrafts.yatches' } },{ '$unwind': '$watercraftContexts' },{'$查找':{来自:'帆船',localField: 'watercraftContexts._id',外国字段:'_id',如:'watercrafts.sailboats' } }

步骤 4 &5 结果

如果您仔细查看,您会注意到 Captain 记录之一以不同的 watercraftType 存在 3 次.$lookup 只会返回匹配特定集合名称的记录.这就是为什么将它们存储在由 collectionName

键控的 Object 中的原因<预><代码>[{ _id: ObjectId('5f7be7145320a65b942bb450'),name: 'CAPTAIN_SAIL',课程:['帆'],许可证:'WC-1',船只上下文:{ _id: ObjectId('5f7be7145320a65b942bb444'),watercraftType: '帆船',参考:帆船"},__v: 0,船只:{船舶:[],游艇:[],帆船:[ { _id: ObjectId('5f7be7145320a65b942bb444'),类:'帆',name: '飘',__v: 0 } ] } },{ _id: ObjectId('5f7be7145320a65b942bb451'),name: 'CAPTAIN_YATCH',课程:['游艇'],许可证:'WC-2',船只上下文:{ _id: ObjectId('5f7be7145320a65b942bb445'),watercraftType: '游艇',参考:'游艇'},__v: 0,船只:{船舶:[],游艇:[ { _id: ObjectId('5f7be7145320a65b942bb445'),班级:'游艇',name: '液体黄金',__v: 0 } ],帆船:[] } },{ _id: ObjectId('5f7be7145320a65b942bb452'),name: 'CAPTAIN_SHIP',类:['船'],许可证:'WC-3',船只上下文:{ _id: ObjectId('5f7be7145320a65b942bb446'),watercraftType: '船',参考:船"},__v: 0,船只:{ 船舶:[ { _id: ObjectId('5f7be7145320a65b942bb446'),类:船",name: '珍妮',__v: 0 } ],游艇:[],帆船:[] } },{ _id: ObjectId('5f7be7145320a65b942bb456'),name: 'CAPTAIN_SAIL_YATCH_SHIP',课程:['sail', 'yatch', 'ship'],许可证:'WC-7',船只上下文:{ _id: ObjectId('5f7be7145320a65b942bb44d'),watercraftType: '帆船',参考:帆船"},__v: 0,船只:{船舶:[],游艇:[],帆船:[ { _id: ObjectId('5f7be7145320a65b942bb44d'),类:'帆',name: '膨胀粉碎机',__v: 0 } ] } },{ _id: ObjectId('5f7be7145320a65b942bb456'),name: 'CAPTAIN_SAIL_YATCH_SHIP',课程:['sail', 'yatch', 'ship'],许可证:'WC-7',船只上下文:{ _id: ObjectId('5f7be7145320a65b942bb44e'),watercraftType: '游艇',参考:'游艇'},__v: 0,船只:{船舶:[],游艇:[ { _id: ObjectId('5f7be7145320a65b942bb44e'),班级:'游艇',name: '奥黛丽',__v: 0 } ],帆船:[] } },{ _id: ObjectId('5f7be7145320a65b942bb456'),name: 'CAPTAIN_SAIL_YATCH_SHIP',课程:['sail', 'yatch', 'ship'],许可证:'WC-7',船只上下文:{ _id: ObjectId('5f7be7145320a65b942bb44f'),watercraftType: '船',参考:船"},__v: 0,船只:{ 船舶:[ { _id: ObjectId('5f7be7145320a65b942bb44f'),类:船",name: '珍妮四世',__v: 0 } ],游艇:[],帆船: [] } } ]

第 6 步 $project - 使用 project 来展平连接的 Object Map

我们可以使用project来选择所有现有的数据,并将join结果的Object Map展平为一个Array.

 { '$project'://将包含值为 'true' 的键{名称:真实,许可证:真实,类:真的,_id:真实,watercraftContexts: 真,__v:对,watercrafts://重新分配 watercrafts 的值{ '$setUnion'://接受要展平的数组数组['$watercrafts.ships','$watercrafts.yatches','$watercrafts.sailboats']}}}

$project 结果

上述 $project 的结果会用一个扁平化的 watercrafts 数组替换 watercrafts 对象,但需要注意的是仍然存在 Captain 的重复记录,其中匹配了许多不同的查找.我们将在下一步将它们重新组合在一起.

[ { _id: ObjectId('5f7bea8d79dfe25bf3cb9695'),name: 'CAPTAIN_SAIL',课程:['帆'],许可证:'WC-1',船只上下文:{ _id: ObjectId('5f7bea8d79dfe25bf3cb9689'),watercraftType: '帆船',参考:帆船"},__v: 0,船只:[ { _id: ObjectId('5f7bea8d79dfe25bf3cb9689'),类:'帆',name: '飘',__v: 0 } ] },{ _id: ObjectId('5f7bea8d79dfe25bf3cb9696'),name: 'CAPTAIN_YATCH',课程:['游艇'],许可证:'WC-2',船只上下文:{ _id: ObjectId('5f7bea8d79dfe25bf3cb968a'),watercraftType: '游艇',参考:'游艇'},__v: 0,船只:[ { _id: ObjectId('5f7bea8d79dfe25bf3cb968a'),班级:'游艇',name: '液体黄金',__v: 0 } ] },{ _id: ObjectId('5f7bea8d79dfe25bf3cb9697'),name: 'CAPTAIN_SHIP',类:['船'],许可证:'WC-3',船只上下文:{ _id: ObjectId('5f7bea8d79dfe25bf3cb968b'),watercraftType: '船',参考:船"},__v: 0,船只:[ { _id: ObjectId('5f7bea8d79dfe25bf3cb968b'),类:船",name: '珍妮',__v: 0 } ] },{ _id: ObjectId('5f7bea8d79dfe25bf3cb969b'),name: 'CAPTAIN_SAIL_YATCH_SHIP',课程:['sail', 'yatch', 'ship'],许可证:'WC-7',船只上下文:{ _id: ObjectId('5f7bea8d79dfe25bf3cb9692'),watercraftType: '帆船',参考:帆船"},__v: 0,船只:[ { _id: ObjectId('5f7bea8d79dfe25bf3cb9692'),类:'帆',name: '膨胀粉碎机',__v: 0 } ] },{ _id: ObjectId('5f7bea8d79dfe25bf3cb969b'),name: 'CAPTAIN_SAIL_YATCH_SHIP',课程:['sail', 'yatch', 'ship'],许可证:'WC-7',船只上下文:{ _id: ObjectId('5f7bea8d79dfe25bf3cb9693'),watercraftType: '游艇',参考:'游艇'},__v: 0,船只:[ { _id: ObjectId('5f7bea8d79dfe25bf3cb9693'),班级:'游艇',name: '奥黛丽',__v: 0 } ] },{ _id: ObjectId('5f7bea8d79dfe25bf3cb969b'),name: 'CAPTAIN_SAIL_YATCH_SHIP',课程:['sail', 'yatch', 'ship'],许可证:'WC-7',船只上下文:{ _id: ObjectId('5f7bea8d79dfe25bf3cb9694'),watercraftType: '船',参考:船"},__v: 0,船只:[ { _id: ObjectId('5f7bea8d79dfe25bf3cb9694'),类:船",name: '珍妮四世',__v: 0 } ] } ]

步骤 7 $unwind 和 $group

我们$unwind 以便我们现在可以将属于同一Captain 的所有watercrafts 分组.我们还必须使用 $mergeObjectsCaptain 集合中的附加数据临时存储在一个新的临时变量下,为最后阶段做准备.

 { '$unwind': '$watercrafts' },{'$组':{_id: '$_id',数据:{'$合并对象':{名称:'$名称',许可证:'$许可证',类:'$类',watercraftContexts: '$watercraftContexts',__v: '$__v' } },船只:{ '$push': '$watercrafts' } } }

$unwind$group 结果

现在我们真的到了某个地方.我们已将转换减少到最初的 4 个 Captain 并将连接扁平化为单个数组.

[ { _id: ObjectId('5f7bed5e271dd95c306c25a4'),数据:{名称:'CAPTAIN_SHIP',许可证:'WC-3',类:['船'],船只上下文:{ _id: ObjectId('5f7bed5e271dd95c306c2598'),watercraftType: '船',参考:船"},__v: 0 },船只:[ { _id: ObjectId('5f7bed5e271dd95c306c2598'),类:船",name: '珍妮',__v: 0 } ] },{ _id: ObjectId('5f7bed5e271dd95c306c25a8'),数据:{名称:'CAPTAIN_SAIL_YATCH_SHIP',许可证:'WC-7',课程:['sail', 'yatch', 'ship'],船只上下文:{ _id: ObjectId('5f7bed5e271dd95c306c25a1'),watercraftType: '船',参考:船"},__v: 0 },船只:[ { _id: ObjectId('5f7bed5e271dd95c306c259f'),类:'帆',name: '膨胀粉碎机',__v: 0 },{ _id: ObjectId('5f7bed5e271dd95c306c25a0'),班级:'游艇',name: '奥黛丽',__v: 0 },{ _id: ObjectId('5f7bed5e271dd95c306c25a1'),类:船",name: '珍妮四世',__v: 0 } ] },{ _id: ObjectId('5f7bed5e271dd95c306c25a2'),数据:{名称:'CAPTAIN_SAIL',许可证:'WC-1',课程:['帆'],船只上下文:{_id:对象('5f7bed5e271dd95c306c2596'),watercraftType: '帆船',参考:帆船"},__v: 0 },船只:[ { _id: ObjectId('5f7bed5e271dd95c306c2596'),类:'帆',name: '飘',__v: 0 } ] },{ _id: ObjectId('5f7bed5e271dd95c306c25a3'),数据:{名称:'CAPTAIN_YATCH',许可证:'WC-2',课程:['游艇'],船只上下文:{ _id: ObjectId('5f7bed5e271dd95c306c2597'),watercraftType: '游艇',参考:'游艇'},__v: 0 },船只:[ { _id: ObjectId('5f7bed5e271dd95c306c2597'),班级:'游艇',name: '液体黄金',__v: 0 } ] } ]

第 8 步 $replaceRoot 和 $project

我们剩下的就是将我们的data合并到每条记录的根目录中并移除临时变量data

//将 'data' 合并到每条记录的根{ '$replaceRoot': { newRoot: { '$mergeObjects': [ '$data', '$$ROOT' ] } } },//使用 $project 删除数据(只包含我们想要的字段){'$项目':{名称:真实,许可证:真实,类:真的,_id:真实,watercraftContexts: 真,__v:对,船只:真实 }}

$replaceRoot &$project 结果

现在我们得到了我们为......一个Captain 和一个混合关联类型数组watercrafts

的结果<预><代码>[{名称:'CAPTAIN_SAIL_YATCH_SHIP',许可证:'WC-7',课程:['sail', 'yatch', 'ship'],船只上下文:{ _id: ObjectId('5f7bf3b3680b375ca1755ea6'),watercraftType: '船',参考:船"},__v: 0,_id: ObjectId('5f7bf3b3680b375ca1755ead'),船只:[ { _id: ObjectId('5f7bf3b3680b375ca1755ea4'),类:'帆',name: '膨胀粉碎机',__v: 0 },{ _id: ObjectId('5f7bf3b3680b375ca1755ea5'),班级:'游艇',name: '奥黛丽',__v: 0 },{ _id: ObjectId('5f7bf3b3680b375ca1755ea6'),类:船",name: '珍妮四世',__v: 0 } ] },{名称:'CAPTAIN_SAIL',许可证:'WC-1',课程:['帆'],船只上下文:{ _id: ObjectId('5f7bf3b3680b375ca1755e9b'),watercraftType: '帆船',参考:'帆船' },__v: 0,_id: ObjectId('5f7bf3b3680b375ca1755ea7'),船只:[ { _id: ObjectId('5f7bf3b3680b375ca1755e9b'),类:'帆',name: '飘',__v: 0 } ] },{名称:'CAPTAIN_YATCH',许可证:'WC-2',课程:['游艇'],船只上下文:{ _id: ObjectId('5f7bf3b3680b375ca1755e9c'),watercraftType: '游艇',参考:'游艇'},__v: 0,_id: ObjectId('5f7bf3b3680b375ca1755ea8'),船只:[ { _id: ObjectId('5f7bf3b3680b375ca1755e9c'),班级:'游艇',name: '液体黄金',__v: 0 } ] },{名称:'CAPTAIN_SHIP',许可证:'WC-3',类:['船'],船只上下文:{ _id: ObjectId('5f7bf3b3680b375ca1755e9d'),watercraftType: '船',参考:船"},__v: 0,_id: ObjectId('5f7bf3b3680b375ca1755ea9'),船只:[ { _id: ObjectId('5f7bf3b3680b375ca1755e9d'),类:船",name: '珍妮',__v: 0 } ] } ]

你知道了……只花了 2 天的时间就搞定了这个问题.如果您正在尝试类似的聚合关联,我希望它可以为您节省一些时间.快乐编码!

最终管道

<预><代码>[{ '$匹配':{ _ID:{'$in':[ ObjectId('5f7bf3b3680b375ca1755ea9'),ObjectId('5f7bf3b3680b375ca1755ea7'),ObjectId('5f7bf3b3680b375ca1755ea8'),ObjectId('5f7bf3b3680b375ca1755ead')]}}},{ '$unwind': '$watercraftContexts' },{'$查找':{ 来自:'船',localField: 'watercraftContexts._id',外国字段:'_id',如:'watercrafts.ships' } },{ '$unwind': '$watercraftContexts' },{'$查找':{来自:'游艇',localField: 'watercraftContexts._id',外国字段:'_id',如:'watercrafts.yatches' } },{ '$unwind': '$watercraftContexts' },{'$查找':{来自:'帆船',localField: 'watercraftContexts._id',外国字段:'_id',如:'watercrafts.sailboats' } },{'$项目':{名称:真实,许可证:真实,类:真的,_id:真实,watercraftContexts: 真,__v:对,船只:{ '$setUnion':['$watercrafts.ships','$watercrafts.yatches','$watercrafts.sailboats'] } } },{ '$unwind': '$watercrafts' },{'$组':{_id: '$_id',数据:{'$合并对象':{名称:'$名称',许可证:'$许可证',类:'$类',watercraftContexts: '$watercraftContexts',__v: '$__v' } },船只:{ '$push': '$watercrafts' } } },{ '$replaceRoot': { newRoot: { '$mergeObjects': [ '$data', '$$ROOT' ] } } },{'$项目':{名称:真实,许可证:真实,类:真的,_id:真实,watercraftContexts: 真,__v:对,船只:真实 } }]

I'm new to the aggregate feature with mongodb and mongoose and have been having difficulty getting the desired results after passing data through my pipeline.

Below I have simplified the using a fictional example models

The scenario

I have 3 Models (Ship, Yatch, and Sailboat) that share the interface and extend from a base class. A 4th model, Captain, which has an array watercraftContexts that contain objects used to reference the types of watercrafts associated to each Captain.

Example Mongo Data/Schema setup

// Model Name: 'Captain', Collection Name: 'captains'
{
  name: 'Jack Sparrow',                // Captian name
  license: 'SYS-123',                  // License Number
  classes: ['sail', 'yatch', 'ship'],  // Array of allowed operational vessel types
  watercraftContexts: [
    {
       _id: ObjectId('xxxxxxxxxxxxxxx'), // Foreign Model ID
       type: 'Sailboat',                 // Model Name
       ref: 'sailboats'.                 // Collection Name
    },
    {
       _id: ObjectId('xxxxxxxxxxxxxxx'), // Foreign Model ID
       type: 'Yatch',                    // Model Name
       ref: 'yatches'.                   // Collection Name
    },
    {
       _id: ObjectId('xxxxxxxxxxxxxxx'), // Foreign Model ID
       type: 'Ship',                     // Model Name
       ref: 'ships'.                     // Collection Name
    }
  ]
}

As you can see, the array of objects has been setup to use the mongoose.populate() method with the ref and _id fields and I've implemented a virtual getter watercrafts to hydrate with the populate() feature (code not posted).

A new field is created as watercrafts with an array of all objects from the 3 different associated collections when using mongoose.Model queries.

The Problem

I also need a way to aggregate against this data to produce similar results since the Model methods are not available in the aggregate pipline.

Here is the query generated from my programmatic mongo aggregate:

[ { '$match':
     { _id:
        { '$in':
           [ ObjectId('5f77bc653887221a703415e1'),
             ObjectId('5f77bc653887221a703415df'),
             ObjectId('5f77bc653887221a703415e0'),
             ObjectId('5f77bc653887221a703415e5') ] } } },
  { '$unwind': '$watercraftContexts' },
  { '$lookup':
     { from: 'ships',
       localField: 'watercraftContexts._id',
       foreignField: '_id',
       as: 'watercrafts.ships' } },
  { '$unwind': '$watercraftContexts' },
  { '$lookup':
     { from: 'yatches',
       localField: 'watercraftContexts._id',
       foreignField: '_id',
       as: 'watercrafts.yatches' } },
  { '$unwind': '$watercraftContexts' },
  { '$lookup':
     { from: 'sailboats',
       localField: 'watercraftContexts._id',
       foreignField: '_id',
       as: 'watercrafts.sailboats' } },
  { '$group':
     { _id: '$_id',
       watercrafts:
        { '$addToSet':
           { '$concatArrays':
              [ '$watercrafts.ships',
                '$watercrafts.yatches',
                '$watercrafts.sailboats' ] } }

I'm constructing a mongoose aggregate like so:

const Captain = mongoose.model('Captain')
const aggregate = Captain.aggregrate()

// Dynamically create Aggregate Pipeline in another function
const captains = await Captain.find({})
const captainIds = captains.map(capt => capt._id)

// Match sub-set of documents (in actual project)
aggregate.match({ _id: { $in: captainIds } })

// Collection names to apply $lookup aggregate
const collectionNames = ['sailboats', 'yatches', 'ships']

// Unwind and Lookup for each polymorphic child class's collection
collectionNames.forEach(collection => {
  // Separate watercraftContexts into individual records for lookup
  aggregate.unwind('watercraftContexts')
  // Inner Join collection data on record
  aggregate.lookup({
    from: collection,
    localField: '$watercrafContexts._id',
    foreignField: '_id',
      // Object keyed by collection name with array of collection records
      // to avoid overwrite of previous collection aggregate lookup
    as: `watercrafts.${collection}`  
  })
})

// Re-group the records by Captain Object Id
const aggregateAssociationPaths = collectionNames.map(collection => 
  // Mongo Path to each collection $lookup
  `$watercrafts.${collection}`
)

// Re-assemble $unwind and $group by Captain's ObjectId
aggregate.group({
  _id: '$_id',
  $addToSet: {
    // 
    $concatArrays: aggregateAssociationPaths 
  }
})

/***  !!!   !!!   !!!   !!!   !!!   !!!   !!!   !!!   !!!   !!!   !!!   !!!   !!!  !!!  ***
 *                                                                                        *
 *    WHAT DO I DO NEXT TO GET ALL THE CAPTAIN DATA WITH THE AGGREGATED `watercrafts`
 *                                                                                        *
 ***  !!!   !!!   !!!   !!!   !!!   !!!   !!!   !!!   !!!   !!!   !!!   !!!   !!!  !!!  ***/

// Execute Aggregation
const captiansWithWatercraftsAssociations = await aggregate

My data is up to this point looks like this and the group isn't working with mongoose:

[ { _id: 5f77bc653887221a703415df,
    watercrafts:
     [ { _id: 5f77bc653887221a703415d3,
         class: 'sail',
         name: 'Gone with the Wind',
         __v: 0 } ] },
  { _id: 5f77bc653887221a703415e0,
    watercrafts:
     [ { _id: 5f77bc653887221a703415d4,
         class: 'yatch',
         name: 'Liquid Gold',
         __v: 0 } ] },
  { _id: 5f77bc653887221a703415e1,
    watercrafts:
     [ { _id: 5f77bc653887221a703415d5,
         class: 'ship',
         name: 'Jenny',
         __v: 0 } ] },
  { _id: 5f77bc653887221a703415e5,
    watercrafts:
     [ { _id: 5f77bc653887221a703415dd,
         class: 'yatch',
         name: 'Audrey',
         __v: 0 } ] },
  { _id: 5f77bc653887221a703415e5,
    watercrafts:
     [ { _id: 5f77bc653887221a703415dc,
         class: 'sail',
         name: 'Swell Shredder',
         __v: 0 } ] },
  { _id: 5f77bc653887221a703415e5,
    watercrafts:
     [ { _id: 5f77bc653887221a703415de,
         class: 'ship',
         name: 'Jenny IV',
         __v: 0 } ] } ]

Thanks for the support

解决方案

This was a tricky one for someone new to MongoDb's aggregate. I'll break down my answer into steps to demonstrate to other attempting to aggregate an array with referencing multiple collections.

Step 1 - $match to filter on collection

The $match is accepts the same queries as db.collection.find({}) and returns an array of matching results in the case below, I select 4 specific records here


{ '$match':
     { _id:
        { '$in':
           [
              ObjectId('5f7bdb3eea134b5a5c976285'),
              ObjectId('5f7bdb3eea134b5a5c976283'),
              ObjectId('5f7bdb3eea134b5a5c976284'),
              ObjectId('5f7bdb3eea134b5a5c976289')
           ]
        }
     }
}

$match Result

[ 
  { _id: ObjectId('5f7be0b37e2bdf5b19e4724d'),
    name: 'CAPTAIN_SAIL',
    classes: [ 'sail' ],
    license: 'WC-1',
    watercraftContexts:
     [ { _id: ObjectId('5f7be0b37e2bdf5b19e47241'),
         watercraftType: 'Sailboat',
         ref: 'sailboats' } ],
    __v: 0 },
  { _id: ObjectId('5f7be0b37e2bdf5b19e4724e'),
    name: 'CAPTAIN_YATCH',
    classes: [ 'yatch' ],
    license: 'WC-2',
    watercraftContexts:
     [ { _id: ObjectId('5f7be0b37e2bdf5b19e47242'),
         watercraftType: 'Yatch',
         ref: 'yatches' } ],
    __v: 0 },
  { _id: ObjectId('5f7be0b37e2bdf5b19e4724f'),
    name: 'CAPTAIN_SHIP',
    classes: [ 'ship' ],
    license: 'WC-3',
    watercraftContexts:
     [ { _id: ObjectId('5f7be0b37e2bdf5b19e47243'),
         watercraftType: 'Ship',
         ref: 'ships' } ],
    __v: 0 },
  { _id: ObjectId('5f7be0b37e2bdf5b19e47253'),
    name: 'CAPTAIN_SAIL_YATCH_SHIP',
    classes: [ 'sail', 'yatch', 'ship' ],
    license: 'WC-7',
    watercraftContexts:
     [ { _id: ObjectId('5f7be0b37e2bdf5b19e4724a'),
         watercraftType: 'Sailboat',
         ref: 'sailboats' },
       { _id: ObjectId('5f7be0b37e2bdf5b19e4724b'),
         watercraftType: 'Yatch',
         ref: 'yatches' },
       { _id: ObjectId('5f7be0b37e2bdf5b19e4724c'),
         watercraftType: 'Ship',
         ref: 'ships' } ],
    __v: 0 }
]

Step 2 - $unwind so we can iterate with $loopup

In this result set there is an array of objects with { _id: <ObjectId>, watercraftType: <ModelName> } to loop over the array and join each of these objects with there respective collection record, we have to break up the array into individual independent records. The $unwind feature will create a new data set for the next aggregate stage

  { '$unwind': '$watercraftContexts' },

$unwind Result

As you can see $unwind now creates a record with a single watercraftContext we are now set to use the $lookup

[ { _id: ObjectId('5f7be2231da37c5b5915bf9b'),
    name: 'CAPTAIN_SAIL',
    classes: [ 'sail' ],
    license: 'WC-1',
    watercraftContexts:
     { _id: ObjectId('5f7be2231da37c5b5915bf8f'),
       watercraftType: 'Sailboat',
       ref: 'sailboats' },
    __v: 0 },
  { _id: ObjectId('5f7be2231da37c5b5915bf9c'),
    name: 'CAPTAIN_YATCH',
    classes: [ 'yatch' ],
    license: 'WC-2',
    watercraftContexts:
     { _id: ObjectId('5f7be2231da37c5b5915bf90'),
       watercraftType: 'Yatch',
       ref: 'yatches' },
    __v: 0 },
  { _id: ObjectId('5f7be2231da37c5b5915bf9d'),
    name: 'CAPTAIN_SHIP',
    classes: [ 'ship' ],
    license: 'WC-3',
    watercraftContexts:
     { _id: ObjectId('5f7be2231da37c5b5915bf91'),
       watercraftType: 'Ship',
       ref: 'ships' },
    __v: 0 },
  { _id: ObjectId('5f7be2231da37c5b5915bfa1'),
    name: 'CAPTAIN_SAIL_YATCH_SHIP',
    classes: [ 'sail', 'yatch', 'ship' ],
    license: 'WC-7',
    watercraftContexts:
     { _id: ObjectId('5f7be2231da37c5b5915bf98'),
       watercraftType: 'Sailboat',
       ref: 'sailboats' },
    __v: 0 },
  { _id: ObjectId('5f7be2231da37c5b5915bfa1'),
    name: 'CAPTAIN_SAIL_YATCH_SHIP',
    classes: [ 'sail', 'yatch', 'ship' ],
    license: 'WC-7',
    watercraftContexts:
     { _id: ObjectId('5f7be2231da37c5b5915bf99'),
       watercraftType: 'Yatch',
       ref: 'yatches' },
    __v: 0 },
  { _id: ObjectId('5f7be2231da37c5b5915bfa1'),
    name: 'CAPTAIN_SAIL_YATCH_SHIP',
    classes: [ 'sail', 'yatch', 'ship' ],
    license: 'WC-7',
    watercraftContexts:
     { _id: ObjectId('5f7be2231da37c5b5915bf9a'),
       watercraftType: 'Ship',
       ref: 'ships' },
    __v: 0 } ]

Step 4 $lookup - Joins each record from the foreign collection

It is important to note that we must $unwind before calling $lookup for each different collection we need to join. Since we want to join multiple collections, we need to store the result in an objected keyed by the collection for later aggregation.

  // Only performs $lookup on 'ships' collection
  { '$lookup':
     { from: 'ships',  // Collection Name - Note: repeat for each collection
       localField: 'watercraftContexts._id', // The field with id to link
       foreignField: '_id',  // The field on the foreign collection to match
       as: 'watercrafts.ships' // The path where to store the lookup result
     }
  }

Step 5 - Repeat the $unwind and $lookup for the other joins

Repeat the above to steps for the additional joins, and key by the collection name. I have combined the aggregate stages to demonstrate the repetition.

  { '$unwind': '$watercraftContexts' },
  { '$lookup':
     { from: 'yatches',
       localField: 'watercraftContexts._id',
       foreignField: '_id',
       as: 'watercrafts.yatches' } },
  { '$unwind': '$watercraftContexts' },
  { '$lookup':
     { from: 'sailboats',
       localField: 'watercraftContexts._id',
       foreignField: '_id',
       as: 'watercrafts.sailboats' } }

Step 4 & 5 Results

If you look carefully, you notice that one of the Captain records exists 3 times with a different watercraftType. $lookup will only return records matching specific collection name. This is why why store them in an Object keyed by collectionName

[
  { _id: ObjectId('5f7be7145320a65b942bb450'),
    name: 'CAPTAIN_SAIL',
    classes: [ 'sail' ],
    license: 'WC-1',
    watercraftContexts:
     { _id: ObjectId('5f7be7145320a65b942bb444'),
       watercraftType: 'Sailboat',
       ref: 'sailboats' },
    __v: 0,
    watercrafts:
     { ships: [],
       yatches: [],
       sailboats:
        [ { _id: ObjectId('5f7be7145320a65b942bb444'),
            class: 'sail',
            name: 'Gone with the Wind',
            __v: 0 } ] } },
  { _id: ObjectId('5f7be7145320a65b942bb451'),
    name: 'CAPTAIN_YATCH',
    classes: [ 'yatch' ],
    license: 'WC-2',
    watercraftContexts:
     { _id: ObjectId('5f7be7145320a65b942bb445'),
       watercraftType: 'Yatch',
       ref: 'yatches' },
    __v: 0,
    watercrafts:
     { ships: [],
       yatches:
        [ { _id: ObjectId('5f7be7145320a65b942bb445'),
            class: 'yatch',
            name: 'Liquid Gold',
            __v: 0 } ],
       sailboats: [] } },
  { _id: ObjectId('5f7be7145320a65b942bb452'),
    name: 'CAPTAIN_SHIP',
    classes: [ 'ship' ],
    license: 'WC-3',
    watercraftContexts:
     { _id: ObjectId('5f7be7145320a65b942bb446'),
       watercraftType: 'Ship',
       ref: 'ships' },
    __v: 0,
    watercrafts:
     { ships:
        [ { _id: ObjectId('5f7be7145320a65b942bb446'),
            class: 'ship',
            name: 'Jenny',
            __v: 0 } ],
       yatches: [],
       sailboats: [] } },
  { _id: ObjectId('5f7be7145320a65b942bb456'),
    name: 'CAPTAIN_SAIL_YATCH_SHIP',
    classes: [ 'sail', 'yatch', 'ship' ],
    license: 'WC-7',
    watercraftContexts:
     { _id: ObjectId('5f7be7145320a65b942bb44d'),
       watercraftType: 'Sailboat',
       ref: 'sailboats' },
    __v: 0,
    watercrafts:
     { ships: [],
       yatches: [],
       sailboats:
        [ { _id: ObjectId('5f7be7145320a65b942bb44d'),
            class: 'sail',
            name: 'Swell Shredder',
            __v: 0 } ] } },
  { _id: ObjectId('5f7be7145320a65b942bb456'),
    name: 'CAPTAIN_SAIL_YATCH_SHIP',
    classes: [ 'sail', 'yatch', 'ship' ],
    license: 'WC-7',
    watercraftContexts:
     { _id: ObjectId('5f7be7145320a65b942bb44e'),
       watercraftType: 'Yatch',
       ref: 'yatches' },
    __v: 0,
    watercrafts:
     { ships: [],
       yatches:
        [ { _id: ObjectId('5f7be7145320a65b942bb44e'),
            class: 'yatch',
            name: 'Audrey',
            __v: 0 } ],
       sailboats: [] } },
  { _id: ObjectId('5f7be7145320a65b942bb456'),
    name: 'CAPTAIN_SAIL_YATCH_SHIP',
    classes: [ 'sail', 'yatch', 'ship' ],
    license: 'WC-7',
    watercraftContexts:
     { _id: ObjectId('5f7be7145320a65b942bb44f'),
       watercraftType: 'Ship',
       ref: 'ships' },
    __v: 0,
    watercrafts:
     { ships:
        [ { _id: ObjectId('5f7be7145320a65b942bb44f'),
            class: 'ship',
            name: 'Jenny IV',
            __v: 0 } ],
       yatches: [],
       sailboats: [] } } ]

Step 6 $project - Use project to flatten the Object Map of joins

We can use project to select all the existing data and flatten the Object Map of join results into a single Array.

  { '$project':
     // keys with the value 'true' will be included
     { name: true,
       license: true,
       classes: true,
       _id: true,
       watercraftContexts: true,
       __v: true,
       watercrafts:            // Re-assigns value of watercrafts
        { '$setUnion':         // Accepts an array of arrays to flatten
           [
             '$watercrafts.ships',
             '$watercrafts.yatches',
             '$watercrafts.sailboats'
           ]
        }
     }
  }

$project Result

The results of the above $project will replace the watercrafts object with an flatten array of watercrafts, but it is important to note that there are still duplicate records of Captain where matching many different lookups. We will re-piece them together in the next step.

[ { _id: ObjectId('5f7bea8d79dfe25bf3cb9695'),
    name: 'CAPTAIN_SAIL',
    classes: [ 'sail' ],
    license: 'WC-1',
    watercraftContexts:
     { _id: ObjectId('5f7bea8d79dfe25bf3cb9689'),
       watercraftType: 'Sailboat',
       ref: 'sailboats' },
    __v: 0,
    watercrafts:
     [ { _id: ObjectId('5f7bea8d79dfe25bf3cb9689'),
         class: 'sail',
         name: 'Gone with the Wind',
         __v: 0 } ] },
  { _id: ObjectId('5f7bea8d79dfe25bf3cb9696'),
    name: 'CAPTAIN_YATCH',
    classes: [ 'yatch' ],
    license: 'WC-2',
    watercraftContexts:
     { _id: ObjectId('5f7bea8d79dfe25bf3cb968a'),
       watercraftType: 'Yatch',
       ref: 'yatches' },
    __v: 0,
    watercrafts:
     [ { _id: ObjectId('5f7bea8d79dfe25bf3cb968a'),
         class: 'yatch',
         name: 'Liquid Gold',
         __v: 0 } ] },
  { _id: ObjectId('5f7bea8d79dfe25bf3cb9697'),
    name: 'CAPTAIN_SHIP',
    classes: [ 'ship' ],
    license: 'WC-3',
    watercraftContexts:
     { _id: ObjectId('5f7bea8d79dfe25bf3cb968b'),
       watercraftType: 'Ship',
       ref: 'ships' },
    __v: 0,
    watercrafts:
     [ { _id: ObjectId('5f7bea8d79dfe25bf3cb968b'),
         class: 'ship',
         name: 'Jenny',
         __v: 0 } ] },
  { _id: ObjectId('5f7bea8d79dfe25bf3cb969b'),
    name: 'CAPTAIN_SAIL_YATCH_SHIP',
    classes: [ 'sail', 'yatch', 'ship' ],
    license: 'WC-7',
    watercraftContexts:
     { _id: ObjectId('5f7bea8d79dfe25bf3cb9692'),
       watercraftType: 'Sailboat',
       ref: 'sailboats' },
    __v: 0,
    watercrafts:
     [ { _id: ObjectId('5f7bea8d79dfe25bf3cb9692'),
         class: 'sail',
         name: 'Swell Shredder',
         __v: 0 } ] },
  { _id: ObjectId('5f7bea8d79dfe25bf3cb969b'),
    name: 'CAPTAIN_SAIL_YATCH_SHIP',
    classes: [ 'sail', 'yatch', 'ship' ],
    license: 'WC-7',
    watercraftContexts:
     { _id: ObjectId('5f7bea8d79dfe25bf3cb9693'),
       watercraftType: 'Yatch',
       ref: 'yatches' },
    __v: 0,
    watercrafts:
     [ { _id: ObjectId('5f7bea8d79dfe25bf3cb9693'),
         class: 'yatch',
         name: 'Audrey',
         __v: 0 } ] },
  { _id: ObjectId('5f7bea8d79dfe25bf3cb969b'),
    name: 'CAPTAIN_SAIL_YATCH_SHIP',
    classes: [ 'sail', 'yatch', 'ship' ],
    license: 'WC-7',
    watercraftContexts:
     { _id: ObjectId('5f7bea8d79dfe25bf3cb9694'),
       watercraftType: 'Ship',
       ref: 'ships' },
    __v: 0,
    watercrafts:
     [ { _id: ObjectId('5f7bea8d79dfe25bf3cb9694'),
         class: 'ship',
         name: 'Jenny IV',
         __v: 0 } ] } ]

Step 7 $unwind and $group

We $unwind so that we can now group all watercrafts belonging to the same Captain. We also have to use $mergeObjects to temporarily store the additional data from the Captain collection under a new temporary variable to prepare for the final stages.

  { '$unwind': '$watercrafts' },
  { '$group':
     { _id: '$_id',
       data:
        { '$mergeObjects':
           { name: '$name',
             license: '$license',
             classes: '$classes',
             watercraftContexts: '$watercraftContexts',
             __v: '$__v' } },
       watercrafts: { '$push': '$watercrafts' } } }

$unwind and $group Result

Now we're really getting somewhere. We have reduced our transformation to our initial 4 Captains and flattened our joins into a single array.

[ { _id: ObjectId('5f7bed5e271dd95c306c25a4'),
    data:
     { name: 'CAPTAIN_SHIP',
       license: 'WC-3',
       classes: [ 'ship' ],
       watercraftContexts:
        { _id: ObjectId('5f7bed5e271dd95c306c2598'),
          watercraftType: 'Ship',
          ref: 'ships' },
       __v: 0 },
    watercrafts:
     [ { _id: ObjectId('5f7bed5e271dd95c306c2598'),
         class: 'ship',
         name: 'Jenny',
         __v: 0 } ] },
  { _id: ObjectId('5f7bed5e271dd95c306c25a8'),
    data:
     { name: 'CAPTAIN_SAIL_YATCH_SHIP',
       license: 'WC-7',
       classes: [ 'sail', 'yatch', 'ship' ],
       watercraftContexts:
        { _id: ObjectId('5f7bed5e271dd95c306c25a1'),
          watercraftType: 'Ship',
          ref: 'ships' },
       __v: 0 },
    watercrafts:
     [ { _id: ObjectId('5f7bed5e271dd95c306c259f'),
         class: 'sail',
         name: 'Swell Shredder',
         __v: 0 },
       { _id: ObjectId('5f7bed5e271dd95c306c25a0'),
         class: 'yatch',
         name: 'Audrey',
         __v: 0 },
       { _id: ObjectId('5f7bed5e271dd95c306c25a1'),
         class: 'ship',
         name: 'Jenny IV',
         __v: 0 } ] },
  { _id: ObjectId('5f7bed5e271dd95c306c25a2'),
    data:
     { name: 'CAPTAIN_SAIL',
       license: 'WC-1',
       classes: [ 'sail' ],
       watercraftContexts:
        { _id: Object('5f7bed5e271dd95c306c2596'),
          watercraftType: 'Sailboat',
          ref: 'sailboats' },
       __v: 0 },
    watercrafts:
     [ { _id: ObjectId('5f7bed5e271dd95c306c2596'),
         class: 'sail',
         name: 'Gone with the Wind',
         __v: 0 } ] },
  { _id: ObjectId('5f7bed5e271dd95c306c25a3'),
    data:
     { name: 'CAPTAIN_YATCH',
       license: 'WC-2',
       classes: [ 'yatch' ],
       watercraftContexts:
        { _id: ObjectId('5f7bed5e271dd95c306c2597'),
          watercraftType: 'Yatch',
          ref: 'yatches' },
       __v: 0 },
    watercrafts:
     [ { _id: ObjectId('5f7bed5e271dd95c306c2597'),
         class: 'yatch',
         name: 'Liquid Gold',
         __v: 0 } ] } ]

Step 8 $replaceRoot and $project

All we have left is to merge our data into the root of each record and remove the temporary variable data

  // Merges 'data' into the root of each record
  { '$replaceRoot': { newRoot: { '$mergeObjects': [ '$data', '$$ROOT' ] } } },
  // Use $project to remove data (include only the fields we want)
  { '$project':
     { name: true,
       license: true,
       classes: true,
       _id: true,
       watercraftContexts: true,
       __v: true,
       watercrafts: true } 
  }

$replaceRoot & $project Result

Now we have the result we set out for...A Captain with an array of mixed associated types watercrafts

[ 
  { name: 'CAPTAIN_SAIL_YATCH_SHIP',
    license: 'WC-7',
    classes: [ 'sail', 'yatch', 'ship' ],
    watercraftContexts:
     { _id: ObjectId('5f7bf3b3680b375ca1755ea6'),
       watercraftType: 'Ship',
       ref: 'ships' },
    __v: 0,
    _id: ObjectId('5f7bf3b3680b375ca1755ead'),
    watercrafts:
     [ { _id: ObjectId('5f7bf3b3680b375ca1755ea4'),
         class: 'sail',
         name: 'Swell Shredder',
         __v: 0 },
       { _id: ObjectId('5f7bf3b3680b375ca1755ea5'),
         class: 'yatch',
         name: 'Audrey',
         __v: 0 },
       { _id: ObjectId('5f7bf3b3680b375ca1755ea6'),
         class: 'ship',
         name: 'Jenny IV',
         __v: 0 } ] },
  { name: 'CAPTAIN_SAIL',
    license: 'WC-1',
    classes: [ 'sail' ],
    watercraftContexts:
     { _id: ObjectId('5f7bf3b3680b375ca1755e9b'),
       watercraftType: 'Sailboat',
       ref: 'sailboats' },
    __v: 0,
    _id: ObjectId('5f7bf3b3680b375ca1755ea7'),
    watercrafts:
     [ { _id: ObjectId('5f7bf3b3680b375ca1755e9b'),
         class: 'sail',
         name: 'Gone with the Wind',
         __v: 0 } ] },
  { name: 'CAPTAIN_YATCH',
    license: 'WC-2',
    classes: [ 'yatch' ],
    watercraftContexts:
     { _id: ObjectId('5f7bf3b3680b375ca1755e9c'),
       watercraftType: 'Yatch',
       ref: 'yatches' },
    __v: 0,
    _id: ObjectId('5f7bf3b3680b375ca1755ea8'),
    watercrafts:
     [ { _id: ObjectId('5f7bf3b3680b375ca1755e9c'),
         class: 'yatch',
         name: 'Liquid Gold',
         __v: 0 } ] },
  { name: 'CAPTAIN_SHIP',
    license: 'WC-3',
    classes: [ 'ship' ],
    watercraftContexts:
     { _id: ObjectId('5f7bf3b3680b375ca1755e9d'),
       watercraftType: 'Ship',
       ref: 'ships' },
    __v: 0,
    _id: ObjectId('5f7bf3b3680b375ca1755ea9'),
    watercrafts:
     [ { _id: ObjectId('5f7bf3b3680b375ca1755e9d'),
         class: 'ship',
         name: 'Jenny',
         __v: 0 } ] } ]

And there you have it...only took 2 days to figure this out. I hope it saves you some time if you're attempting a similar aggregate association. Happy coding!

Final Pipeline

[ 
  { '$match':
     { _id:
        { '$in':
           [ ObjectId('5f7bf3b3680b375ca1755ea9'),
             ObjectId('5f7bf3b3680b375ca1755ea7'),
             ObjectId('5f7bf3b3680b375ca1755ea8'),
             ObjectId('5f7bf3b3680b375ca1755ead')
           ]
        }
     }
  },
  { '$unwind': '$watercraftContexts' },
  { '$lookup':
     { from: 'ships',
       localField: 'watercraftContexts._id',
       foreignField: '_id',
       as: 'watercrafts.ships' } },
  { '$unwind': '$watercraftContexts' },
  { '$lookup':
     { from: 'yatches',
       localField: 'watercraftContexts._id',
       foreignField: '_id',
       as: 'watercrafts.yatches' } },
  { '$unwind': '$watercraftContexts' },
  { '$lookup':
     { from: 'sailboats',
       localField: 'watercraftContexts._id',
       foreignField: '_id',
       as: 'watercrafts.sailboats' } },
  { '$project':
     { name: true,
       license: true,
       classes: true,
       _id: true,
       watercraftContexts: true,
       __v: true,
       watercrafts:
        { '$setUnion':
           [ '$watercrafts.ships',
             '$watercrafts.yatches',
             '$watercrafts.sailboats' ] } } },
  { '$unwind': '$watercrafts' },
  { '$group':
     { _id: '$_id',
       data:
        { '$mergeObjects':
           { name: '$name',
             license: '$license',
             classes: '$classes',
             watercraftContexts: '$watercraftContexts',
             __v: '$__v' } },
       watercrafts: { '$push': '$watercrafts' } } },
  { '$replaceRoot': { newRoot: { '$mergeObjects': [ '$data', '$$ROOT' ] } } },
  { '$project':
     { name: true,
       license: true,
       classes: true,
       _id: true,
       watercraftContexts: true,
       __v: true,
       watercrafts: true } }
]

这篇关于Mongoose.aggregate(pipeline) 使用 $unwind, $lookup, $group 链接多个集合的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆