Laravel Eloquent with()中take()无法按分类加载指定数量关联记录
解决Laravel 10 Eloquent预加载关联时
take()仅生效于第一个分类的问题 问题原因
你当前的写法之所以只给第一个分类加载6篇文章,是因为Eloquent的预加载逻辑:先查询所有分类,再执行一条SELECT * FROM articles WHERE category_id IN (分类ID列表) LIMIT 6的语句。这里的LIMIT 6是对整个关联查询结果集取前6条,而非每个分类单独取6条,所以这些结果基本都会属于ID最小的那个分类,其他分类自然拿不到关联数据。
解决方案
下面提供三种可行的解决方式,你可以根据项目的数据库环境和性能需求选择:
方法1:自定义关联(兼容性好)
在Category模型中定义一个新的关联方法,通过子查询实现每个分类取最多6篇文章:
// Category.php public function limitedArticles() { return $this->hasMany(Article::class)->whereIn('id', function ($query) { $query->select('id') ->from('articles') ->whereColumn('category_id', 'categories.id') ->orderBy('created_at', 'desc') // 可根据需求调整排序字段 ->take(6); }); }
使用时直接调用这个新关联:
$categories = Category::with('limitedArticles')->get()->toArray();
方法2:集合处理(简单直观,适合分类数量少的场景)
先获取所有分类,再逐个加载对应分类的前6篇文章。注意:这种方式会触发N+1查询,分类数量多的时候不推荐使用:
$categories = Category::get()->map(function ($category) { $category->articles = $category->articles()->take(6)->get(); return $category; })->toArray();
方法3:窗口函数(效率最高,需数据库支持)
如果你的数据库支持窗口函数(如MySQL 8+、PostgreSQL),可以用一次查询完成所有分类的关联数据获取,性能最优:
use Illuminate\Support\Facades\DB; $categories = Category::query() ->select('categories.*') ->joinSub( function ($query) { $query->select( 'articles.*', DB::raw('ROW_NUMBER() OVER (PARTITION BY category_id ORDER BY created_at DESC) as rn') )->from('articles'); }, 'filtered_articles', 'categories.id', '=', 'filtered_articles.category_id' ) ->where('filtered_articles.rn', '<=', 6) ->get() ->groupBy('id') ->map(function ($group) { $category = $group->first(); $category->articles = $group->pluck('filtered_articles')->flatten(); return $category; }) ->values() ->toArray();
总结
- 若数据库版本较低,优先选方法1;
- 分类数量少、追求代码简洁选方法2;
- 追求性能且数据库支持窗口函数选方法3。
内容的提问来源于stack exchange,提问作者badr aldeen
相关产品推荐
相关产品推荐

