推荐使用数据库原生CTE递归查询实现Laravel 11.x无限级分类,MySQL 8.0+和PostgreSQL均支持,可通过DB::select()或fromRaw()手动构建SQL,模型层宜封装为scope方法以提升复用性。

在 Laravel 11.x 中实现无限级分类(如菜单、栏目、部门树),推荐使用数据库原生的 CTE(Common Table Expressions)递归查询,尤其适用于 MySQL 8.0+ 或 PostgreSQL。Laravel 11 原生不直接封装 CTE 语法,但可通过 DB::select() 或 Query Builder 的 fromRaw() + unionAll() 手动构建递归 SQL。
MySQL 8.0+ 递归 CTE 查询写法(含 Laravel 调用)
假设有一张 categories 表,结构为:id, name, parent_id(根节点 parent_id = 0 或 NULL)。
要查出某个分类(如 id=1)的所有子子孙孙(含自身),可这样写:
WITH RECURSIVE category_tree AS ( -- 锚点:起始节点 SELECT id, name, parent_id, 0 AS depth FROM categories WHERE id = 1 <p>UNION ALL</p><p>-- 递归:找子节点 SELECT c.id, c.name, c.parent_id, ct.depth + 1 FROM categories AS c INNER JOIN category_tree AS ct ON c.parent_id = ct.id ) SELECT * FROM category_tree ORDER BY depth, id;</p>
Laravel 中调用示例:
- 用
DB::select()直接执行(最简单):
use Illuminate\Support\Facades\DB;
<p>$results = DB::select("
WITH RECURSIVE category_tree AS (
SELECT id, name, parent_id, 0 AS depth
FROM categories
WHERE id = ?
UNION ALL
SELECT c.id, c.name, c.parent_id, ct.depth + 1
FROM categories AS c
INNER JOIN category_tree AS ct ON c.parent_id = ct.id
)
SELECT * FROM category_tree ORDER BY depth, id
", [1]);</p>- 注意:MySQL 要求开启 CTE 支持(默认 8.0+ 已启用),且不能在子查询或视图中嵌套递归 CTE。
PostgreSQL 递归 CTE 写法(兼容 Laravel)
PostgreSQL 语法更标准,支持 RECURSIVE 关键字和 CONNECT BY 风格别名:
WITH RECURSIVE tree AS ( SELECT id, name, parent_id, 0 AS level FROM categories WHERE id = 1 <p>UNION ALL</p><p>SELECT c.id, c.name, c.parent_id, t.level + 1 FROM categories c INNER JOIN tree t ON c.parent_id = t.id ) SELECT * FROM tree ORDER BY level, id;</p>
Laravel 调用方式同上,仅需替换 SQL 字符串。
用 Query Builder 模拟递归(兼容低版本 MySQL)
若数据库不支持 CTE(如 MySQL 5.7),Laravel 无法真正“递归”,只能靠 PHP 多次查询或预计算路径(如 path 字段)。但若坚持用 Builder “拼” CTE,可借助 fromRaw 和 unionAll 构建基础结构(仅限简单场景,不推荐深度递归):
- 手动展开有限层级(如最多 4 级),用
leftJoin连表; - 或用
DB::raw()+fromRaw('(...) AS t')包裹完整 CTE SQL; - 不建议在 Builder 中用 unionAll 拼递归逻辑——它不支持自引用,无法替代真正的 CTE。
模型层封装建议(Laravel 11 推荐做法)
将 CTE 查询封装进 Eloquent 模型的 scope 或自定义方法中,保持复用性:
// app/Models/Category.php
class Category extends Model
{
public function scopeWithDescendants($query, $id)
{
return $query->fromRaw("
WITH RECURSIVE category_tree AS (
SELECT id, name, parent_id, 0 AS depth
FROM categories
WHERE id = ?
UNION ALL
SELECT c.id, c.name, c.parent_id, ct.depth + 1
FROM categories AS c
INNER JOIN category_tree AS ct ON c.parent_id = ct.id
)
SELECT * FROM category_tree
", [$id]);
}
}
<p>// 使用
$tree = Category::withDescendants(1)->get();</p>⚠️ 注意:该写法返回的是 stdClass 对象集合(非模型实例),如需模型功能,建议查出 ID 后再用 whereIn('id', [...]) 加载。


















