Laravel预加载中使用orderBy()->first()关联数据丢失问题咨询
问题原因
你在关联预加载闭包中调用first()出现数据丢失,本质是Laravel预加载机制的特性导致的:
Laravel的预加载为了避免N+1查询,会先拿到所有父模型(这里是所有符合条件的ServiceProvider)的主键列表,再一次性查询所有关联的sp_services记录,最后按外键匹配到对应父模型。你在闭包中加first()相当于给这个全局的关联查询加了LIMIT 1,最终整个关联查询只会返回1条sp_services记录,只有一个ServiceProvider能匹配到数据,其余服务商的关联自然为空。
最优实现方案
方案1:新增一对一关联取单条记录(推荐,Laravel 8+支持)
直接在ServiceProvider模型中新增一个hasOne类型的关联,专门用于取每个服务商价格最低的服务:
class ServiceProvider extends Model { // 原有关联保留 public function sp_services() { return $this->hasMany(SpService::class, 'sp_id', 'id'); } // 新增取最低价服务的关联 public function cheapest_service() { // 要取最高价就把oldest改成latest return $this->hasOne(SpService::class, 'sp_id', 'id')->oldest('price'); } }
修改查询代码直接预加载这个新关联即可:
$branchesQuery = SpBranch::query(); $branchesQuery->where('status', true); $branchesQuery->when($request->filled('price'), function($query) { return $query->with(['service_provider' => function($query) { $query->whereHas('sp_services') ->with('cheapest_service'); }]); }); $branches = $branchesQuery->paginate($perPageLimit); return $branches;
这个方案性能最优,完全符合Laravel的关联设计规范,不会产生多余查询。
方案2:集合层面过滤(适合数据量小的场景)
如果不想修改模型,也可以在预加载时仅保留排序逻辑,拿到查询结果后用集合方法取每条关联的第一条:
// 预加载时只做排序 $branchesQuery->when($request->filled('price'), function($query) { return $query->with(['service_provider' => function($query) { $query->whereHas('sp_services') ->with(['sp_services' => fn($q) => $q->oldest('price')]); }]); }); $branches = $branchesQuery->paginate($perPageLimit); // 处理分页结果,只保留每个服务商的第一条服务 $branches->through(function($branch) { if (isset($branch->service_provider)) { $branch->service_provider->setRelation( 'sp_services', collect([$branch->service_provider->sp_services->first()]) ); } return $branch; }); return $branches;
补充说明
你当前代码里写的orderBy('price', 'DESC')是按价格倒序,取的是最高价的服务,如果你要取最低价,需要把排序方向改成ASC。
内容的提问来源于stack exchange,提问作者Wcan
相关产品推荐
相关产品推荐

