Laravel中如何基于关联表的用户总分获取排名
获取用户总分排名的实现思路
我有users和achievements两张数据表,每个用户的总分基于achievements表中的数据计算,这部分功能目前运行正常。现在需要根据已计算的总分,获取该用户在所有用户中的排名,比如能得出ID为5的用户排第5名。
现有代码示例
Achievement模型中的定义
public function user() { return $this->belongsTo(User::class); } public function getAttributeSum() { $sum = 0; foreach(func_get_args() as $attribute){ $sum += $this->getAttribute($attribute); } return $sum; }
User模型中的定义
public function achievement() { return $this->hasOne(Achievement::class, 'user_id', 'id'); }
获取用户总分的方式
$achievement = Achievement::where('user_id', $user->id)->first(); $score = $achievement->getAttributeSum('donations', 'shares', 'listens', 'watches', 'readings', 'prayers');
achievements表结构

待实现的排名功能
现在需要补充获取排名的代码:
$achievement = Achievement::where('user_id', $user->id)->first(); $score = $achievement->getAttributeSum('donations', 'shares', 'listens', 'watches', 'readings', 'prayers'); $placement = ??? // <---- (例如 5 of 2000)
请问有实现思路吗?
实现方案
思路1:数据库层面计算排名(性能更优)
直接通过SQL计算每个用户的总分,统计比当前用户分数高的用户数量,再加1就是排名,适合数据量大的场景:
// 构造总分计算的SQL表达式 $scoreColumns = ['donations', 'shares', 'listens', 'watches', 'readings', 'prayers']; $scoreExpr = implode(' + ', $scoreColumns); // 获取当前用户的总分 $currentScore = Achievement::where('user_id', $user->id)->selectRaw($scoreExpr . ' as total')->value('total'); // 统计分数高于当前用户的人数,加1得到排名 $rank = Achievement::selectRaw('COUNT(*) + 1 as rank') ->whereRaw("($scoreExpr) > ?", [$currentScore]) ->value('rank'); // 获取总用户数 $totalUsers = User::count(); // 拼接成目标格式 $placement = "{$rank} of {$totalUsers}";
思路2:模型层面计算(适合小数据量场景)
如果用户数据量不大,可以先拉取所有用户的总分,再通过内存排序计算排名:
// 获取所有用户及对应的总分 $allUsersWithScore = User::with(['achievement'])->get()->map(function($user) { return [ 'user_id' => $user->id, 'total_score' => $user->achievement ? $user->achievement->getAttributeSum('donations', 'shares', 'listens', 'watches', 'readings', 'prayers') : 0 ]; })->sortByDesc('total_score')->values(); // 查找当前用户的排名(数组索引从0开始,需加1) $rank = $allUsersWithScore->search(function($item) use ($user) { return $item['user_id'] == $user->id; }) + 1; $totalUsers = $allUsersWithScore->count(); $placement = "{$rank} of {$totalUsers}";
优化建议
- 数据量大时优先用思路1,数据库层面计算性能远高于内存排序。
- 可以考虑新增
total_score字段,定时通过队列或数据库触发器更新总分,后续排名查询会更高效;也可以给achievements表的分数字段建立联合索引,提升查询速度。
内容的提问来源于stack exchange,提问作者mafortis
相关产品推荐
相关产品推荐

