在 Mongodb 中查找和聚合多个级别的子文档

Posted

技术标签:

【中文标题】在 Mongodb 中查找和聚合多个级别的子文档【英文标题】:Lookup and aggregate multiple levels of subdocument in Mongodb 【发布时间】:2020-05-29 17:39:49 【问题描述】:

我已经使用 $lookup、$unwind 和 $match 尝试了许多类似问题的答案,但我无法让它适用于我的 sub-sub-sub-subdocument 情况。

我有这个收藏,东西:


    "_id" : ObjectId("5a7241f7912cfc256468cb27"),
    "name" : "Fortress of Solitude",
    "alias" : "fortress_of_solitude",
,

    "_id" : ObjectId("5a7247ec548c9ad042f579e2"),
    "name" : "Batcave",
    "alias" : "batcave",
,

    "_id" : ObjectId("6a7247bc548c9ad042f579e8"),
    "name" : "Oz",
    "alias" : "oz",
,

还有这个单文档集合,Venues:


    "_id" : ObjectId("5b9acabbbf71f39223f8de6e"),
    "name" : "The Office",
    "floors" : [ 
        
            "name" : "1st Floor",
            "places" : [ 
                
                    "name" : "Front Entrance",
                    "alias" : "front_entrance"
                
            ] 
        , 
        
            "name" : "2nd Floor",
            "places" : [ 
                
                    "name" : "Batcave",
                    "alias" : "batcave"
                ,
                
                    "name" : "Oz",
                    "alias" : "oz"
                
           ]
        
    ]

我想返回所有事物,但如果事物和地点之间的别名匹配,则将地点的 floors.places.name 与每个事物聚合(如果存在)。所以,我想返回:


    "_id" : ObjectId("5a7241f7912cfc256468cb27"),
    "name" : "Fortress of Solitude",
    "alias" : "fortress_of_solitude",
                                 <-- nothing added here because
                                 <-- it's not found in Venues
,

    "_id" : ObjectId("5a7247ec548c9ad042f579e2"),
    "name" : "Batcave",
    "alias" : "batcave",
    "floors" : [                        <-- this should be 
                                       <-- returned 
            "places" : [                <-- because 
                                       <-- the alias
                    name" : "Batcave"   <-- matches
                                       <-- in Venues
            ]                           <-- 
                                       <-- 
    ]                                   <--     
,

    "_id" : ObjectId("6a7247bc548c9ad042f579e8"),
    "name" : "Oz",
    "alias" : "oz",
    "floors" : [                        <-- this should be 
                                       <-- returned 
            "places" : [                <-- because 
                                       <-- the alias
                    name" : "Oz"        <-- matches
                                       <-- in Venues
            ]                           <-- 
                                       <-- 
    ]                                   <--     

我已经了解了以下查询,但它只返回整个 Venues.floors 数组作为每个事物的聚合,这聚合了太多无关的数据。我只想将 Venues 中的每个相关 floor.place 子子文档合并到其对应的 Thing 中(如果它存在于 Venues 中)。

db.getCollection('things').aggregate([
  $lookup: from: "venues",localField: "alias",foreignField: "floors.places.alias",as: "matches",
  
    $replaceRoot:  newRoot:  $mergeObjects: [  $arrayElemAt: [ "$matches", 0 ] , "$$ROOT" ]  
  ,
   $project:  matches: 0    
])

我正在为现有答案苦苦挣扎,这些答案似乎在 MongoDB 版本 3.2、3.4、3.6 或 4.2 中发生了变化,包括或不包括 $unwind、$pipeline 和其他术语。有人可以解释如何获得像这样聚合的子子子文档吗?谢谢!

【问题讨论】:

【参考方案1】:

从 MongoDB v3.6 开始,我们可以执行uncorrelated sub-queries,这让我们可以更灵活地加入两个集合。

试试这个:

db.things.aggregate([
  
    $lookup: 
      from: "venues",
      let: 
        "alias": "$alias"
      ,
      pipeline: [
        
          $unwind: "$floors"
        ,
        
          $project: 
            _id: 0,
            places: 
              $filter: 
                input: "$floors.places",
                cond: 
                  $eq: [
                    "$$alias",
                    "$$this.alias"
                  ]
                
              
            
          
        ,
        
          $match: 
            "places.0": 
              $exists: true
            
          
        ,
        
          $unset: "places.name"
        
      ],
      as: "floors"
    
  
])

MongoPlayground

【讨论】:

谢谢!您的回答使 floor 成为新的根,并且大部分都有效,但它返回每个结果中的所有位置(如果它们都在场地中定义,它会返回 floor.places 中的堡垒和蝙蝠洞),所以我与使用 addFields 和 group 来摆脱无关的地方的答案一起去。不过,您的解决方案显示了其他 userul 位。 (我更新了示例数据,在场地中也列出了要塞,这样你就可以明白我的意思了)。 添加了 Oz,实际上是为了显示无关的地方输出。 @RealHandy 请再试一次,我已经更新了管道条件 看起来你的更新让places[] 完全消失了,所以它只是floors[ name: Batcave ] 而不是floors[ places: [ name: Batcave ] ] @RealHandy 再次检查:)【参考方案2】:

你可以试试这个:

db.things.aggregate([
    
        $lookup:
        
            from: "venues",
            let:  alias: "$alias" ,
            pipeline: [
                 $unwind:  path: "$floors", preserveNullAndEmptyArrays: true  ,
                 $match:  $expr:  $in: ['$$alias', '$floors.places.alias']   ,
                /**  Below stages are only if you've docs like doc 2 in Venues */
                 $addFields:  'floors.places':  $filter:  input: '$floors.places', cond:  $eq: ['$$this.alias', '$$alias']     ,
                 $group:  _id: '$_id', name:  $first: '$name' , floors:  $push: '$floors'   ,
                $project : 'floors.places.alias': 1, _id :0 // Optional
            ],
            as: "matches"
        
    
])

测试: MongoDB-Playground

【讨论】:

我喜欢在@Valijon 答案中使用 replaceRoot 来使 floor 成为附加数据的根,而不是匹配。我的几次添加尝试都没有奏效(我对这些 mongodb 语法很陌生)。不过,除此之外,您的答案确实提供了我所希望的,即删除所有无关的楼层和位置数据。谢谢! @RealHandy :当你说将附加数据的根放在底层而不是匹配时,我有点困惑(你确实将floors 带到了顶层但是如果你有两层楼怎么办?)请针对所有当前问题提出另一个问题并在此处标记它,我们当然可以帮助你.. 让我试着澄清一下(和很多人一样,我希望你能格式化 cmets)。另一个解决方案有 floor[],仍然是一个数组,作为添加到输出中每个事物的子文档。因此,如果说,电梯 1 是一个事物并且存在于场地的两个楼层,那么电梯 1 将有楼层,添加两个元素的阵列。在您的解决方案中,matches[0] 是附加数据的根。只有一个场地,所以matches[]中总是只有一个元素。但是,如果我添加了第二个场地,其中也有一个电梯 1,那么我需要你的 match[] 来显示两个场地。 我只是喜欢从附加输出的根目录中删除matches[],因为在我的场景中只有一个匹配(地点)。 我不确定最终是否需要比名称更多的字段,但我很清楚如何对您的解决方案进行这些编辑。

以上是关于在 Mongodb 中查找和聚合多个级别的子文档的主要内容,如果未能解决你的问题,请参考以下文章

带有大量文档的 MongoDb 聚合查找?

带有大量文档的 MongoDb 聚合查找?

mongodb Aggregation聚合操作之$facet

MongoDB,如何将查找和排序与聚合中的 $cond 结合起来?

MongoDB,如何将查找和排序与聚合中的 $cond 结合起来?

$在mongodb中查找多个级别