MongoosJS:派生/计算值的最佳方法

时间:2016-09-13 13:53:09

标签: mongodb mongoose mongoose-schema

我正在为我的家人创建一个大学足球博彩应用程序。 这是我的模式:

await

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

我仍然是MongoDB和Mongoose的新手,所以我不确定如何处理这个问题。由于游戏文档永远不会超过200条记录,我认为这两个分数应该从存储在数据库中的数据中导出或计算出来。

以下是我到目前为止所考虑的可能解决方案:

  • 对两个分数进行虚拟属性,不确定这对多个用户有何影响
  • 将属性保留在文档中,但是当本周游戏的结果保存到数据库时,使用中间件重新计算分数。
  • 使用静态方法计算分数。

任何建议都将受到赞赏。

1 个答案:

答案 0 :(得分:1)

您可以使用聚合框架来计算聚合。对于常见的聚合操作,这是Map / Reduce的更快替代方案。 在MongoDB中,管道由一系列应用于集合的特殊运算符组成,以处理数据记录并返回计算结果。聚合操作将来自多个文档的值组合在一起,并且可以对分组数据执行各种操作以返回单个结果。有关详细信息,请参阅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" }

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

这是下一个管道阶段的必要操作, $group 步骤,您可以按字段week"userPicks.user" <对展平的文档进行分组/ p>

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

$group 管道运算符类似于SQL的GROUP BY子句。在SQL中,除非使用任何聚合函数,否则不能使用GROUP BY。同样,您也必须在MongoDB中使用聚合函数。您可以阅读有关聚合函数here的更多信息。

在此 $group 操作中,计算每个用户每周得分(即他们每周正确预测的足球比赛数量)的逻辑是通过三元运算符完成的 $cond ,它采用逻辑条件作为它的第一个参数(if),然后返回第二个参数,其中评估为真(然后)或第三个参数,其中为false(否则) )。这使得返回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 累加器运算符计算另一个聚合,即总分。在同一个管道中,您可以使用 $push 运算符汇总每周分数列表,该运算符会为每个组返回一组表达式值。

这里需要注意的一点是,在执行管道时,MongoDB会将运营商相互管道化。 &#34;管&#34;这里采用Linux的含义:运算符的输出成为以下运算符的输入。每个运算符的结果是一个新的文档集合。所以Mongo按如下方式执行上述管道:

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

现在,当您在Mongoose中运行聚合管道时,结果将有一个_id键,这是用户ID,您需要在此字段中填充结果,即Mongoose将执行&#34; join& #34;在用户集合上,并在结果中使用用户架构返回文档。

作为旁注,为了帮助理解管道或调试它,如果您得到意外结果,只需使用第一个管道运算符运行聚合。例如,在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
                    ]
                }
            }           
        }
    }
])

重复这些步骤,直到进入最后的管道步骤。