Laravel Eager Load关联中使用Limit/Take仅返回单条记录如何解决
问题原因
你写的关联在预加载时不生效,核心原因是Eloquent预加载关联的逻辑是先查询所有符合条件的父模型(Person),再一次性查询所有关联的Activity数据,最后把关联数据匹配到对应的父模型上。你加的limit(1)是作用于整个Activity关联查询的结果集,只会返回全局最新的1条活动,自然只能匹配到第一个Person模型。
解决方法
下面给出3种不同场景的原生解决方案,无需引入额外扩展:
方案1:集合过滤(开发成本最低,适合小数据量场景)
先定义正常的多对多关联,再通过模型访问器从关联集合中过滤最新单条活动,预加载时直接加载全量关联即可。
代码实现
// Person模型内代码 class Person extends Model { // 正常的多对多活动关联 public function activities() { return $this->belongsToMany(Activity::class, 'activity_job', 'job_id', 'activity_id'); } // 最新活动访问器 public function getLastActivityAttribute() { return $this->activities->sortByDesc('created_at')->first(); } }
使用方式
// 预加载时直接加载activities关联即可 $persons = Person::with('activities')->get(); // 直接取每个人员的最新活动 foreach ($persons as $person) { echo $person->last_activity->title ?? '无活动'; }
适用场景
单页展示人员数≤100、每人关联活动数≤100的场景,写法最简洁,没有SQL优化成本。
方案2:子查询过滤(性能最优,适合大数据量场景)
通过whereRaw子查询在SQL层面就为每个人员过滤出最新的1条活动,返回的关联结果每条人员只会对应1条活动,没有冗余数据。
代码实现
// Person模型内关联代码 public function lastActivity() { return $this->belongsToMany(Activity::class, 'activity_job', 'job_id', 'activity_id') ->orderByDesc('created_at') ->whereRaw( '(SELECT COUNT(*) FROM activity_job AS aj2 INNER JOIN activities AS a2 ON aj2.activity_id = a2.id WHERE aj2.job_id = activity_job.job_id AND a2.created_at >= activities.created_at) = 1' ); }
使用方式
// 直接预加载lastActivity关联即可 $persons = Person::with('lastActivity')->get(); foreach ($persons as $person) { echo $person->lastActivity->title ?? '无活动'; }
适用场景
人员或活动数据量较大的场景,SQL层面直接过滤冗余数据,查询性能更高,兼容Laravel 5.8及以上所有版本。
方案3:latestOfMany语法(Laravel 8.42+ 推荐,写法最优雅)
Laravel 8.42版本新增了latestOfMany()、oldestOfMany()方法,专门用于获取单条最新/最早的关联数据,针对多对多场景可以通过中间表模型中转实现。
代码实现
- 先创建中间表模型
ActivityJob - 在Person模型中定义通过中间表的hasOne关联
// Person模型内代码 public function lastActivity() { return $this->hasOne(ActivityJob::class, 'job_id') ->latestOfMany('created_at') ->with('activity'); }
- 在
ActivityJob模型中定义关联Activity的belongsTo关联
// ActivityJob模型内代码 public function activity() { return $this->belongsTo(Activity::class); }
使用方式
$persons = Person::with('lastActivity.activity')->get(); foreach ($persons as $person) { echo $person->lastActivity?->activity?->title ?? '无活动'; }
内容的提问来源于stack exchange,提问作者Hyzyr
相关产品推荐
相关产品推荐

