Laravel 中无法用 groupBy + orderBy 获取每组最大 version 的完整记录,因 SQL 标准规定 GROUP BY 后 SELECT * 返回非确定行;max() 仅返回数值,须用子查询或 LEFT JOIN 自连接实现。

直接说结论:Laravel 中想通过关联查出「每组 name 对应 version 最大的那条完整记录」,groupBy + orderBy 组合无效,max() 只返回数字不返回整行——必须用子查询或自连接。
为什么 groupBy + orderBy 在 Laravel 里取不到每组最大值的整行
MySQL(及多数数据库)中,GROUP BY 后 SELECT * 是非标准行为:数据库会从每组随机选一行填充未分组字段,ORDER BY 在分组前执行,对分组结果无约束力。Eloquent 的 orderBy('version', 'DESC')->groupBy('name') 看似合理,实际返回的可能是任意一条 name 相同的记录,version 值完全不可控。
常见错误现象:
-
Item::orderBy('version', 'DESC')->groupBy('name')->get()返回结果中 version 并非每组最大 -
Item::max('version')只返回一个数字,不是模型实例,无法访问其他字段 -
Item::latest('version')->groupBy('name')同样失效,latest()作用于整个结果集
用子查询实现「每组取 version 最大整行」(推荐)
语义清晰、跨数据库兼容(MySQL 5.7+、PostgreSQL 支持),但注意 SQLite 不支持二维 whereIn。
实操建议:
- 先用子查询找出每组
name对应的最高version - 再用
whereIn(['name', 'version'], ...)匹配原始表中的完整行 - 若遇 SQLite 报错
SQLSTATE[HY000]: General error,说明不支持多列WHERE IN,需切换为 JOIN 方案
示例代码:
$items = DB::table('images')
->whereIn(['name', 'version'], function ($query) {
$query->from('images as i2')
->select('name', DB::raw('MAX(version)'))
->groupBy('name');
})
->get();
用 LEFT JOIN 自连接避免子查询兼容性问题
纯 Eloquent 写法,不依赖数据库对多列 IN 的支持,适合 SQLite 或需要更稳定行为的场景。
Laravel Cloud(cloud.laravel.com)。此技能用于处理所有 Laravel Cloud 请求,包括搜索和读取数据。只要任务涉及 Laravel Cloud,即使用此技能。
核心逻辑:把表自连接,让 i1 的每条记录尝试匹配到 i2 中同 name 但 version 更大的记录;若匹配不到(i2.name IS NULL),说明 i1 就是该组最大值。
实操要点:
- 必须用
leftJoin(),不能用join(),否则会漏掉只有一条记录的组 on('i1.version', ' 是关键条件,不是!=或>-
whereNull('i2.name')表示没找到更大的匹配项,即i1是最大值 - 记得在
select()中明确写'i1.*',避免字段歧义
示例代码:
$items = Image::select('i1.*')
->from('images as i1')
->leftJoin('images as i2', function ($join) {
$join->on('i1.name', '=', 'i2.name')
->on('i1.version', 'whereNull('i2.name')
->get();
关联模型中嵌套取每组最大值的坑
当需求变成「查出某个 Category 下每个 Tag 的最新 Post」这类多层嵌套时,直接在 with() 闭包里用 orderBy + limit(1) 会失效——因为 with() 加载的是预加载集合,limit(1) 会被忽略或仅作用于整个关联结果集。
正确做法分两步:
- 先用子查询或 JOIN 查出所有目标记录(如每个 tag_id 对应的最新 post_id)
- 再用
whereIn('id', [...])批量加载这些 post,并通过with('tag')补充关联 - 如果一定要在关系定义里固化逻辑,可在 Tag 模型中定义一个
latestPost()关系,返回hasOne(Post::class)->latest('created_at'),但注意它只适用于单条,不适用于“每组一条”批量场景
容易被忽略的一点:当数据量大时,子查询中的 MAX(version) 和外层 WHERE IN 都需要对应字段有索引,否则性能急剧下降。务必确保 (name, version) 是联合索引,而非单独索引。










