我可以在猫鼬聚合之前使用填充吗?

Posted

技术标签:

【中文标题】我可以在猫鼬聚合之前使用填充吗?【英文标题】:Can I use populate before aggregate in mongoose? 【发布时间】:2016-05-09 06:22:45 【问题描述】:

我有两个模型,一个是用户

 userSchema = new Schema(
     userID: String,
     age: Number
 );

另一个是所有用户每天记录几次的分数

 ScoreSchema = new Schema(
     userID: type: String, ref: 'User',
     score: Number,
     created_date = Date,
     ....
 )

我想对一些满足特定要求的用户的分数进行一些查询/计算,比如我想计算所有用户的平均分数超过 20 天。

我的想法是,首先在 Scores 上执行 populate 以填充用户的年龄,然后再执行 aggregate

有点像

Score.
    populate('userID','age').
    aggregate([
        $match: 'userID.age': $gt: 20,
        $group: ...,
        $group: ...
    ], function(err, data));

在聚合之前使用填充可以吗?还是我先找到所有符合要求的userID,保存在一个数组中,然后用$in来匹配分数文档?

【问题讨论】:

【参考方案1】:

不,你不能在.aggregate() 之前调用.populate(),你不能这样做是有充分理由的。但是您可以采取不同的方法。

.populate() 方法在“客户端”工作,其中底层代码实际执行附加查询(或更准确地说是 $in 查询)以从引用的集合中“查找”指定元素。

相比之下,.aggregate() 是“服务器端”操作,因此您基本上不能在“客户端”操作内容,然后将这些数据提供给稍后的聚合管道阶段。这一切都需要存在于您正在操作的集合中。

MongoDB 3.2 及更高版本可通过$lookup 聚合管道操作获得更好的方法。在这种情况下,最好从User 集合中处理以缩小选择范围:

User.aggregate(
    [
        // Filter first
         "$match": 
            "age":  "$gt": 20  
        ,
        // Then join
         "$lookup": 
            "from": "scores",
            "localField": "userID",
            "foriegnField": "userID",
            "as": "score"
        ,
        // More stages
    ],
    function(err,results) 

    
)

这基本上将在User 对象中包含一个新字段“score”,作为在“查找”时与其他集合匹配的项目“数组”:


    "userID": "abc",
    "age": 21,
    "score": [
        "userID": "abc",
        "score": 42,
        // other fields
    ]

结果始终是一个数组,因为一般预期的用法是可能的“一对多”关系的“左连接”。如果没有结果匹配,那么它只是一个空数组。

要使用内容,只需以任何方式使用数组即可。例如,您可以使用$arrayElemAt 运算符,以便在以后的任何操作中只获取数组的单个第一个元素。然后你可以像使用任何普通的嵌入字段一样使用内容:

         "$project": 
            "userID": 1,
            "age": 1,
            "score":  "$arrayElemAt": [ "$score", 0 ] 
        

如果您没有可用的 MongoDB 3.2,那么处理受另一个集合的关系限制的查询的另一个选择是首先从该集合中获取结果,然后使用 $in 过滤第二个:

// Match the user collection
User.find( "age":  "$gt": 20  ,function(err,users) 

    // Get id list      
    userList = users.map(function(user) 
       return user.userID;
    );

    Score.aggregate(
        [ 
            // use the id list to select items
             "$match": 
                "userId":  "$in": userList 
            ,
            // more stages
        ],
        function(err,results) 

        
    );

);

因此,通过从另一个集合中获取有效用户列表到客户端,然后在查询中将其提供给另一个集合,这是在早期版本中实现这一点的唯一方法。

【讨论】:

如果 userList 是 objectId 的数组,你必须将它们转换为字符串 你是个聪明人,但我认为foriegnField 应该是foreignField。没什么大不了的

以上是关于我可以在猫鼬聚合之前使用填充吗?的主要内容,如果未能解决你的问题,请参考以下文章

如何仅在猫鼬中使用聚合填充嵌套在对象数组中的字段?

如何在猫鼬中使用聚合

如何在猫鼬聚合中使用字段值? (节点)

在猫鼬聚合框架中按日期排序

如何使用聚合在猫鼬中对文档数组进行分页?

您可以在猫鼬中执行多个嵌套填充吗?