在 Laravel 项目里,分类和文章通常是典型的关联关系,一个分类下可以有多篇文章,统计每个分类对应的文章数量是常见的业务需求。如果采用循环遍历分类再逐个查询文章数量的方式,会产生 N+1 次查询问题,数据量稍大就会明显拖慢接口响应速度,使用 Eloquent 提供的关联统计能力可以很好地解决这个问题。

基础关联配置
首先需要在分类模型和文章模型中定义好关联关系,这是使用 Eloquent 统计功能的前提。分类模型 Category 中定义与文章的关联:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Category extends Model
{
// 定义分类下的文章关联,一个分类有多个文章
public function articles()
{
return $this->hasMany(Article::class, 'category_id', 'id');
}
}文章模型 Article 中定义所属分类的关联:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Article extends Model
{
// 定义文章所属的分类关联
public function category()
{
return $this->belongsTo(Category::class, 'category_id', 'id');
}
}使用 withCount 预加载统计
Eloquent 提供的withCount方法可以在查询分类的同时,预加载关联的文章数量,只会生成两条 SQL 查询,性能远优于循环查询。
基础统计用法
直接调用 withCount 传入关联方法名,就可以在查询结果中自动生成 articles_count 字段存储统计数量:
<?php
use App\Models\Category;
// 查询所有分类,并统计每个分类下的文章数量
$categories = Category::withCount('articles')->get();
// 遍历输出结果
foreach ($categories as $category) {
echo "分类名称:{$category->name},文章数量:{$category->articles_count}" . PHP_EOL;
}这个方法生成的 SQL 逻辑是先查询所有分类,再通过一条子查询统计每个分类对应的文章数量,避免了循环查询的性能浪费。
自定义统计字段名
如果不想使用默认的 articles_count 作为字段名,可以在 withCount 中指定自定义的键名:
<?php
use App\Models\Category;
// 自定义统计字段名为 article_total
$categories = Category::withCount(['articles as article_total'])->get();
foreach ($categories as $category) {
echo "分类名称:{$category->name},文章总数:{$category->article_total}" . PHP_EOL;
}带条件的分类文章统计
实际业务中经常需要统计符合特定条件的文章数量,比如只统计已发布的文章,可以在 withCount 中闭包定义查询条件。
<?php
use App\Models\Category;
// 统计每个分类下已发布的文章数量,已发布状态为 status=1
$categories = Category::withCount(['articles as published_article_count' => function ($query) {
$query->where('status', 1);
}])->get();
foreach ($categories as $category) {
echo "分类名称:{$category->name},已发布文章数:{$category->published_article_count}" . PHP_EOL;
}查询时过滤分类
如果只需要查询有文章的分类,或者需要按文章数量排序,可以结合 having 和 orderBy 实现:
<?php
use App\Models\Category;
// 查询文章数大于0的分类,并按文章数降序排序
$categories = Category::withCount('articles')
->having('articles_count', '>', 0)
->orderBy('articles_count', 'desc')
->get();
foreach ($categories as $category) {
echo "分类名称:{$category->name},文章数量:{$category->articles_count}" . PHP_EOL;
}不同场景的选择建议
如果是简单的全量统计,直接使用 withCount 即可;如果需要统计不同条件的数量,可以在 withCount 中传入多个关联配置,一次性获取多个统计结果:
<?php
use App\Models\Category;
// 一次性统计总文章数、已发布文章数、草稿文章数
$categories = Category::withCount([
'articles as total_articles',
'articles as published_articles' => function ($query) {
$query->where('status', 1);
},
'articles as draft_articles' => function ($query) {
$query->where('status', 0);
}
])->get();这种方式只会生成一条分类查询和一条多字段统计的子查询,性能最优,适合需要多维度统计分类数据的场景。