Laravel Eloquent 关联查询实现每父级限制子记录数量
在使用 Laravel 的 Eloquent ORM 进行关联查询时,我们经常会遇到这样的需求:查询父模型的同时,关联查询子模型,并且希望每个父级对应的子记录只返回固定数量的条目,而不是全部返回。比如查询文章列表时,每篇文章只返回最新的3条评论,这种场景就需要用到每父级限制子记录数量的处理方式。
基础场景准备
我们先假设有两个常见的模型:Post(文章模型)和Comment(评论模型),它们之间是一对多的关系:一篇文章可以有多个评论,一个评论只属于一篇文章。模型的基础定义如下:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
// 文章模型
class Post extends Model
{
// 定义与评论的一对多关联
public function comments()
{
return $this->hasMany(Comment::class);
}
}
// 评论模型
class Comment extends Model
{
// 定义与文章的反向关联
public function post()
{
return $this->belongsTo(Post::class);
}
}常规关联查询的问题
如果我们直接使用with方法加载关联,会返回所有关联的子记录:
// 查询所有文章,同时加载所有关联的评论
$posts = Post::with('comments')->get();
// 遍历输出时,每篇文章的comments属性会包含所有评论
foreach ($posts as $post) {
echo "文章ID:{$post->id},评论数量:" . count($post->comments) . "<br>";
}但我们的需求是每篇文章只返回最新的3条评论,上面的写法显然不符合要求,这时候就需要对关联查询进行限制。
使用 with 结合闭包限制子记录数量
Eloquent 的with方法支持传入数组形式的关联配置,我们可以对关联查询添加闭包,在闭包中对子查询进行排序和数量限制:
// 查询文章,每篇文章只加载最新的3条评论
$posts = Post::with(['comments' => function ($query) {
// 按评论创建时间倒序排序,保证拿到的是最新的评论
$query->orderBy('created_at', 'desc')
// 限制只返回3条
->limit(3);
}])->get();
// 验证结果
foreach ($posts as $post) {
echo "文章ID:{$post->id},最新3条评论内容:<br>";
foreach ($post->comments as $comment) {
echo "- {$comment->content}(创建时间:{$comment->created_at})<br>";
}
echo "<br>";
}这种方式是最常用的实现方式,它的原理是在加载关联时,针对每个父级的关联查询单独添加排序和限制条件,最终每个父级只会拿到指定数量的子记录。需要注意的是,这里的排序是必须的,如果不排序直接限制数量,拿到的是数据库默认顺序的前3条,不一定是符合业务需求的最新记录。
定义带限制的关联方法
如果这个限制子记录数量的需求在多个地方都会用到,我们可以把限制逻辑封装到模型的关联方法中,方便复用:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Post extends Model
{
// 普通的全量评论关联
public function comments()
{
return $this->hasMany(Comment::class);
}
// 只返回最新3条评论的关联方法
public function latestThreeComments()
{
return $this->hasMany(Comment::class)
->orderBy('created_at', 'desc')
->limit(3);
}
}使用时直接调用新的关联方法即可:
// 直接调用封装好的关联方法
$posts = Post::with('latestThreeComments')->get();
foreach ($posts as $post) {
echo "文章ID:{$post->id},最新3条评论数量:" . count($post->latestThreeComments) . "<br>";
}分页场景下的注意事项
如果父级模型需要分页,这种做法依然有效,因为限制子记录数量的逻辑是在关联查询阶段处理的,不会影响父级的分页结果:
// 父级文章分页,每页10条,每篇文章带最新3条评论
$posts = Post::with(['comments' => function ($query) {
$query->orderBy('created_at', 'desc')->limit(3);
}])->paginate(10);
// 分页输出
foreach ($posts as $post) {
echo "文章标题:{$post->title}<br>";
}
// 渲染分页链接
echo $posts->links();常见误区提醒
- 不要在父级查询上使用
limit,那是限制父级记录的数量,不是限制子记录的数量。 - 如果关联查询没有加排序条件,直接用
limit拿到的子记录是不确定的,一定要根据业务需求添加合适的排序规则。 - 使用闭包限制关联时,闭包里的查询只对当前关联生效,不会影响其他地方的关联查询。
以上就是 Laravel Eloquent 中实现每父级限制子记录数量的几种常用方式,实际开发中可以根据场景选择直接在with中写闭包,或者封装到模型关联方法中复用。