【问题标题】:Link each element of array in a document to the corresponding element in an array of another document with MongoDB使用 MongoDB 将文档中数组的每个元素链接到另一个文档的数组中的相应元素
【发布时间】:2020-02-12 22:28:51
【问题描述】:

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

我有这个 products 集合,其中包含具有此架构的文档:

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

然后是这个 cities 集合,包含具有此架构的文档:

{
        "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 运算符进行聚合,例如 this

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

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

编辑

通过在两个数组上使用展开:

    { 
         {$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")
        }
      }
    ]
 ]
}

如何将所有内容分组,使其与我最初的问题所述一样干净? 同样,我完全迷失了聚合框架。

【问题讨论】:

    标签: mongodb pipeline bson


    【解决方案1】:

    操作是从城市文档中的instructionList中挑选每个相关对象放入产品文档的relatedList中)

    给定一个关于cities集合的示例文档:

    {"_id": ObjectId("5e4a22a08c54c8e2380b853b"),
      "name": "TestCity",
      "instructionList": [
        {"related_id": "a", "foo": "x"},
        {"related_id": "b", "foo": "y"},
        {"related_id": "c", "foo": "z"}
    ]}
    

    以及products 集合上的示例文档:

    {"_id": ObjectId("5e45cdd8e8d44a31a432a981"),
      "name": "TestProduct",
      "relatedList": [
        {"id": "a"},
        {"id": "b"}
    ]}
    

    您可以尝试使用以下聚合管道来实现:

    db.products.aggregate([
        {"$lookup":{
            "from": "cities", 
            "let": { "rId": "$relatedList.id" }, 
            "pipeline": [
                {"$unwind":"$instructionList"},
                {"$match":{
                    "$expr":{
                        "$in":["$instructionList.related_id", "$$rId"]
                    }
                }
            }], 
            "as":"relatedList",
        }}, 
        {"$project":{
            "name":"$name",
            "relatedList":{
                "$map":{
                    "input":"$relatedList",
                    "as":"x",
                    "in":{
                        "related_id":"$$x.instructionList.related_id",
                        "foo":"$$x.instructionList.foo"
                    }                
                }
            }
        }}
    ]);
    

    得到如下结果:

    {  "_id": ObjectId("5e45cdd8e8d44a31a432a981"),
       "name": "TestProduct",
       "relatedList": [
              {"related_id": "a", "foo": "x"},
              {"related_id": "b", "foo": "y"}
    ]}
    

    以上是在MongoDB v4.2.x中测试的。

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

    这里稍微复杂的原因是因为你有一个数组relatedList,还有一个子文档数组instructionList。当您使用 $eq 运算符引用 instructionList.related_id(可能意味着多个值)时,管道不知道要匹配哪一个。

    在上面的管道中,我添加了$unwind 阶段以将instructionList 转换为多个单个文档。之后,使用$in 表示数组relatedList 中单个值instructionList.related_id 的匹配。

    【讨论】:

    • 感谢您的回答,但它不起作用,问题在于 $relatedList 数组,除非它正在展开,否则无法评估
    • @SquixDev,我注意到您已将 products 集合 relatedList 的 ID 数组更改为带有 id 字段的文档数组。我已更新答案以反映您的架构验证。
    • 再次测试,还是不行。它只是用 InstructionList 的每条指令填充 relatedList,而不检查该指令是否与产品的 relatedList 的相关元素之一有关。 (也许我不清楚我想要实现什么?)
    • @SquixDev,更新了答案以反映您更新后的问题
    【解决方案2】:

    我相信你只需要 $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 的文档
      湾。 $unwind 数组,以便我们可以考虑单个元素
      C。重复 $match,这样我们就只剩下匹配的元素了(希望只有 1 个)
    • $addFields 将从查找中检索到的 foo 字段分配给来自 relatedList 的对象
    • $group 将所有具有相同 _id 的文档(即从单个原始文档展开)收集在一起,将第一个文档存储为“根”,并将所有相关列表元素推回数组中
    • $addFields 将relatedList 移入根目录
    • $replaceRoot 返回root,它现在应该是原始文档,每个relatedList元素都添加了匹配的foo

    【讨论】:

      猜你喜欢
      • 2012-09-28
      • 1970-01-01
      • 2021-10-25
      • 1970-01-01
      • 2022-07-06
      • 2016-06-28
      • 2020-04-09
      • 1970-01-01
      • 2023-02-07
      相关资源
      最近更新 更多