You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Laravel关联查询中如何引用父模型属性做日期条件比对

实现方案

问题原因

你在关联方法内定义预加载闭包时,闭包的执行时机是ORM构建关联查询的阶段,此时对应的Appointment模型还未被实际查询出来,自然无法读取单个模型的created_at属性。Laravel的批量预加载是一次性拼接SQL查询所有符合条件的关联记录,构建查询阶段不会遍历每个父模型传值,因此原写法拿不到目标属性。

方案1:SQL层直接比对(性能最优)

如果你的关联链路外键逻辑清晰,可以直接通过子查询在SQL层完成时间比对,不需要在PHP层面传值,没有额外的查询开销。
先确认关联链路的外键规则:

  • appointment_events.appointment_id 关联 appointments.id
  • appointment_events.customer_id 关联 customers.id
  • sections.customer_id 关联 customers.id
  • treatments.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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.01 01:51:24