mysql 5.7+ 默认开启 only_full_group_by 时,groupby() 必须配合 select() 显式声明非聚合字段,否则报错;php 层分组应使用 collection::groupby(),而非查询构建器。

直接用 groupBy() 但没配 select()?基本会报错,尤其是 MySQL 5.7+ 默认开启 ONLY_FULL_GROUP_BY 模式时,错误信息通常是 Expression #1 of SELECT list is not in GROUP BY clause。
数据库层分组必须显式声明非聚合字段
MySQL 严格模式下,SELECT 列表里所有非聚合字段(比如 user_id、status)都得出现在 groupBy() 参数中,否则 SQL 直接被拒。
- 错误写法:
DB::table('orders')->groupBy('user_id')->get()—— 没指定select,数据库不知道该返回哪条记录 - 正确写法:
DB::table('orders')->select('user_id', DB::raw('COUNT(*) as count'))->groupBy('user_id')->get() - 多字段分组时,
select()和groupBy()的字段顺序要一致,例如按年月分组就得同时在selectRaw()里提取YEAR(created_at)并显式命名为year,再传groupBy('year', 'month') - 别依赖
select('*'),Eloquent 模型里也一样:用->select('status', DB::raw('SUM(amount) as total'))替代->select('*')
想按字段“归类成子集”?别用查询构建器的 groupBy()
查出一堆模型后想按 status 分成几个数组(比如 active 一堆、inactive 一堆),这不是数据库分组的事,是 Collection 的活儿。
- 先
get()拿到集合,再链式调用groupBy('status'):User::where('active', 1)->get()->groupBy('status') - 字段不存在会静默失败(比如模型没加
$appends或没预加载关联),建议先dd($collection->first()->toArray())确认结构 - 分组后是
Collection对象,不是数组;API 返回前必须->toArray(),否则json_encode()会出乱码 - 闭包分组更灵活:
$users->groupBy(fn ($u) => $u->profile?->level ?? 'guest')
关联表字段分组不能靠 with() + groupBy()
with('orders') 后接 groupBy('orders.status') 会被忽略——预加载和主查询隔离,groupBy() 只作用于主表。
- 真要按关联字段分组(如“每个订单状态对应多少用户”),必须用
join()拉平:DB::table('users')->leftJoin('orders', 'users.id', '=', 'orders.user_id') -
select()里出现的非聚合字段(如orders.status)必须也在groupBy()中列出 - 聚合函数要用
DB::raw()包裹并带别名,否则取值困难:DB::raw('COUNT(DISTINCT users.id) as user_count') - 如果需要嵌套结构(如 status → [order1, order2]),别硬拼 SQL,
get()后用 CollectiongroupBy()更稳
分组后排序、分页、筛选的坑
orderBy() 放 groupBy() 前后都不影响 SQL 执行顺序,但字段必须出现在 SELECT 列表里,否则 MySQL 会报错或忽略。
- 按聚合结果排序:
selectRaw('status, COUNT(*) as cnt')->groupBy('status')->orderBy('cnt', 'desc') -
paginate()在分组后基本失效——分页器按原始行数算,但分组后总条数已变;改用forPage()或先get()再处理 -
having()是唯一能对聚合结果过滤的方式,where('cnt', '>', 5)无效,得用having('cnt', '>', 5) - 大数据量慎用 PHP 层分组:上万条记录全 load 进内存再
groupBy(),容易 OOM
最常被忽略的一点:本地开发不报错,上线就崩。因为 MySQL 5.7 和 8.0+ 默认 SQL mode 不同,ONLY_FULL_GROUP_BY 开关状态不一致。别关它,老老实实每条 groupBy() 都配好 select() 和字段对齐。











