Laravel 多对多关联透视表如何筛选最大日期关联记录
原有代码失效原因
你之前的写法无法生效核心有两个逻辑错位:
$invoices是Invoice模型的顶层集合,而pivot.shift_swapped_date是挂在每个被关联出来的Shift模型实例上的属性,不是Invoice本身的字段。Laravel集合的max()方法只会遍历集合顶层元素的属性取值,根本不会深入嵌套的关联集合内部取数,自然无法拿到预期结果。- 就算遍历能触达内层关联,直接全量加载所有关联记录的方式,会把每个Invoice对应的全部shift关联都读进内存,再手动找最大值,大数据量下会产生不必要的性能损耗。
正确实现方案
方案1:关联层直接约束取最大日期记录(推荐,性能最优)
不需要全量加载所有关联,直接在关联定义里加数据库层的过滤条件,只取对应Invoice下透视表shift_swapped_date最大的那条关联记录。
先在Invoice模型里新增专用关联方法:
// 新增在Invoice模型内部 public function maxSwapShift() { return $this->belongsToMany(Shift::class, 'invoice_shift') ->withPivot([ 'invoice_id', 'shift_id', 'shift_taken_id', 'shift_swapped_date', 'shift_taken_date', 'tas', 'msg', 'status_tas' ]) // 数据库层直接匹配透视表最大日期,避免全量加载关联 ->whereRaw('invoice_shift.shift_swapped_date = ( SELECT MAX(shift_swapped_date) FROM invoice_shift WHERE invoice_shift.invoice_id = invoice.id )'); }
控制器中预加载这个关联即可直接拿到每个Invoice对应的目标记录:
$invoices = Invoice::with('maxSwapShift')->get(); // 遍历取值示例 foreach ($invoices as $invoice) { $maxShiftDate = $invoice->maxSwapShift->first()->pivot->shift_swapped_date ?? null; }
注意:
- 如果存在多条透视记录的
shift_swapped_date和最大值持平,这个查询会返回所有同值记录。如果只需要固定取一条,可额外加排序限制:->orderByDesc('invoice_shift.id') ->limit(1) - 你当前在
Shift模型的invoices关联中没有配置withPivot,如果需要从Shift侧反向查询关联Invoice并读取透视表自定义字段,需要在对应belongsToMany方法中补充和Invoice侧一致的withPivot参数,否则无法读取到透视表值。
方案2:全量加载关联后手动遍历取值(适合小数据量场景)
如果不想改动模型关联定义,在已全量加载shifts关联的前提下,需要做两层遍历才能取到值,不能直接对顶层Invoice集合取max:
$invoices = Invoice::with('shifts')->get(); foreach ($invoices as $invoice) { // 从当前Invoice自己的shifts关联集合中,提取透视字段的最大值 $maxSwapDate = $invoice->shifts->max('pivot.shift_swapped_date'); // 匹配对应最大值的关联记录 $maxSwapRecord = $invoice->shifts ->where('pivot.shift_swapped_date', $maxSwapDate) ->first(); }
这种写法的缺点是会加载所有关联记录到内存,数据量大时性能较差。
额外补充:如果你的需求不是为每个Invoice单独取自己的最大日期,而是要从所有Invoice关联的全部透视记录中,找全局的最大
shift_swapped_date,不需要遍历关联,直接查透视表即可:use Illuminate\Support\Facades\DB; $globalMaxDate = DB::table('invoice_shift')->max('shift_swapped_date');
内容的提问来源于stack exchange,提问作者Marco Bozzola
相关产品推荐
相关产品推荐

