具有嵌套对象数组的 MongoDB 聚合

Posted

技术标签:

【中文标题】具有嵌套对象数组的 MongoDB 聚合【英文标题】:MongoDB aggregation with nested arrays of objects 【发布时间】:2020-04-23 14:35:50 【问题描述】:

我正在努力从 Mongo DB 中获取一些汇总数据。我有以下收藏:

餐厅:


        "_id" : ObjectId("5e0ff6d424f9fc12bc3d9464"),
        "name" : "Pizzaria Don Juan",
        "active" : true,
        "users" : [
                
                        "_id" : ObjectId("5e10fc2adc147a373c312144")
                ,
                
                        "_id" : ObjectId("5e11ff8003eb832ef84342a6")
                
        ],
        "socialMedias" : [
                
                        "_id" : ObjectId("5e1008943330ad05d4e1867c"),
                        "url" : "https://instagram/jetpizzas"
                ,
                
                        "_id" : ObjectId("5e10089a3330ad05d4e1867d"),
                        "url" : "https://facebook.com/jetpizzas"
                
        ],
        "branches" : [
                
                        "name" : "Teste"
                ,
                
                        "name" : "Teste 2"
                
        ],
        "sections" : [
                
                        "name" : "Bebidas"
                
        ],
        "__v" : 0


        "_id" : ObjectId("5e0ffd23991918424c8d7c3b"),
        "name" : "Pizza Ruth",
        "active" : true,
        "users" : [ ],
        "socialMedias" : [ ],
        "branches" : [ ],
        "sections" : [ ],
        "__v" : 0


        "_id" : ObjectId("5e0ffd3d991918424c8d7c3c"),
        "name" : "Feijão de Corda",
        "active" : true,
        "users" : [ ],
        "socialMedias" : [ ],
        "branches" : [ ],
        "sections" : [ ],
        "__v" : 0

用户


        "_id" : ObjectId("5e10fc2adc147a373c312144"),
        "isExpired" : false,
        "isBlocked" : false,
        "loginTentatives" : 0,
        "profile" : 2,
        "active" : true,
        "username" : "contato@pizzariadonjuan.com.br",
        "password" : "$2a$10$xhmw83QXbMvSqmrKAUYn.O4fOxboEyVkVB0DGkSsJUOp7K4bYQkCm",
        "email" : "",
        "phone" : "",
        "createdAt" : ISODate("2020-01-04T20:57:14.634Z"),
        "__v" : 0


        "_id" : ObjectId("5e11ff8003eb832ef84342a6"),
        "isExpired" : false,
        "isBlocked" : false,
        "loginTentatives" : 0,
        "profile" : 2,
        "active" : true,
        "username" : "sac@pizzariadonjuan.com.br",
        "password" : "$2a$10$wby3cs89jyO0HUbEiGLKye0jOB3U295zzIsu8xGJ4wnQtw5jcvSZO",
        "email" : "",
        "phone" : "",
        "createdAt" : ISODate("2020-01-05T15:23:44.386Z"),
        "__v" : 0


        "_id" : ObjectId("5e11ff9c03eb832ef84342a7"),
        "isExpired" : false,
        "isBlocked" : false,
        "loginTentatives" : 0,
        "profile" : 2,
        "active" : true,
        "username" : "juan@pizzariadonjuan.com.br",
        "password" : "$2a$10$nEM3RxEjYbI77R9vOWUrMOGeHFDmdZqVKUNtTLuKZVLNQBQqIbew.",
        "email" : "",
        "phone" : "",
        "createdAt" : ISODate("2020-01-05T15:24:12.456Z"),
        "__v" : 0

个人资料


        "_id" : ObjectId("5e0ea5f6832df0473cacacda"),
        "number" : 1,
        "name" : "Cliente",
        "__v" : 0


        "_id" : ObjectId("5e0ea5ff832df0473cacacdb"),
        "number" : 2,
        "name" : "Restaurante",
        "__v" : 0


        "_id" : ObjectId("5e0ea607832df0473cacacdc"),
        "number" : 0,
        "name" : "Admin",
        "__v" : 0

和社交媒体:


        "_id" : ObjectId("5e1008943330ad05d4e1867c"),
        "name" : "Instagram",
        "__v" : 0


        "_id" : ObjectId("5e10089a3330ad05d4e1867d"),
        "name" : "Facebook",
        "__v" : 0


        "_id" : ObjectId("5e1009043330ad05d4e1867f"),
        "name" : "LinkedIn",
        "__v" : 0

我的目标是获取与餐厅对象相关的所有对象。使用以下代码:

db.restaurants.aggregate([
 $lookup:  from: "users", localField: "users._id", foreignField: "_id", as: "foundUsers"  , 
$group:  
'_id': '$_id', 
'name':  "$first": "$name" , 
'active':  "$first": "$active" , 
users:  $push: '$foundUsers' , 
branches:  "$first": "$branches" , 
sections:  "$first": "$sections" ,
socialMedias:  "$first": "$socialMedias" 

,
$unwind: '$users',
 $unset: 'users.password' ,
 $lookup:  from: "profiles", localField: "users.profile", foreignField: "number", as: "profile"  ,
 $addFields:  'users.profile':  $arrayElemAt: ['$profile', 0]   ,
 $unset: 'profile' ,
 

 $lookup:  from: "socialmedias", localField: "socialMedias._id", foreignField: "_id", as: "socialMedia"  ,
 $addFields:  'socialMedias.name':  $arrayElemAt: ['$socialMedia.name', 0]   ,
$group:  
'_id': '$_id', 
'name':  "$first": "$name" , 
'active':  "$first": "$active" , 
users:  $first: '$users' , 
branches:  "$first": "$branches" , 
sections:  "$first": "$sections" ,
socialMedias:  "$first": "$socialMedias" 


])

我明白了:

    
        "_id" : ObjectId("5e0ffd3d991918424c8d7c3c"),
        "name" : "Feijão de Corda",
        "active" : true,
        "users" : [ ],
        "branches" : [ ],
        "sections" : [ ],
        "socialMedias" : [ ]


        "_id" : ObjectId("5e0ffd23991918424c8d7c3b"),
        "name" : "Pizza Ruth",
        "active" : true,
        "users" : [ ],
        "branches" : [ ],
        "sections" : [ ],
        "socialMedias" : [ ]


        "_id" : ObjectId("5e0ff6d424f9fc12bc3d9464"),
        "name" : "Pizzaria Don Juan",
        "active" : true,
        "users" : [
                
                        "_id" : ObjectId("5e10fc2adc147a373c312144"),
                        "isExpired" : false,
                        "isBlocked" : false,
                        "loginTentatives" : 0,
                        "profile" : 
                                "_id" : ObjectId("5e0ea5ff832df0473cacacdb"),
                                "number" : 2,
                                "name" : "Restaurante",
                                "__v" : 0
                        ,
                        "active" : true,
                        "username" : "contato@pizzariadonjuan.com.br",
                        "email" : "",
                        "phone" : "",
                        "createdAt" : ISODate("2020-01-04T20:57:14.634Z"),
                        "__v" : 0
                ,
                
                        "_id" : ObjectId("5e11ff8003eb832ef84342a6"),
                        "isExpired" : false,
                        "isBlocked" : false,
                        "loginTentatives" : 0,
                        "profile" : 
                                "_id" : ObjectId("5e0ea5ff832df0473cacacdb"),
                                "number" : 2,
                                "name" : "Restaurante",
                                "__v" : 0
                        ,
                        "active" : true,
                        "username" : "sac@pizzariadonjuan.com.br",
                        "email" : "",
                        "phone" : "",
                        "createdAt" : ISODate("2020-01-05T15:23:44.386Z"),
                        "__v" : 0
                
        ],
        "branches" : [
                
                        "name" : "Teste"
                ,
                
                        "name" : "Teste 2"
                
        ],
        "sections" : [
                
                        "name" : "Bebidas"
                
        ],
        "socialMedias" : [
                
                        "_id" : ObjectId("5e1008943330ad05d4e1867c"),
                        "url" : "https://instagram/jetpizzas",
                        "name" : "Instagram"
                ,
                
                        "_id" : ObjectId("5e10089a3330ad05d4e1867d"),
                        "url" : "https://facebook.com/jetpizzas",
                        "name" : "Instagram"
                
        ]

请注意,嵌套数组 socialMedias 的社交媒体名称值错误(重复的“Instagram”名称,它应该是 Instagram 的一条记录和 Facebook 的另一条记录)。即使我尝试从餐厅集合中展开 socialMedias 数组,它也只返回具有社交媒体值的餐厅对象。

有什么线索可以解决这个问题吗?

【问题讨论】:

【参考方案1】:

您将$lookup 结果与现有数组合并的方式是这里的一个问题。你不能跑:

 $addFields:  'socialMedias.name':  $arrayElemAt: ['$socialMedia.name', 0]   ,

因为你总是会得到第一个数组元素。您需要使用 $map 、 $filter 和 $mergeObjects 合并两个数组:


    $addFields: 
        socialmedias: 
            $map: 
                input: "$socialMedias",
                as: "sm",
                in: 
                    $mergeObjects: [
                        "$$this",
                        
                            $arrayElemAt: [  $filter:  input: "$socialmedias", cond:  $eq: [ "$$sm.number", "$$this._id" ]   , 0 ]
                        
                    ]
                
            
        
    

您还需要将其应用于user.profile,因为当前的解决方案容易出错。

Mongo Playground

【讨论】:

【参考方案2】:

非常感谢您的帮助。我试过这个查询:

db.restaurants.aggregate([
 $lookup:  from: "users", localField: "users._id", foreignField: "_id", as: "foundUsers"  , 
$group:  
'_id': '$_id', 
'name':  "$first": "$name" , 
'active':  "$first": "$active" , 
users:  $push: '$foundUsers' , 
branches:  "$first": "$branches" , 
sections:  "$first": "$sections" ,
socialMedias:  "$first": "$socialMedias" 

,
$unwind: '$users',
 $unset: 'users.password' ,
 $lookup:  from: "profiles", localField: "users.profile", foreignField: "number", as: "profile"  ,
 $addFields:  'users.profile':  $arrayElemAt: ['$profile', 0]   ,
 $unset: 'profile' ,


 $lookup:  from: "socialmedias", localField: "socialMedias._id", foreignField: "_id", as: "foundSocialMedia"  ,

    $addFields: 
      socialMedias: 
        $map: 
          input: "$socialMedias",
          as: "sm",
          in: 
            $mergeObjects: [
              "$$sm",
              
                $arrayElemAt: [
                  
                    $filter: 
                      input: "$foundSocialMedia",
                      cond: 
                        $eq: [
                          "$$sm._id",
                          "$$this._id"
                        ]
                      
                    
                  ,
                  0
                ]
              
            ]
          
        
      
    
  ,
 $unset: 'foundSocialMedia' ,
])

我得到了这个想要的结果:


        "_id" : ObjectId("5e0ffd3d991918424c8d7c3c"),
        "name" : "Feijão de Corda",
        "active" : true,
        "users" : [ ],
        "branches" : [ ],
        "sections" : [ ],
        "socialMedias" : [ ]


        "_id" : ObjectId("5e0ffd23991918424c8d7c3b"),
        "name" : "Pizza Ruth",
        "active" : true,
        "users" : [ ],
        "branches" : [ ],
        "sections" : [ ],
        "socialMedias" : [ ]


        "_id" : ObjectId("5e0ff6d424f9fc12bc3d9464"),
        "name" : "Pizzaria Don Juan",
        "active" : true,
        "users" : [
                
                        "_id" : ObjectId("5e10fc2adc147a373c312144"),
                        "isExpired" : false,
                        "isBlocked" : false,
                        "loginTentatives" : 0,
                        "profile" : 
                                "_id" : ObjectId("5e0ea5ff832df0473cacacdb"),
                                "number" : 2,
                                "name" : "Restaurante",
                                "__v" : 0
                        ,
                        "active" : true,
                        "username" : "contato@pizzariadonjuan.com.br",
                        "email" : "",
                        "phone" : "",
                        "createdAt" : ISODate("2020-01-04T20:57:14.634Z"),
                        "__v" : 0
                ,
                
                        "_id" : ObjectId("5e11ff8003eb832ef84342a6"),
                        "isExpired" : false,
                        "isBlocked" : false,
                        "loginTentatives" : 0,
                        "profile" : 
                                "_id" : ObjectId("5e0ea5ff832df0473cacacdb"),
                                "number" : 2,
                                "name" : "Restaurante",
                                "__v" : 0
                        ,
                        "active" : true,
                        "username" : "sac@pizzariadonjuan.com.br",
                        "email" : "",
                        "phone" : "",
                        "createdAt" : ISODate("2020-01-05T15:23:44.386Z"),
                        "__v" : 0
                
        ],
        "branches" : [
                
                        "name" : "Teste"
                ,
                
                        "name" : "Teste 2"
                
        ],
        "sections" : [
                
                        "name" : "Bebidas"
                
        ],
        "socialMedias" : [
                
                        "_id" : ObjectId("5e1008943330ad05d4e1867c"),
                        "url" : "https://instagram/jetpizzas",
                        "name" : "Instagram",
                        "__v" : 0
                ,
                
                        "_id" : ObjectId("5e10089a3330ad05d4e1867d"),
                        "url" : "https://facebook.com/jetpizzas",
                        "name" : "Facebook",
                        "__v" : 0
                
        ]

【讨论】:

以上是关于具有嵌套对象数组的 MongoDB 聚合的主要内容,如果未能解决你的问题,请参考以下文章

mongoDB对嵌套对象数组的聚合查找

MongoDB 聚合与嵌套的对象属性数组与日期

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

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

mongodb $查找带有投影的数组中的嵌套对象

如何在 MongoDB 中推入具有精确键值的对象嵌套数组?