
本文介绍在 laravel 中如何递归统计任意层级分类下所有子分类所关联的商品总数,适用于“仅叶子节点关联商品”的典型树形结构场景。
本文介绍在 laravel 中如何递归统计任意层级分类下所有子分类所关联的商品总数,适用于“仅叶子节点关联商品”的典型树形结构场景。
在典型的电商或内容管理系统中,分类常以树形结构组织(如 category 表的 parent 字段),而商品(Product)通常只直接关联到最深层的“叶子分类”(如题中所述:仅第 3 级分类 C-1、C-2 关联商品)。此时,若需向上聚合统计——例如查询根分类 A 下全部子分类共关联多少商品——就不能依赖简单的 withCount() 或单层 hasMany,而必须实现跨 N 层的递归聚合。
✅ 正确解法:使用递归关系 + 子查询聚合(推荐)
Laravel 原生不支持直接 withCount('subCategories.products') 的多层嵌套计数,但可通过定义一个递归访问器(accessor)结合子查询优雅解决。关键在于:先获取当前分类及其所有后代 ID,再一次性统计这些分类下关联的商品总数。
在 Category 模型中添加如下方法:
// app/Models/Category.php
public function products()
{
return $this->belongsToMany(Product::class, 'category_product'); // 根据实际中间表调整
}
// 获取当前分类及其所有后代分类的 ID 集合(含自身)
public function getAllDescendantIdsAttribute()
{
$ids = [$this->id];
$stack = [$this->id];
while (!empty($stack)) {
$parentId = array_pop($stack);
$children = static::where('parent', $parentId)->pluck('id')->toArray();
$ids = array_merge($ids, $children);
$stack = array_merge($stack, $children);
}
return $ids;
}
// 计算本分类及所有子分类下的商品总数(核心逻辑)
public function getProductsCountAttribute()
{
return Product::whereHas('categories', function ($q) {
$q->whereIn('category_id', $this->all_descendant_ids);
})->count();
}
? 使用示例:
$categoryA = Category::find(1); echo $categoryA->products_count; // 输出 8
⚠️ 注意事项与优化建议
- 性能考量:上述递归 ID 收集适合中小型分类树(descendant_count)提升查询效率。
-
关系命名一致性:确保
Product模型中正确定义了反向多对多关系,例如:// in Product.php public function categories() { return $this->belongsToMany(Category::class, 'category_product'); } -
避免 N+1 查询:切勿在循环中调用
$category->products_count——应提前用withCount()或批量预加载优化,或改用数据库原生递归查询(MySQL 8.0+ 支持WITH RECURSIVE)。
✅ 总结
统计 N 层分类的商品总数,本质是「自顶向下收集后代 ID + 自底向上聚合计数」。本文提供的方案兼顾可读性、可维护性与 Laravel 生态兼容性,无需额外扩展包即可落地。对于高并发或超大规模分类系统,可进一步结合缓存(如 Redis 存储各分类的 products_count)或物化视图优化响应速度。










