MongoDB 如何实现实时排名

当我们将考试分数录入系统时,会要对学生的分数进行一个排名,这个不困难。困难的是当学生的分数变更时,如何实时更新这些排名?

如果我们将排名保存为一个字段,那么意味着每次修改分数都会导致重新计算排名,以及更新数据库中的排名字段值。这个计算量可大可小,极端的情况下,如果一个学生的分数从第一名变成 0(比如因为作弊而成绩清零),那么所有学生的排名都有可能要改,这就导致大批量的数据库 update 操作。

这种方式显然是效率非常低的。那么这里给出一个解决办法,能够实现修改分数的同时,排名立刻得到更新,而无需大规模修改数据库记录。本文以 MongoDB 为例介绍如何保存这些分数。

假设我们有 10 个学生的考试成绩分别为:

50 50 50 60 60 60 60 80 90 100

那么我可以创建一条排名记录(假设这条记录在集合 scores 里面),内容如下:

{
  key: 'exam_001',
  scoreMap: [
    {score:50, count:3},
    {score:60, count:4},
    {score:80, count:1},
    {score:90, count:1},
    {score:100, count:1}
  ]
}

看到这里你就应该明白了,我保存的只是每个分数对应的人数。这样当我需要添加一个 80 分的成绩时,我只需将 {score:80, count:1} 改为 {score:80, count:2} 即可。

这种情况下如何获得一个学生的排名呢?首先要说明,每个学生对应的分数并没有保存在这里(你可以保存到另外的表里面,然后查出该学生的分数),这里保存的是分数的排名,而不是学生的排名。所以你查出分数后,对 scoreMap 数组中所有 score 大于该分数的元素的 count 值进行一个总和即可。

比如我想知道 85 分是第几名,从数组中可以算知大于 85 分的人数为 2,那么 85 分自然是第 3 名了。

那么在 MongoDB 里面,可以这样查询:

db.scores.aggregate([
     {$match:{key:'exam_001'}},
     {$unwind:"$scoreMap"},
     {$match:{'scoreMap.score': {$gt: 85}}},
     {$group:{_id:0,count:{$sum:'$scoreMap.count'}}}
]);

查出来的结果为:

{ "_id" : 0, "count" : 2 }

这种保存方式有一个极大的好处,就是不论有多少学生参加排名,我的记录数始终是有限的。假设这门考试总分 100 分,有 100 万学生参加排名,那么极端情况下 scoreMap 数组中也只会有 201 个元素(0, 0.5, 1, … 99.5, 100)。也就是说,它的值空间是非常有限的

有人会进一步提出:如果我将班级平均分(带两位小数)也加入这样的排名呢?值空间将会变成几万个,这样的排名查询起来岂不会很慢?

实际上不是这样子的。虽然两位小数使得值空间很大,但参与排名的班级非常有限,也就是说值的实际数量很小。如果是 100 个班级参与排名,不存在相同平均分的情况下, scoreMap 数组中也只有 100 个元素。所以计算排名丝毫不会慢下来。

如何更新

现在我们的排名已经不再是直接保存在数据库,而是在查询的时候算出来,那么当一个学生的成绩变更时,我应该如何更新数据库记录呢?

当一个学生的成绩从 a 变为 b 时,我们需要做两个操作:

  1. 将成绩为 a 的学生数量 -1;

  2. 将成绩为 b 的学生数量 +1。

在 MongoDB 中,更新 scoreMap 数组元素的语句是这样的:

给某个分数的人数加一的时候:

db.scores.update(
    {key:'exam_001', scoreMap:{$elemMatch:{score:99.5}}},
    {$inc:{'scoreMap.$.count':1}}
)

给某个分数的人数减一的时候:

db.scores.update(
    {key:'exam_001', scoreMap:{$elemMatch:{score:99.5,count:{$gt:0}}}},
    {$inc:{'scoreMap.$.count':-1}}
)

注意减一的时候,查询条件加上了一个 count:{$gt:0} 的判断,这是为了避免人数变成负数。

总之,通过这样的保存方式,我们能够实现当学生成绩改变时,快速简单的修改数据库记录,而又能够实时的查询出新的排名了。

    原文作者:捏造的信仰
    原文地址: https://segmentfault.com/a/1190000004146644
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞