laravel中对关联数据分组统计需用子查询或join:子查询法在selectraw中嵌套聚合并关联主表id;join法需group by加distinct防重复计数;还可封装为模型作用域复用。

在Laravel中对关联数据做分组统计时,需要先查出主表记录,再基于关联表字段聚合计算(如COUNT、SUM),但直接在with()里写selectRaw或groupBy会报错——因为Eloquent不支持在关系预加载中嵌套聚合查询。
使用子查询构造关联聚合字段
第一步:在主查询的select()中用DB::raw()包裹子查询语句→第二步:子查询SELECT部分只写聚合函数,FROM指向关联表→第三步:子查询WHERE条件必须关联主表ID,用?占位符或use绑定变量。
这一步必须用DB::raw()包裹整个子查询字符串,否则Laravel会把括号内内容当普通字段名处理,导致SQL语法错误。
示例代码:
```php
$posts = Post::select('posts.*')
->selectRaw('(SELECT COUNT(*) FROM comments WHERE comments.post_id = posts.id) as comments_count')
->selectRaw('(SELECT SUM(upvotes) FROM reactions WHERE reactions.post_id = posts.id) as total_upvotes')
->get();
```
用join替代with实现带条件的聚合
方法一:left join关联表→用GROUP BY posts.id分组→在selectRaw中写COUNT(DISTINCT comments.id)等聚合表达式。
注意:如果主表一条记录对应关联表多条数据,不加DISTINCT会导致COUNT虚高。例如一个帖子有3条评论和2次点赞,直接COUNT(reactions.id)会算出6次而非2次。
方法二:先用子查询生成聚合结果临时表→再与主表LEFT JOIN。
```php
$subQuery = DB::table('reactions')
->select('post_id', DB::raw('SUM(upvotes) as total_upvotes'), DB::raw('COUNT(*) as reaction_count'))
->groupBy('post_id');
$posts = Post::select('posts.*', 'agg.total_upvotes', 'agg.reaction_count')
->leftJoinSub($subQuery, 'agg', 'agg.post_id', '=', 'posts.id')
->get();
```
在模型作用域中封装嵌套聚合逻辑
第一步:在Post模型中定义scopeWithAggregates()作用域→第二步:接收$builder参数,调用selectRaw添加子查询字段→第三步:返回$builder对象以便链式调用。
这个作用域能复用在任意Post查询后,比如Post::where('status', 'published')->withAggregates()->get()。
```php
public function scopeWithAggregates($builder)
{
return $builder
->selectRaw('(SELECT COUNT(*) FROM comments WHERE comments.post_id = posts.id) as comments_count')
->selectRaw('(SELECT AVG(rating) FROM ratings WHERE ratings.post_id = posts.id) as avg_rating');
}
```
调用时直接写Post::withAggregates()->get()即可获取带聚合字段的结果集。











