将$lookup的结果合并到现有数组中

8

我是mongo的新手,需要您的帮助。

我有一个名为studijneProgramy的集合。这是一个示例文档:

{
    "_id" : "dGFY",
    "garranti" : [
        {
            "typ" : {
                "sk" : "garant",
                "en" : "Chairman of study board"
            },
            "id" : "1025769"
        },
        {
            "typ" : {
                "sk" : "predseda odborovej komisie",
                "en" : "Chairman of study board"
            },
            "id" : "1025769"
        }
    ]
}

接下来我有一个名为osoby的集合。
示例文档:

{
    "_id" : "1025769",
    "plneMeno" : "prof. RNDr. Peter Moczo, DrSc.",
    "priezvisko" : "Moczo",
    "meno" : "Peter",
    "jeGarantProgramu" : "dGFY/x"
}

我需要的是将来自Osoby的文档添加到数组Garranti中相应的文档中(其中studijneProgramy.garanti.id == osoby._id)。这是我期望的结果:
{
    "_id" : "dGFY",
    "garranti" : [
        {
            "typ" : {
                "sk" : "garant",
                "en" : "Chairman of study board"
            },
            "id" : "1025769"
            "garant":{
                "_id" : "1025769",
                "plneMeno" : "prof. RNDr. Peter Moczo, DrSc.",
                "priezvisko" : "Moczo",
                "meno" : "Peter",
                "jeGarantProgramu" : "dGFY/x"
            }
        },
        {
            "typ" : {
                "sk" : "predseda odborovej komisie",
                "en" : "Chairman of study board"
            },
            "id" : "1025769"
            "garant":{
                "_id" : "1025769",
                "plneMeno" : "prof. RNDr. Peter Moczo, DrSc.",
                "priezvisko" : "Moczo",
                "meno" : "Peter",
                "jeGarantProgramu" : "dGFY/x"
            }
        }
    ]
}

我尝试了这个聚合,但它替换了garranti的内容。

db.studijneProgramy.aggregate([
{
    $lookup:
    {
        from:"osoby", 
        localField:"garranti.id",
        foreignField:"_id", 
        as:"garranti.garant"
    }
 }
]
).pretty()

任何帮助都将不胜感激!

可能是重复的问题:https://dev59.com/8FsW5IYBdhLWcg3wdW6T - dege
@dege 有点是的!但那里提出的实际问题已经得到解决,尽管所呈现的“解决方案”只是今天解决这个特定变体问题的一种可能方式。还有“其他”方法。 - Neil Lunn
1个回答

23

MongoDB的$lookup操作不会用"lookup"集合中的匹配结果来“更新”现有数组中的元素。它将仅输出与给定条件匹配的“数组”元素,无论是与您拥有的“现有数组”中的值匹配还是与单个值匹配。

为了使用“服务器”$lookup操作来“匹配”条目,您必须使用以下其中一种选项之一才能以所需的形式返回结果。

首先对数组进行$unwind操作

最简单的方法是更改文档的结构,使源中的每个数组成员成为其自己的文档部分,然后再尝试“匹配”相关信息:

db.studijneProgramy.aggregate([
  { "$unwind": "$garranti" },
  { "$lookup": {
    "from": "osoby",
    "as": "garranti.garrant",
    "localField": "garranti.id",
    "foreignField": "_id"
  }},
  { "$unwind": "$garranti.garrant" },
  { "$group": {
    "_id": "$_id",
    "garranti": { "$push": "$garranti" }
  }}
])

由于原始数组现在是单独的文档,因此每个文档只会从连接的集合中接收到匹配的"数组"。这将再次使用$unwind,最后使用$group,以 "joined" 条目形式使用$push到最终的数组形式。

关联 "数组"

如果版本支持,可以使用$indexOfArray$arrayElemAt函数,将 $lookup 的输出数组与文档中现有的数组条目"匹配"起来:

db.studijneProgramy.aggregate([
  { "$lookup": {
    "from": "osoby",
    "as": "related",
    "localField": "garranti.id",
    "foreignField": "_id"
  }},
  { "$project": {
    "garranti": {
      "$map": {
        "input": "$garranti",
        "in": {
          "typ": "$$this.typ",
          "id": "$$this.id",
          "garrant": {
            "$arrayElemAt": [
              "$related",
              { "$indexOfArray": [ "$related._id", "$$this.id" ] }
            ]
          }
        }
      }
    }
  }}
])

因此,查找返回“匹配项数组”(related),您可以通过$map将其匹配条目进行转置,以便在原始文档数组中重新塑造文档结果,因为您无法像前面提到的那样“定位”现有数组的每个元素。$lookup输出。当然,这需要一个额外的$project阶段或类似阶段来重新塑造文档结果。

实际上,这是“服务器”上某些库(如“mongoose”)为“客户端上的联接仿真”所做的直接相关操作。有效地,“外部”条目被“映射”到现有数组上。

子管道处理

另一种更加花哨和冗长的选择是使用MongoDB 3.6及以上版本可用的未关联子查询的“子管道”处理。在这里,我们基本上在$lookup的“子管道”中进行操作,而不是在后续聚合阶段中进行处理:

db.studijneProgramy.aggregate([
  { "$lookup": {
    "from": "osoby",
    "as": "garranti",
    "let": { "garranti": "$garranti" },
    "pipeline": [
      { "$match": {
        "$expr": { "$in": [ "$_id", "$$garranti.id" ] } 
      }},
      { "$addFields": {
        "docs": {
          "$filter": {
            "input": "$$garranti",
            "cond": {
              "$eq": [ "$$this.id", "$_id" ]
            }
          }
        }
      }},
      { "$unwind": "$docs" },
      { "$replaceRoot": {
        "newRoot": {
          "$mergeObjects": [
            "$docs",
            { "garrant": {
              "$arrayToObject": {
                "$filter": { 
                  "input": { "$objectToArray": "$$ROOT" },
                  "cond": { "$ne": [ "$$this.k", "docs"] }
                }
              }
            }}
          ]
        }
      }}
    ]
  }}
])

这种方法实际上将“源文档”中的“匹配的数组元素”作为数组放置在每个匹配的外部元素中,从而将操作“颠倒过来”。

然后,处理有效地在过滤后的源列表上使用$unwind,然后合并来自外部集合的内容,因此现在似乎$lookup“输出数组”实际上是来自“本地数组”的数据,现在与“外部内容”合并。

实际上,它只是对上面相同的$map过程的更高级调用,但在结果与原始父文档合并并覆盖原始数组属性之前进行条目的“相关”。


我认为JIRA上有一个类似的问题,但我觉得所有这样的报告都标记为“按设计工作”,因此它不太可能从当前状态发生变化。

所以你误解的是“join”会自动“合并”数组条目。 它不会。

如果您确实想要“合并数组输出”,则上述方法是进行此操作的“服务器”方法。


非常感谢。那帮了我很多忙。 我尝试使用$unwind的第一种解决方案,它运行得非常顺畅。 - Lukáš Slaninka
最简单的形式(示例1)无法处理空数组,它会在数组内部给出一个空对象的值,而最后一个则非常好用。您能否就相同情况下的性能发表评论?如果针对大型数据集,这将是非常有帮助的。 - Nitish Makhija
只有爱才能触发 <3。 - talha_ah

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接