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
]
}
}
}
}
])
重复这些步骤,直到到达最后的流水线步骤。
我正在为我的家人开发一个大学橄榄球博彩应用程序。 这是我的模式:
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
]
}
}
}
}
])
重复这些步骤,直到到达最后的流水线步骤。