Mongoose.aggregate(pipeline) 使用 $unwind、$lookup、$group 链接多个集合

Posted

技术标签:

【中文标题】Mongoose.aggregate(pipeline) 使用 $unwind、$lookup、$group 链接多个集合【英文标题】:Mongoose.aggregate(pipeline) link multiple collections using $unwind, $lookup, $group 【发布时间】:2021-01-18 15:59:55 【问题描述】:

我是 mongodbmongoose 的聚合功能的新手,并且在通过我的管道传递数据后很难获得所需的结果。

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

场景

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

示例 Mongo 数据/架构设置

// 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
    
  ]

如您所见,对象数组已设置为使用带有 ref_id 字段的 mongoose.populate() 方法,并且我实现了 virtual getter watercrafts 与 @987654336 水合@ 功能(代码未发布)。

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

问题

由于aggregate pipline 中不提供模型方法,因此我还需要一种针对此数据的aggregate 方法以产生类似的结果。

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

[  '$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' ]  

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

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

到目前为止,我的数据看起来像这样,并且该小组没有使用 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  ]  ]

感谢支持

【问题讨论】:

如果您的问题是关于聚合管道的,您应该询问使用可以在 mongo shell 中输入的代码。 @D.SM 添加了从查询生成的聚合 【参考方案1】:

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

第 1 步 - $match 以过滤集合

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


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

$匹配结果
[ 
   _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 
]

第 2 步 - $unwind 以便我们可以使用 $loopup 进行迭代

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

   '$unwind': '$watercraftContexts' ,
$unwind 结果

如您所见,$unwind 现在使用单个 watercraftContext 创建记录,我们现在设置为使用 $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  ]
第 4 步 $lookup - 加入外部集合中的每条记录

需要注意的是,在为每个需要加入的不同集合调用$lookup 之前,我们必须先$unwind。由于我们要连接多个集合,因此我们需要将结果存储在由集合键控的对象中以供以后聚合。

  // 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
     
  

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

对其他连接重复上述步骤,并按集合名称作为键。我已经结合了聚合阶段来演示重复。

   '$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'  
第 4 步和第 5 步结果

如果您仔细观察,您会注意到其中一个 Captain 记录与不同的 watercraftType 存在 3 次。 $lookup 只会返回匹配特定集合名称的记录。这就是为什么将它们存储在由collectionName 键入的Object 中的原因

[
   _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: []   ]

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

我们可以使用 project 来选择所有现有的数据,并将 join 结果的 Object Map 扁平化为单个 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 结果

上述$project 的结果将watercrafts 对象替换为watercrafts 的扁平数组,但重要的是要注意Captain 的重复记录匹配许多不同的查找。我们将在下一步将它们重新拼凑在一起。

[  _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  ]  ]

步骤 7 $unwind 和 $group

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

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

现在我们真的取得了进展。我们已将转换减少到最初的 4 个Captains,并将我们的连接扁平化为一个数组。

[  _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  ]  ]

第 8 步 $replaceRoot 和 $project

我们剩下的就是将我们的data 合并到每条记录的根并删除临时变量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 结果

现在我们得到了我们为...A Captain 设置的结果,其中包含一组混合关联类型 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  ]  ]

你有它......只花了 2 天时间来解决这个问题。如果您尝试类似的聚合关联,我希望它能为您节省一些时间。编码愉快!

最终流水线

[ 
   '$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 链接多个集合的主要内容,如果未能解决你的问题,请参考以下文章

Mongoose: aggregate聚合 $group使用说明

pipe fitting

Java NIO系列教程 Pipe

转:Java NIO系列教程 Pipe

5.管道 Pipe

[apue] 神奇的 Solaris pipe