Mongoose JS:derived/calculated 值的最佳方法

MongoosJS: Best approach for a derived/calculated value

我正在为我的家人开发一个大学橄榄球博彩应用程序。 这是我的模式:

const GameSchema = new mongoose.Schema({
    home: {
        type: String,
        required: true
    },
    opponent: {
       type: String,
       required: true
    },
    homeScore: Number,
    opponentScore: Number,
    week:{ 
       type: Number,
       required: true
    },
    winner: String,
    userPicks: [
        {
            user: {
                type: mongoose.Schema.Types.ObjectId,
                ref: 'User'
            },
            choosenTeam: String
        }
    ]
}); 

const UserSchema = new mongoose.Schema({
    name: String
});

我需要能够计算每个用户的每周得分(即他们每周正确预测的足球比赛数量)和他们的累积得分(即每个用户总体上正确预测的比赛数量)

我对 MongoDB 和 Mongoose 还是很陌生,所以我不确定如何处理这个问题。由于 Game 文档永远不会超过 200 条记录,我认为这两个分数都应该从数据库中存储的数据中导出或计算。

以下是目前我想到的可能的解决方案:

如有任何建议,我们将不胜感激。

您可以使用聚合框架来计算聚合。对于常见的聚合操作,这是比 Map/Reduce 更快的替代方法。 在 MongoDB 中,管道由一系列应用于集合的特殊运算符组成,以处理数据记录和 return 计算结果。聚合操作将来自多个文档的值分组在一起,并且可以对分组数据执行各种操作以 return 单个结果。更多详情请参考documentation.

考虑运行使用以下管道以获得所需的结果:

var pipeline = [
    { "$unwind": "$userPicks" },
    {
        "$group": {
            "_id": {
                "week": "$week",
                "user": "$userPicks.user"
            },
            "weeklyScore": {
                "$sum": {
                    "$cond": [
                        { "$eq": ["$userPicks.chosenTeam", "$winner"] },
                        1, 0
                    ]
                }
            }           
        }
    },
    {
        "$group": {
            "_id": "$_id.user",
            "weeklyScores": {
                "$push": {
                    "week": "$_id.week",
                    "score": "$weeklyScore"
                }
            },
            "totalScores": { "$sum": "$weeklyScore" }
        }
    }
];

Game.aggregate(pipeline, function(err, results){
    User.populate(results, { "path": "_id" }, function(err, results) {
        if (err) throw err;
        console.log(JSON.stringify(results, undefined, 4));
    });
})

在上面的流水线中,第一步是$unwind运算符

{ "$unwind": "$userPicks" }

当数据存储为数组时,这非常方便。当 unwind 运算符应用于列表数据字段时,它将为应用 unwind 的列表数据字段的每个元素生成一条新记录。它基本上使数据变平。

这是下一个流水线阶段的必要操作,即 $group 步骤,您可以按字段 week"userPicks.user"

    {
        "$group": {
            "_id": {
                "week": "$week",
                "user": "$userPicks.user"
            },
            "weeklyScore": {
                "$sum": {
                    "$cond": [
                        { "$eq": ["$userPicks.chosenTeam", "$winner"] },
                        1, 0
                    ]
                }
            }           
        }
    }

$group 管道运算符类似于 SQL 的 GROUP BY 子句。在 SQL 中,除非使用任何聚合函数,否则不能使用 GROUP BY。同样,您也必须在 MongoDB 中使用聚合函数。您可以在此处.

阅读有关聚合函数的更多信息

在这个$group操作中,计算每个用户的每周得分(即他们每周预测正确的足球比赛次数)的逻辑是通过三元组完成的运算符 $cond 将逻辑条件作为第一个参数 (if) 然后 return 是第二个参数,其中评估为真 (then) 或第三个参数 where false (else)。这使得 true/false returns 变成 1 和 0 分别馈送到 $sum:

"$cond": [
    { "$eq": ["$userPicks.chosenTeam", "$winner"] },
    1, 0
]

因此,如果在正在处理的文档中 "$userPicks.chosenTeam" 字段与 "$winner" 字段相同,则 $cond 运算符会提供值 1 与总和,否则它总和为零值。

第二组流水线:

{
    "$group": {
        "_id": "$user",
        "weeklyScores": {
            "$push": {
                "week": "$_id.week",
                "score": "$weeklyScore"
            }
        },
        "totalScores": { "$sum": "$weeklyScore" }
    }
}

从之前的流水线中获取文档,并通过 user 字段对它们进行进一步分组,并使用 $sum accumulator operator. Within the same pipeline, you can aggregate a list of the weekly scores by using the $push 计算另一个聚合,即总分return 是每个组的表达式值数组的运算符。

这里要注意的一件事是在执行管道时,MongoDB 通过管道将运算符相互连接。 "Pipe"这里取Linux的意思:一个运算符的输出成为后面运算符的输入。每个运算符的结果是一个新的文档集合。所以Mongo执行上面的管道如下:

collection | $unwind | $group | $group => result

现在,当您 运行 在 Mongo 中设置聚合管道时,结果将有一个 _id 键,它是用户 ID,您需要在此填充结果字段即 Mongoose 将对用户集合执行 "join",并 return 结果中包含用户架构的文档。


作为旁注,为了帮助理解管道或调试它,如果您得到意外结果,运行 仅使用第一个管道运算符的聚合。例如,运行 mongo shell 中的聚合为:

db.games.aggregate([
    { "$unwind": "$userPicks" }
])

检查结果以查看 userPicks 数组是否被正确解构。如果这给出了预期的结果,请添加下一个:

db.games.aggregate([
    { "$unwind": "$userPicks" },
    {
        "$group": {
            "_id": {
                "week": "$week",
                "user": "$userPicks.user"
            },
            "weeklyScore": {
                "$sum": {
                    "$cond": [
                        { "$eq": ["$userPicks.chosenTeam", "$winner"] },
                        1, 0
                    ]
                }
            }           
        }
    }
])

重复这些步骤,直到到达最后的流水线步骤。