Laravel关联查询中如何引用父模型属性做日期条件比对
实现方案
问题原因
你在关联方法内定义预加载闭包时,闭包的执行时机是ORM构建关联查询的阶段,此时对应的Appointment模型还未被实际查询出来,自然无法读取单个模型的created_at属性。Laravel的批量预加载是一次性拼接SQL查询所有符合条件的关联记录,构建查询阶段不会遍历每个父模型传值,因此原写法拿不到目标属性。
方案1:SQL层直接比对(性能最优)
如果你的关联链路外键逻辑清晰,可以直接通过子查询在SQL层完成时间比对,不需要在PHP层面传值,没有额外的查询开销。
先确认关联链路的外键规则:
appointment_events.appointment_id关联appointments.idappointment_events.customer_id关联customers.idsections.customer_id关联customers.idtreatments.section_id关联sections.id
直接修改Appointment模型的关联定义即可:
// Appointment.php public function appointment_events(): HasMany { return $this->hasMany(AppointmentEvent::class) ->with([ 'customer' => function ($query) { $query->with([ 'section' => function ($subquery) { $subquery ->with([ 'advice', 'treatment' => function ($treatmentQuery) { $treatmentQuery->whereRaw( 'treatments.created_at > ( SELECT appointments.created_at FROM appointments INNER JOIN appointment_events ON appointment_events.appointment_id = appointments.id WHERE appointment_events.customer_id = sections.customer_id ORDER BY appointments.created_at DESC LIMIT 1 )' ); }, ]); }, ]); }, ]); }
注意:如果一个客户对应多个预约记录,需要根据业务逻辑调整子查询的排序规则,确保取到的是对应关联的预约时间。
方案2:分阶段预加载(逻辑最准确)
如果关联链路存在多对多/一对多的复杂映射,怕子查询匹配错记录,可以先把上层模型查出来,拿到created_at值之后再批量预加载treatment,逻辑100%准确,还可以通过分组避免N+1问题。
第一步:简化模型内的关联预加载定义,不要在关联里写死treatment的查询条件
// Appointment.php public function appointment_events(): HasMany { return $this->hasMany(AppointmentEvent::class) ->with([ 'customer.section:id,customer_id', // 只查需要的字段减少开销 'customer.section.advice' ]); }
第二步:调整控制器查询逻辑,先查上层模型,分组批量预加载符合条件的treatment
// AppointmentController.php public function index(): JsonResource { $users = User::with([ 'appointment.appointment_events.customer' ]) ->active() ->get(); // 按预约创建时间分组,收集需要查询的section ID $sectionGroups = []; foreach ($users as $user) { if (!$user->appointment) continue; $appointmentCreatedAt = $user->appointment->created_at->toDateTimeString(); foreach ($user->appointment->appointment_events as $event) { if ($event->customer?->section) { $sectionGroups[$appointmentCreatedAt][] = $event->customer->section->id; } } } // 按时间分组批量预加载treatment,查询次数等于不同预约时间的组数 foreach ($sectionGroups as $createdAt => $sectionIds) { Section::whereIn('id', array_unique($sectionIds)) ->with([ 'treatment' => fn($query) => $query->where('created_at', '>', $createdAt) ]) ->get(); } return UserResource::collection($users); }
由于Eloquent模型是单例模式,同一个ID的模型实例只会存一次,批量查询之后后续访问关联时会自动使用已经查好的treatment数据,不会重复查询。
内容的提问来源于stack exchange,提问作者Izzy
相关产品推荐
相关产品推荐

