Laravel一对多关联下按平均评分排序帖子及过滤问题
实现按平均评分排序帖子并过滤最低评分数量
嘿,我来帮你搞定这个问题!要实现按平均评分排序,同时过滤掉评分数量不足X条的帖子,我们可以利用Laravel的查询构建器和关联聚合功能来实现,下面分两种方案给你讲解:
方案一:使用Laravel的withAggregate(推荐,更简洁)
Laravel 8+ 提供了withAggregate方法,可以直接获取关联模型的聚合值(比如平均值),配合withCount统计评分数量,再用having过滤条件:
// 替换X为你需要的最小评分数量,比如3;stars替换为你的Rating模型中存储评分的字段名 $minRatings = X; return Post::with(['user']) // 计算该帖子的平均评分,别名ratings_avg_stars ->withAggregate('ratings', 'stars', 'avg') // 统计该帖子的评分总数 ->withCount('ratings') // 过滤出评分数量>=X的帖子 ->having('ratings_count', '>=', $minRatings) // 按平均评分倒序排序(热门在前) ->orderByDesc('ratings_avg_stars') ->paginate(15)->onEachSide(1);
关键说明:
withAggregate('ratings', 'stars', 'avg'):第一个参数是关联名称(你的Post模型里已经定义了ratings关联),第二个是Rating表中存储评分的字段(比如stars、score,换成你实际的字段名),第三个是聚合函数(这里用avg求平均)。withCount('ratings'):自动为每个Post模型添加ratings_count属性,记录该帖子的评分数量。having('ratings_count', '>=', $minRatings):因为ratings_count是聚合统计出来的字段,所以要用having而不是where来过滤。
方案二:使用原生SQL聚合(兼容性更强)
如果你用的是Laravel 8以下版本,或者更习惯原生SQL的写法,可以用selectRaw结合leftJoin来实现:
$minRatings = X; return Post::with(['user']) ->leftJoin('ratings', 'posts.id', '=', 'ratings.post_id') // 选择帖子所有字段,同时计算平均评分和评分数量 ->selectRaw('posts.*, AVG(ratings.stars) as avg_rating, COUNT(ratings.id) as rating_count') // 按帖子ID分组,确保每个帖子只返回一条记录 ->groupBy('posts.id') // 过滤评分数量>=X的帖子 ->having('rating_count', '>=', $minRatings) // 按平均评分倒序排序 ->orderByDesc('avg_rating') ->paginate(15)->onEachSide(1);
注意事项:
- 如果你的MySQL开启了严格模式,
groupBy可能需要包含posts表的所有字段,这时候可以把posts.*替换成具体的字段列表,或者在config/database.php中关闭strict模式(不推荐,最好用方案一)。 - 同样要替换
stars为你实际的评分字段名。
这样就能完美实现你要的按平均评分排序,同时只保留至少有X条评分的帖子啦!
内容的提问来源于stack exchange,提问作者Alex
相关产品推荐
相关产品推荐

