Mongoose - 如何聚合两个集合中的内容

Posted

技术标签:

【中文标题】Mongoose - 如何聚合两个集合中的内容【英文标题】:Mongoose - How to aggregate contents from two collections 【发布时间】:2019-11-10 13:38:02 【问题描述】:

我该如何使用 Mongoose 聚合操作呢? 基本上,我有两个集合——“帖子”和“个人资料”。 一个名为“user”的用户 oid 是它们之间的共同引用。 我想使用“Post”中的 ID 从“Profile”中查找“handle”元素并汇总这些结果,以便将用户的句柄包含在响应中。

以下是我在查找单个帖子时能够做到这一点的方法:

// @route   GET api/posts/:id
// @desc    Get post by id
// @access  Public
router.get("/:id", (req, res) => 
  Post.findById(req.params.id)
    .then(post => 
      Profile.findOne( user: post.user )
        .then(profile => res.json( ...post._doc, userHandle: profile.handle ));
    )
    .catch(err => res.status(404).json( "not found": err ));
);

但是,当我尝试将这种方法(使用映射操作)扩展到返回所有帖子的 GET 时,它变得不可行。

// @route   GET api/posts
// @desc    Get all the posts
// @access  Public
router.get("/", (req, res) => 
  Post.find()
    .sort( date: -1 )
    .then(posts =>
      posts.map(
        post =>
          Profile.findOne( user: post.user )
            .then(profile => (
              
                ...JSON.parse(JSON.stringify(post)),
                userHandle: profile.handle
              ))
      ))
    .then(posts => res.json(posts))
    .catch(err => res.status(404).json( "none found": err ));
);

换句话说,如果我有 发布:


    "_id": 
        "$oid": "5d1523b98d9dd16d832a8c5e"
    ,
    "user": 
        "$oid": "5d1504e29dc0bd55461adca7"
    ,
    "recipeName": "Pizza",
    "ingredients": "Flour and sauch"

简介:


    "_id": 
        "$oid": "5d1505089dc0bd55461adca8"
    ,
    "user": 
        "$oid": "5d1504e29dc0bd55461adca7"
    ,
    "handle": "jack",
    "status": "Developer"

那么我真正想要的是这样的 JSON 对象(当我查询帖子时): (注意“userHandle”现在存在)


    "_id": 
        "$oid": "5d1523b98d9dd16d832a8c5e"
    ,
    "user": 
        "$oid": "5d1504e29dc0bd55461adca7"
    ,
    "recipeName": "Pizza",
    "ingredients": "Flour and sauch",
    "userHandle": "jack"

我的 GET 需要返回这些帖子的整个数组。

【问题讨论】:

【参考方案1】:
Post.find().sort(date: -1).then((posts) => 
  Post.populate(posts, [
    path: 'user',
    select: , // projection
    model: User // User model
  ], (err, posts) => 
    if (err) return next(err);
    return res.json(posts);
  );
);

阅读更多关于猫鼬populate

编辑

如果不想每次都包含model,可以在Schema字段中添加ref(集合名称),它会自动指定型号!

示例:

const postSchema = Schema(
  ...
  user:  type: Schema.Types.ObjectId, ref: 'Users' 
);

【讨论】:

这仍然返回具有相同数量键的“Post” - 仍然不包括 user.handle 所以你只想返回post.user 我想用 'user' 的句柄'post'。我会在上面添加示例。【参考方案2】:
getUserDetails: (request, response, next) => 
        try 
            let aggrQuery = [
                
                    $lookup: 
                        from: "Profiles",
                        localField: "Id",//key name of common id in post collection as a reference
                        foreignField: "profileId",// key name of common id in profile collection
                        as: "userDetails"// handle element as well as other data in profile collection will be stored in userDetails
                    
                ,
                
                    "$project": 
                       //we have to carry "_id", "ingredients","recipeName","user" of post collection for the next projection,and whole user information is stored in "userDetails"
                        "_id": 1,
                        "ingredients":1,
                        "recipeName":1,
                        "user":1,
                        userDetails :  $arrayElemAt : 
                      ["$userDetails", 0]
                    
                ,
                
                    "$project": 
                        //finally we prepare required data set for response,"_id", "ingredients","recipeName","user" of post collection,and only userHandle of Profiles from userDetails array(which contains all profile data)
                        "_id": "$_id",
                        "ingredients":"$ingredients",
                        "recipeName":"$recipeName",
                        "user":"$user",
                        "userDetails":
                           "userHandle" :"$userDetails.userHandle",
                           "additionField1":"$userDetails.additionField1",
                           "additionField2":"$userDetails.additionField2",
                              
                    
                
            ]
            Post.aggregate(aggrQuery).exec((err, result) => 
                if(err)
                  response.json(
                      isError: true,
                      statuscode: 404,
                      details: null
                  )
                
                else
                   response.json(
                    isError: false,
                    statuscode: 200,
                    details: result
                  )
                
            )
        
        catch
            response.json(
                isError: true,
                statuscode: 404,
                details: null
            );
        
    ,

【讨论】:

我认为这是我想要的,但由于某种原因,“userDetails”只是作为一个空数组返回:/ 我确实通过将“来自:“配置文件””更改为“来自:“配置文件””来实现此功能,但是如何专门获取用户句柄而不是将整个配置文件添加为“userDetails” " ? 有没有办法做到这一点,而无需专门命名帖子中的字段?我认为将来可能会添加其他字段,如果有一种方法可以将它们全部包含在内而不在投影中命名它们会更容易。 据我所知,目标集合的所有数据在查找后都存储在一个数组中。下一步我们必须从数组中提取必要的数据以进行下一次投影或下一次查找逻辑如果需要。但是我们可以做的一件事是,假设我们从用户详细信息中获取 50 个数据并将所有数据推送到项目内部的一个数组中,这样我们就可以将新创建​​的数组用于下一次操作,而不是再次声明 50 个数据并且再次。请分享您对此的看法。 我不认为我理解你的建议。

以上是关于Mongoose - 如何聚合两个集合中的内容的主要内容,如果未能解决你的问题,请参考以下文章

$match mongodb聚合框架_mongoose中的多条件[重复]

Mongoose聚合从另一个集合中获取多条记录中的喜欢总和

Mongoose 聚合查找 - 如何按特定 id 过滤

匹配 Mongoose 中的两个不同字段,聚合?

聚合函数在页面刷新时复制 ng-repeat 中的项目。需要弄清楚如何停止重复。 Angularjs Mongodb mongoose

Mongoose:如何使用两个字段对聚合响应进行排序