Laravel Eloquent多账单场景下仅筛选最新记录的查询问题
解决Laravel Eloquent筛选客户最新账单的问题
我来帮你搞定这个查询瓶颈!你的核心需求是筛选出没有账单或者最新账单早于指定学年开始时间的客户,但之前的写法会因为客户存在任意一条符合条件的旧账单就返回,没有做到只校验最新的那条记录。下面给你两种靠谱的解决方案:
方法一:用子查询获取每个客户的最新账单时间
这种方法通过子查询先拿到每个客户的最新账单创建时间,再判断这个时间是否符合条件,逻辑清晰且性能不错:
$clients = $clients->whereDoesntHave('bill') ->orWhere(function ($query) use ($activeSchoolYear) { // 子查询:获取每个客户的最新账单创建时间 $latestBillSubquery = \App\Models\Bill::selectRaw('MAX(created_at) as latest_created_at') ->whereColumn('client_id', 'clients.id'); // 筛选:最新账单时间早于学年开始时间的客户 $query->whereExists(function ($sub) use ($latestBillSubquery, $activeSchoolYear) { $sub->select(\DB::raw(1)) ->fromSub($latestBillSubquery, 'latest_bills') ->where('latest_created_at', '<', Carbon::parse($activeSchoolYear->start)); }); }) // 保留你原有的contracts筛选条件 ->whereHas('contracts', function ($query) use ($request) { $query->where('contract_type', '<', 2) ->where('signed', 1) ->where('school_year_id', $request['school_year']); });
方法二:在关联查询中定位到最新账单再判断
这种写法直接在whereHas里先筛选出当前客户的最新账单,再校验时间,更贴近你原来的代码结构:
$clients = $clients->whereDoesntHave('bill') ->orWhereHas('bill', function ($query) use ($activeSchoolYear) { // 先定位到该客户的最新账单 $query->where('created_at', '=', function ($sub) { $sub->selectRaw('MAX(created_at)') ->whereColumn('client_id', 'bills.client_id'); }) // 再判断最新账单的时间是否早于指定时间 ->where('created_at', '<', Carbon::parse($activeSchoolYear->start)); }) ->whereHas('contracts', function ($query) use ($request) { $query->where('contract_type', '<', 2) ->where('signed', 1) ->where('school_year_id', $request['school_year']); });
为什么你之前的尝试没生效?
你之前写的scopeNoActiveBill里用了groupBy('created_at')->take(1),这个逻辑是对所有账单按创建时间分组取第一条,而不是针对每个客户的账单去取最新的,所以没法精准定位到每个客户的最新账单,导致只要有任意一条旧账单符合条件,客户就会被返回。
总结
核心思路就是:必须针对每个客户的最新账单单独做条件判断,而不是检查客户的任意一条账单。上面两种方法都能实现这个需求,你可以根据自己的代码习惯选择~
内容的提问来源于stack exchange,提问作者zlatan
相关产品推荐
相关产品推荐

