Laravel 8 中如何实现一对多关联按子表result_part_id分组取最新记录
环境与前提
使用开启了strict=true SQL模式的Laravel 8 + MySQL 8开发环境,涉及两张数据表结构如下:
exam_results id, result_date examresult_revisions id, examresult_id, result_part_id, revision_id, created_at, updated_at ...
已定义的模型关联:
class Examresult extends Model { public function revisions() { return $this->hasMany(ExamresultRevision::class); } }
需求
每个考试结果(ExamResult)包含多个对应不同result_part_id的部分,每个部分可能存在多个修订版本,需要为每个ExamResult获取按result_part_id分组的最新ExamresultRevision记录。
实现方案
方案1:使用MySQL 8窗口函数(推荐,性能最优)
ROW_NUMBER()窗口函数天然适合分组取TopN的场景,完全兼容严格模式:
// 单个ExamResult加载最新分部分修订 $examResult = Examresult::with(['revisions' => function ($query) { $query->selectRaw('*, ROW_NUMBER() OVER (PARTITION BY result_part_id ORDER BY created_at DESC, id DESC) as rn') ->having('rn', '=', 1); }])->find($examResultId); // 批量加载多个ExamResult的最新分部分修订也可直接使用该写法,无N+1问题
说明:ORDER BY追加id DESC是为了处理同一部分同一时间提交多条修订的极端情况,取自增ID更大的记录作为最新版本,逻辑更稳妥。
方案2:子查询匹配最新记录ID
如果不想使用窗口函数,可以用子查询匹配每个部分的最新修订ID,同样符合严格模式要求:
$examResult = Examresult::with(['revisions' => function ($query) { $latestRevisionIds = ExamresultRevision::selectRaw('MAX(id) as id') ->whereColumn('examresult_id', 'examresult_revisions.examresult_id') ->groupBy('result_part_id'); $query->whereIn('id', $latestRevisionIds); }])->find($examResultId);
方案3:封装为独立关联(Laravel 8.42+版本支持)
可以借助Laravel内置的ofMany方法封装为专属关联,后续调用更简洁:
// 在Examresult模型中新增关联方法 public function latestRevisionPerPart() { return $this->hasMany(ExamresultRevision::class) ->ofMany('id', 'max') // 按自增ID最大判定为最新,也可替换为 'created_at', 'max' 按创建时间判断 ->groupBy('result_part_id'); } // 调用时直接预加载即可 $examResult = Examresult::with('latestRevisionPerPart')->find($examResultId);
注意事项
- 以上所有写法都不会触发严格模式下的
ONLY_FULL_GROUP_BY报错 - 数据量较大时优先选择窗口函数方案,查询效率更高
- 如果修订存在软删除,对应查询需要追加
->withoutTrashed()过滤已删除记录
内容的提问来源于stack exchange,提问作者Saleem Ahmed
相关产品推荐
相关产品推荐

