MongoDB 聚合 $lookup 到具有管道语法和 _id 作为字符串的父数组字段

Posted

技术标签:

【中文标题】MongoDB 聚合 $lookup 到具有管道语法和 _id 作为字符串的父数组字段【英文标题】:MongoDB aggregation $lookup to parent array field with pipeline syntax and _id as string 【发布时间】:2021-08-05 05:25:44 【问题描述】:

我的 MongoDB (v.4.4) 中有以下集合:

guilds:


 _id: String,
 members: [ _id String, rank: number , _id String, rank: number ...]

characters:


  _id: String,
  many_other: 'fields'

我想通过_id 字段加入$lookup 管道语法guild.members <= characters

我不使用 Mongo 内置 OjbectIds 作为 _id,我用两个集合中的字符串覆盖它。正如我所听到的,$lookup 与管道中的字符串为_id 有一些不同的行为。所以在回答之前,请确保你知道它。

我想要的预期结果很简单,我想从原始文档中保存排名,并从$lookup它添加任何其他字段:


  _id: String // (guild)
  members: [
    
      _id: String, // (characters)
      rank: number,
      many_other: '...fields from characters'
    ,
    
      _id: String, // (characters)
      rank: number,
      many_other: '...fields from characters'
    , ...
]

Mongo Playground example: avaliable

我尝试了什么:

各种查询,例如:

      
        $lookup: 
          from: "characters",
          pipeline: [
            
              $match: 
                $expr:  $eq: [  $toString :"$members._id" ,  $toString : "$$character_id"  ] 
              
            
          ],
          as: "guild_members"
        ,
      

将 ID 转换为字符串,使用 let 阶段变量,并使用 $map 运算符。但我离要求的结果还很远。

还有一个问题,就是和问题有些相关,但和查询本身无关

如您所见,characters 集合具有many other fields。其中一些非常重,(因为公会最多可以有 500 个成员)这使得结果文档 >16 MB(MongoDB 阈值限制),这会产生错误。因为据我所知,我们无法从joined 文档中选择字段,所以最好在$lookup 阶段或类似的东西之后立即排除它。任何关于它的建议都会非常受欢迎。

【问题讨论】:

【参考方案1】:

不需要使用管道查找,当您将 members._id 作为 localField 传递时,

$lookup 带字符并将 members._id 作为 localField 传递 $map 迭代 members 数组的循环 $filter 迭代 members_guid 的循环并获取匹配的成员 $arrayElemAt 从上面的过滤器中获取第一个匹配元素 $mergeObjects 将当前字段与上面过滤的成员对象合并
db.guilds.aggregate([
  
    $lookup: 
      from: "characters",
      localField: "members._id",
      foreignField: "_id",
      as: "members_guid"
    
  ,
  
    $project: 
      members: 
        $map: 
          input: "$members",
          as: "m",
          in: 
            $mergeObjects: [
              "$$m",
              
                $arrayElemAt: [
                  
                    $filter: 
                      input: "$members_guid",
                      cond:  $eq: ["$$this._id", "$$m._id"] 
                    
                  ,
                  0
                ]
              
            ]
          
        
      
    
  
])

Playground

【讨论】:

查询没问题,我也有一个,问题有点大。在 $lookup 阶段之前或之后,要从加入的文档 (characters) 中删除/排除某些字段是什么?例如 (characters.pets)。在这种情况下,我可以在$project 阶段,在$map 中简单地删除它们。我说的对吗? 可以,但最好在添加额外的项目阶段删除,请参阅playground【参考方案2】:

我接受@turivishal 的答案,因为旧式原生聚合比pipeline 语法更容易理解。但是如果有人感兴趣,我之前使用过的聚合的旧部分。

db.guilds.aggregate([
  
    $lookup: 
      from: "characters",
      let: 
        members: "$members"
      ,
      pipeline: [
        
          $match: 
            $expr: 
              $in: [
                "$_id",
                "$$members._id"
              ]
            
          
        ,
        
          $addFields: 
            rank: 
              $reduce: 
                input: "$$members",
                initialValue: null,
                in: 
                  $cond: [
                    
                      $eq: [
                        "$$this._id",
                        "$_id"
                      ]
                    ,
                    "$$this.rank",
                    "$$value"
                  ]
                
              
            
          
        
      ],
      as: "members"
    ,
    
  
])

【讨论】:

以上是关于MongoDB 聚合 $lookup 到具有管道语法和 _id 作为字符串的父数组字段的主要内容,如果未能解决你的问题,请参考以下文章

MongoDB——聚合管道之$lookup操作

MongoDB——聚合管道之$lookup操作

基于通过 $lookup 检索的字段的多阶段聚合管道匹配数据

MongoDB在具有附加字段的对象数组上聚合$lookup

MongoDB在具有附加字段的对象数组上聚合$lookup

如何使用 MongoDB 聚合 `$lookup` 作为 `findOne()`