需手动构造left join链或子查询实现嵌套集查询,因eloquent默认不支持;建表时lft、rgt须为无符号整型并建联合索引;根节点lft=1、rgt=2、depth=0须手动插入;查祖先推荐先获取目标节点再用lft/rgt范围查询。

在Laravel中实现嵌套集模型(Nested Set Model)的嵌套查询,需绕过Eloquent默认的扁平关联逻辑,手动构造LEFT JOIN链或使用子查询模拟层级遍历,否则无法一次性获取完整树形路径或深度限定的子树。
创建支持嵌套集的数据库表结构
执行迁移命令生成数据表:php artisan make:migration create_categories_table。
在迁移文件中定义lft、rgt、depth字段:Schema::create('categories', function (Blueprint $table) { $table->id(); $table->string('name'); $table->unsignedBigInteger('lft'); $table->unsignedBigInteger('rgt'); $table->unsignedTinyInteger('depth')->default(0); $table->index(['lft', 'rgt']); });
注意:lft和rgt必须为无符号整型且建立联合索引,否则WHERE lft BETWEEN ? AND ?查询会全表扫描,性能急剧下降。
插入根节点并初始化左右值
手动插入首条记录时,lft设为1,rgt设为2,depth为0:DB::table('categories')->insert(['name' => '电子设备', 'lft' => 1, 'rgt' => 2, 'depth' => 0]);
这一步不可用Model::create(),因为Eloquent不会自动计算lft/rgt——必须显式赋值,否则后续所有嵌套查询的范围判断全部失效。
查询指定节点的所有祖先(向上追溯)
方法一:使用whereRaw配合子查询
Category::whereRaw('lft whereRaw('rgt > (SELECT rgt FROM categories WHERE id = ?)', [$targetId])->orderBy('lft')->get();
方法二:先查出目标节点的lft/rgt再复用
$node = Category::find($targetId); if (!$node) return collect(); return Category::where('lft', 'lft)->where('rgt', '>', $node->rgt)->orderBy('lft')->get();
方法二更快,避免重复子查询,但必须确保$node存在,否则where条件变成where('lft', '
查询指定节点的直接子节点(仅一级)
第一步:获取目标节点的depth值
$parent = Category::findOrFail($parentId);
第二步:查找depth = $parent->depth + 1 且 lft 在 parent.lft 和 parent.rgt 之间的节点
return Category::where('depth', $parent->depth + 1)->whereBetween('lft', [$parent->lft, $parent->rgt])->orderBy('lft')->get();
关键点在于必须用whereBetween而非WHERE lft > ? AND rgt
递归获取整个子树(含所有后代)
使用withDepth扩展包可简化操作,但原生实现需依赖闭包变量传递深度:
$tree = Category::whereBetween('lft', [$root->lft, $root->rgt])->orderBy('lft')->get()->map(function ($item) use ($root) { return ['name' => $item->name, 'depth' => $item->depth - $root->depth]; });
这一步不触发额外SQL,纯内存计算层级偏移,【$root->depth必须在map前确定,不能在闭包内重新查库】,否则每次迭代都执行一次查询,N个节点产生N次数据库往返。











