先说一下应用场景
paper表
有score(得分)、time(考试使用时间),createtime(开始提交时间)、user(用户id,引用user表)四列,结构如下:
const schema = baseSchema().add({
/**
* 会员ID
*/
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'user',
required: true
},
/**
* 得分
*/
score: { type: Number, required: true, index: true },
/**
* 所用时间,单位秒
*/
time: { type: Number, required: true, index: true },
/**
* 答题时间
*/
createtime: { type: Number }
})
user表
有主键_id和昵称nickname两个字段。
一个会员可以多次答题,也就是说paper表中,可以有多个同样的user记录。
(两张表实际项目中列肯定不止那么少,我只是找几个重点罗列)
需求
计算得分前十的排行榜,每个user只根据他最好的得分来进行排名,并且还要显示nickname,得分相同则答题时间少的排名优先,如果再相同,则答题时间早的排名优先。
举个例子:
张三,答题两次,score100,90,time20
李四,答题两次,score80,70,time15
赵五,同样,score80分,60分,time16秒
那么最终排行榜第一名就是张三的90分,第二名就是李四的80分,第三名就是赵五的80分。
即一个user 答题3次都是获得最高分,那么最终的排名也只按照他最好的一次成绩来统计。
先上完整代码
Model.aggregate().lookup({
from: 'users', localField: 'user',
foreignField: '_id', as: 'users'
})
.sort('-score time createtime')
.group({
_id: '$user', score: { $first: '$score' }, time: { $first: '$time' },
createtime: { $first: '$createtime' }, users: { $first: '$users' }
})
.project({
_id: 0, score: 1, time: 1, createtime: 1,
nickname: { $min: '$users.nickname' }
})
.sort('-score time createtime').limit(10)
第一个方法,lookup,就是聚合中联合外键表,有一点非常重要,from字段,必须要加上分数形式,因为在mongoose创建表格时,它会自动把你的表名改成复数形式的,所以这里也一定要填写复数形式,否则会出错。
第二个方法,sort,此时数据中已经有完成的user表和paper表的联合数据了,根据实际需求进行排序。
第三个方法,group,因为只取每个用户的最好成绩,所以需要group分组,然后$first关键字取得第一行数据,也就说该用户的最好成绩。users里面的得到的是一个数组,因为数据都一样,取第一个即可。
第四个方法,project,过滤掉其他不需要列明,0代表不要,1代表需要,另外users结构是数组,所以只要取一个即可,所以随便使用min或者max都行,因为只有一组数据了。
第五个方法,sort,此时集合里是每个用户最好的记录的数据了,然后再根据数据进行排序,即可获得最终的排名。
至此完美实现需求。