聚合查询关键在NULL处理、分组合法性与关联统计真实性:sum/avg等返回NULL需??0补零;groupBy须确保select字段全在分组列或为聚合字段;withCount闭包条件仅作用于关联表,勿误写主表逻辑。

聚合查询不是“写完就跑”,关键在值是否为 NULL、分组是否合法、关联统计是否失真——这三个点踩错一个,结果就离谱。
sum() / avg() / max() / min() 返回 NULL 而不是 0
MySQL 对空结果集的 SUM()、AVG() 默认返回 NULL,Laravel 的 sum()、avg() 等方法直接透传这个行为。PHP 层一旦做加减乘除或 JSON encode,就会报 Trying to perform arithmetic on null 或输出 null 字段。
- 统一用
?? 0补零:$total = Order::where('status', 'paid')->sum('amount') ?? 0 - 避免链式运算:
->sum('price') * 1.1这种写法危险,先取值再算 - 数据库层补 0 可用
DB::raw('COALESCE(SUM(amount), 0)'),但注意它会让 Eloquent 返回数组而非模型实例 -
count()是例外:它永远返回整数,空结果也是0,不用判空
groupBy() 在 MySQL 8.0+ 报 Expression #1 of SELECT list is not in GROUP BY clause
这是严格模式下的语法校验,不是 Laravel bug。你写了 select('user_id', 'created_at')->groupBy('user_id'),但 created_at 既不在 GROUP BY 列表里,也不是聚合字段,MySQL 就拒绝执行。
- 要么所有
select()字段都出现在groupBy()中:->select('user_id', 'status')->groupBy('user_id', 'status') - 要么只选分组字段 + 聚合字段:
->select('user_id', DB::raw('COUNT(*) as cnt'))->groupBy('user_id') - 别依赖
select('*')+groupBy('id'),语义错误且结果不可靠 - 需要查用户信息+统计数?优先用
withCount()或子查询,不硬拼GROUP BY
withCount() 闭包里加错条件导致统计为 0 却不报错
withCount() 底层是 LEFT JOIN + COUNT,不是子查询。你在闭包里写的条件只作用于关联表的匹配行,不会过滤主表记录——所以主表数据全出来,但计数可能全是 0。
- 只放真正属于关联表的条件:
withCount(['orders as paid_orders' => function($q) { $q->where('status', 'paid'); }]) - 别在闭包里写影响主表逻辑的条件,比如
whereNotNull('deleted_at')—— 它无效,且让计数变 0 - 软删除关联需提前在关系定义中处理,例如
public function orders() { return $this->hasMany(Order::class)->withTrashed(); },而不是在withCount()闭包里补 - 要查“每个用户最近一笔订单金额”,
withCount()不适用,得用子查询或withSum()配合latest()临时排序
最常被忽略的是:聚合值为 NULL 不是“没数据”,而是“数据库明确告诉你这次没算出东西”;而 GROUP BY 报错不是配置问题,是 SQL 语义本身不成立。这两点不厘清,调一整天也白搭。











