首页 > 解决方案 > 使用 MongoDB 将文档中数组的每个元素链接到另一个文档的数组中的相应元素

问题描述

使用 MongoDB 4.2 和 MongoDB Atlas 测试聚合管道。

我有这个产品集合,包含具有这种模式的文档:

 {
    "name": "TestProduct",
    "relatedList": [
      {id:ObjectId("someId")},
      {id:ObjectId("anotherId")}
    ]
 }

然后是这个城市集合,包含具有这种模式的文档:

{
        "name": "TestCity",
        "instructionList": [
          { related_id: ObjectId("anotherId"), foo: bar},
          { related_id: ObjectId("someId"), foo: bar}
          { related_id: ObjectId("notUsefulId"), foo: bar}
          ...
        ]
 }

我的目标是加入两个集合以输出类似这样的内容(操作是从城市文档中的指令列表中挑选每个相关对象,将其放入产品文档的相关列表中):

{
        "name": "TestProduct",
        "relatedList": [
          { related_id: ObjectId("someId"), foo: bar},
          { related_id: ObjectId("anotherId"), foo: bar},
        ]
}

我尝试使用 $lookup 运算符进行聚合,如下所示

$lookup:{
  from: 'cities',
  let: {rId:'$relatedList._id'},
  pipeline: [
         {
           $match: {
             $expr: {
               $eq: ["$instructionList.related_id", "$$rId"]
             }
           }
         },
  ]
}

但它不起作用,我对这种复杂的管道语法有点迷失了。

编辑

通过在两个数组上使用 unwind :

    { 
         {$unwind: "$relatedList"},
         {$lookup:{
             from: "cities",
             let: { "rId": "$relatedList.id" },
             pipeline: [
        
                {$unwind:"$instructionList"},
                {$match:{$expr:{$eq:["$instructionList.related_id","$$rId"]}}},

             ],
             as:"instructionList",
         }},

         {$group: {
             _id: "$_id",
             instructionList: {$addToSet:"$instructionList"}

          }}
}

我能够实现我想要的,但是,我根本没有得到一个干净的结果:

{
 "name": "TestProduct",
 instructionList: [
    [
      {
        "name": "TestCity",
        "instructionList": {
         "related_id":ObjectId("someId")
        }
      }
    ],
    [
      {
        "name": "TestCity",
        "instructionList": {
         "related_id":ObjectId("anotherId")
        }
      }
    ]
 ]
}

我怎样才能将所有内容分组以像我原来的问题所说的那样干净?同样,我完全迷失了聚合框架。

标签: mongodbpipelinebson

解决方案


我相信你只需要 $unwind 数组来查找关系,然后 $group 来重新收集它们。也许是这样的:

.aggregeate([
    {$unwind:"relatedList"},
    {$lookup:{
         from:"cities",
         let:{rId:"$relatedList.id"}
         pipeline:[
             {$match:{$expr:{$eq:["$instructionList.related_id", "$$rId"]}}},
             {$unwind:"$instructionList"},
             {$match:{$expr:{$eq:["$instructionList.related_id", "$$rId"]}}},
             {$project:{_id:0, instruction:"$instructionList"}}
         ],
         as: "lookedup"
     }},
     {$addFields: {"relatedList.foo":"$lookedup.0.instruction.foo"}},
     {$group: {
                _id:"$_id",
                root: {$first:"$$ROOT"},
                relatedList:{$push:"$relatedList"}
     }},
     {$addFields:{"root.relatedList":"$relatedList"}},
     {$replaceRoot:{newRoot:"$root"}}
])

每个阶段的一点点:

  • $unwind 为数组的每个元素复制整个文档,用单个元素替换数组
  • 然后 $lookup 可以分别考虑每个元素。$lookup.pipeline 中的阶段
    :$match 所以我们只展开具有匹配 ID
    b 的文档。$unwind 数组,以便我们可以考虑单个元素
    c. 重复 $match 所以我们只剩下匹配的元素(希望只有 1 个)
  • $addFieldsfoo将从查找中检索到的字段分配给来自的对象relatedList
  • $group 将所有具有相同 _id 的文档(即从单个原始文档展开)收集在一起,将第一个文档存储为“根”,并将所有相关列表元素推回数组中
  • $addFields 将相关列表移动到根目录
  • $replaceRoot 返回root,它现在应该是foo添加到每个relatedList元素的匹配项的原始文档

推荐阅读