Laravel Eloquent 实现首次客户的 upcoming 预约查询与排序
Eloquent实现携带即将到来预约的首次消费客户查询
你完全可以在现有Customer查询链上叠加条件实现需求,不需要反向从Reservation发起查询,最终直接返回携带关联预约的Customer集合,两个待解决问题都可以通过Laravel内置的关联查询能力搞定:
- 无历史预约的首次客户筛选:用
whereDoesntHave排除存在过去预约记录的客户 - 按预约时间排序:用关联子查询取出每个客户对应的最早未来预约时间,作为主查询的排序依据,同时避免一对多关联带来的记录重复问题
可直接运行的完整查询代码如下:
use Carbon\Carbon; use App\Models\Customer; use App\Models\Reservation; $now = Carbon::now(); $customerList = Customer::query() // 筛选存在即将到来预约的客户 ->whereHas('reservation', function ($query) use ($now) { $query->where('date', '>', $now); }) // 排除存在任意历史预约的客户,命中首次消费客户的筛选规则 ->whereDoesntHave('reservation', function ($query) use ($now) { $query->where('date', '<=', $now); }) // 按客户最早的即将到来预约时间排序 ->orderBy( Reservation::select('date') ->whereColumn('reservation.customer_id', 'customer.id') ->where('date', '>', $now) ->orderBy('date') ->limit(1), 'asc' ) // 加载客户关联的所有即将到来的预约,关联数据本身按预约时间排序 ->with(['reservation' => function ($query) use ($now) { $query->where('date', '>', $now)->orderBy('date', 'asc'); }]) ->get();
关键实现说明
- 首次客户筛选逻辑:
whereDoesntHave会自动生成NOT EXISTS类型的SQL子查询,只要当前客户存在任意一条日期早于当前时间的预约记录,就会被排除,完全匹配“无任何历史预约的首次消费客户”的定义- 排序逻辑避坑:如果直接通过JOIN关联Reservation表做排序,因为是一对多关系,同一个客户存在多条未来预约时会被查出多次,导致Customer集合出现重复记录;用标量子查询取每个客户最早的未来预约时间排序,既能保证Customer记录的唯一性,也能达到按预约时间排序的效果
- 关联加载逻辑:在
with加载关联数据时同样叠加了时间过滤条件,不会把已经被筛选排除的历史预约记录加载到结果集中,关联预约集合本身也按时间升序排列,和主查询的排序规则保持一致
你之前考虑的从Reservation模型反向发起查询的方案,即使加上“客户无历史预约”的筛选条件,最终返回的根模型是Reservation,后续还需要手动做客户去重、集合结构重组才能拿到你想要的Customer根集合,相比直接在Customer层做查询的方案,额外代码量大、维护成本高,整体性价比很低。
内容的提问来源于stack exchange,提问作者wivku
相关产品推荐
相关产品推荐

