Laravel关联查询中take()返回空集合问题排查
Laravel多对多关联:部分分类关联文章返回空集合问题
问题场景
我想获取4个关联有文章的分类,且每个分类仅返回4篇文章,用了这段代码:
$categoryArticles = Category::whereHas('articles')->with(['articles' => function($q){ $q->select('id', 'title')->take(4); }])->take(4)->get();
结果部分分类的articles关联是空集合;但去掉关联闭包里的take(4)后,所有分类都能正常返回关联文章:
$categoryArticles = Category::whereHas('articles')->with(['articles' => function($q){ $q->select('id', 'title'); }])->take(4)->get();
相关代码
模型定义
Article.php:
public function categories() { return $this->belongsToMany(Category::class, 'article_category'); }
Category.php:
public function articles() { return $this->belongsToMany(Article::class, 'article_category'); }
中间表结构
Schema::create('article_category', function (Blueprint $table) { $table->unsignedBigInteger('article_id'); $table->unsignedBigInteger('category_id'); $table->foreign('article_id')->references('id')->on('articles')->onDelete('cascade'); $table->foreign('category_id')->references('id')->on('categories')->onDelete('cascade'); });
问题原因
- 字段缺失导致关联匹配失败:关联查询的
select只取了id和title,但多对多关联需要中间表的category_id来把文章和分类对应起来,没包含这个字段的话,Laravel无法完成匹配,自然返回空集合。 - take()的全局限制问题:即使加上了
category_id,直接在关联闭包里用take(4)也不是按每个分类取4条,而是全局取前4条文章,再分配到对应的分类上——如果某个分类的关联文章不在这前4条里,该分类的articles就会是空的。
解决方案
方案1:子查询实现每个分类取4篇文章
通过子查询针对每个分类单独限制文章数量,同时确保查询包含中间表的关联字段:
$categoryArticles = Category::whereHas('articles') ->with(['articles' => function ($query) { $query->select('articles.id', 'articles.title', 'article_category.category_id') ->whereIn('articles.id', function ($subQuery) { $subQuery->select('article_id') ->from('article_category') ->whereColumn('category_id', 'categories.id') ->limit(4); }); }]) ->take(4) ->get();
方案2:自定义关联方法
在Category模型里新增一个专门返回前4篇文章的关联:
// Category.php public function topFourArticles() { return $this->belongsToMany(Article::class, 'article_category') ->select('articles.id', 'articles.title', 'article_category.category_id') ->limit(4); }
然后调用这个关联即可:
$categoryArticles = Category::whereHas('articles') ->with('topFourArticles') ->take(4) ->get();
关键注意事项
- 多对多关联的查询必须包含中间表的关联字段(比如这里的
article_category.category_id),否则无法完成文章与分类的匹配。 - 不要直接在关联闭包里用
take()/limit()来实现“每个分类N条”的需求,必须通过子查询或自定义关联的方式,确保限制是针对单个分类而非全局结果。
内容的提问来源于stack exchange,提问作者K1-Aria
相关产品推荐
相关产品推荐

