
本文介绍在 laravel 中通过 eloquent 和 query builder 两种方式,为每个商品关联查询其平均评分(avg)和评分总数(count),支持无评分商品保留展示,并提供性能优化建议。
本文介绍在 laravel 中通过 eloquent 和 query builder 两种方式,为每个商品关联查询其平均评分(avg)和评分总数(count),支持无评分商品保留展示,并提供性能优化建议。
在构建电商或内容平台的商品评价系统时,常需在商品列表页同时展示「平均评分」与「评价总数」。Laravel 提供了多种优雅且高效的实现方式,既可利用原生查询构建聚合数据,也能借助 Eloquent 关系与查询作用域实现更可维护的代码结构。
✅ 推荐方案:使用 Eloquent + 关系预加载(兼顾可读性与性能)
假设你已正确定义模型关系:
// app/Models/Product.php
class Product extends Model
{
protected $table = 'product';
public function reviews()
{
return $this->hasMany(Review::class, 'product_id');
}
// 定义计算属性(仅用于展示,不持久化)
protected $appends = ['average_rating', 'rating_count'];
public function getAverageRatingAttribute()
{
return round($this->reviews->avg('rating'), 1) ?: 0;
}
public function getRatingCountAttribute()
{
return $this->reviews->count();
}
}// app/Models/Review.php
class Review extends Model
{
protected $table = 'review';
}获取全部商品及其评分统计(推荐):
$products = Product::withCount('reviews as rating_count')
->select('id', 'name', 'price')
->withAvg('reviews', 'rating as average_rating')
->get()
->map(function ($product) {
// 确保未评分商品显示为 0 而非 null
$product->average_rating = round($product->average_rating ?? 0, 1);
return $product;
});✅ 输出示例:
[
{
"id": 1,
"name": "Wireless Headphones",
"price": "129.99",
"rating_count": 42,
"average_rating": 4.3
},
{
"id": 2,
"name": "Bluetooth Speaker",
"price": "89.50",
"rating_count": 0,
"average_rating": 0.0
}
]⚠️ 注意:withAvg() 和 withCount() 是 Laravel 8+ 原生支持的聚合预加载方法,底层自动使用 LEFT JOIN + GROUP BY,避免 N+1 查询,且天然兼容空评分场景(返回 null,可安全转为 0)。
? 替代方案:使用 Query Builder(适合复杂聚合或跨库场景)
若暂未定义模型关系,或需高度定制 SQL,可直接使用 Query Builder:
use Illuminate\Support\Facades\DB;
$products = DB::table('product')
->leftJoin(DB::raw('(SELECT product_id, AVG(rating) as avg_rating, COUNT(*) as total_count FROM review GROUP BY product_id) as ratings'),
'ratings.product_id', '=', 'product.id')
->select(
'product.id',
'product.name',
'product.price',
DB::raw('COALESCE(ratings.avg_rating, 0) as average_rating'),
DB::raw('COALESCE(ratings.total_count, 0) as rating_count')
)
->get();该写法显式执行子查询聚合,再左连接主表,确保所有商品(含零评分)均被包含,且 COALESCE 保证数值型字段默认为 0。
? 关键注意事项
- 性能优先:避免在循环中调用 $product->reviews()->avg('rating') —— 这将触发 N+1 查询,严重拖慢响应。
- 空值处理:AVG() 和 COUNT() 在无匹配记录时分别返回 NULL 和 0,务必用 COALESCE() 或 PHP 层 ?? 0 处理。
-
索引优化:为 review.product_id 字段添加数据库索引,大幅提升 JOIN 与分组性能:
CREATE INDEX idx_review_product_id ON review(product_id);
- 缓存建议:对静态或低频更新的商品评分,可结合 Laravel Cache(如 Redis)缓存聚合结果,例如按 product_id 缓存 1 小时。
综上,推荐优先采用 withCount() + withAvg() 组合方案——语义清晰、性能优异、兼容 Laravel 最佳实践,是构建高可用评分系统的首选方式。


















