我可以在mongoose聚合之前使用populate吗?

时间:2016-01-31 21:29:37

标签: mongodb mongoose mongodb-query aggregation-framework

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

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

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

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

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

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

这样的东西
Score.
    populate('userID','age').
    aggregate([
        {$match: {'userID.age': {$gt: 20}}},
        {$group: ...},
        {$group: ...}
    ], function(err, data){});

在聚合之前使用populate是否可以?或者我首先找到满足要求的所有userID并将它们保存在一个数组中,然后使用$ in来匹配得分文档?

1 个答案:

答案 0 :(得分:11)

不,你不能在.populate()之前致电.aggregate(),并且有一个很好的理由说明你不能。但是你可以采取不同的方法。

.populate()方法适用于"客户端"其中底层代码实际执行其他查询(或更准确地说是$in查询)到"查找"来自引用集合的指定元素。

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

通过$lookup聚合管道操作,MongoDB 3.2及更高版本可以使用更好的方法。在这种情况下,也可能最好从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对象中作为"数组"匹配"查找"到另一个集合:

{
    "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) {

        }
    );

});

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