高性能无限级分类实现思路
表结构

废话不多说,直接上结果
- example:查询北京下所有子节点(包含北京)
select * from cy_address where tree like '0-1-2-%' or id = 2 order by id asc

model代码
<?php
namespace App\Models;
use Illuminate\Support\Facades\DB;
class Address extends BaseModel
{
protected $table = 'address';
protected $guarded = [];
protected $primaryKey = 'id';
public $timestamps = false;
public static function boot()
{
parent::boot();
# 更新节点信息
static::created(function ($model) {
if ($model->parentid) {
$parent = static::find($model->parentid);
static::find($model->id)->update([
'keyword' => implode(',', parse_pinyin($model->name)),
'tree' => $parent->tree . '-' . $model->id
]);
$parent->update(['child' => 1]);
} else {
static::find($model->id)->update([
'keyword' => implode(',', parse_pinyin($model->name)),
'tree' => '0-' . $model->id,
]);
}
});
static::updating(function ($model) {
$dirty = $model->getDirty();
if (isset($dirty['name'])) {
$model->keyword = implode(',', parse_pinyin($model->name));
}
# 移动节点,更新自身tree
if (isset($dirty['parentid'])) {
$parent = static::find($model->parentid);
$model->tree = $parent->tree . '-' . $model->id;
$parent->update(['child' => 1]);
}
});
static::updated(function ($model) {
$dirty = $model->getDirty();
$original = $model->getRawOriginal();
# 移动节点
if (isset($dirty['parentid'])) {
DB::select(DB::raw("update " . DB::getTablePrefix() . "address set tree = CONCAT('{$model->tree}-',id) where tree like '{$original['tree']}-%'"));
$child = static::where('parentid', $original['parentid'])->first();
if (!$child) {
static::find($original['parentid'])->update(['child' => 0]);
}
}
});
static::deleting(function ($model) {
$tree = $model->tree;
static::where('tree', 'like', "{$tree}-%")->delete();
});
// 更新child状态
static::deleted(function ($model) {
$parent = static::find($model->parentid);
if ($parent->getAllChild()->count() == 1) {
$parent->update(['child' => 0]);
}
});
}
# 获取所有子节点
public function getAllChild()
{
return static::where('tree', 'like', "{$this->tree}-%")->orWhere('id', $this->id)->orderBy('id', 'ASC')->get();
}
}
本作品采用《CC 协议》,转载必须注明作者和本文链接
关于 LearnKu
不懂就问,直接查parentid,比查tree这个字段低效吗?
全路径的设计没有问题,是为了提高查询性能,你这个SQL存在问题 like '%0-1-2-%',需要改为’0-1-2-%'才能走索引
查询一时爽,维护火葬场,,,
不过这个省市三级,不需要变更层级关系,用这个确实可以,,,
mysql 8 支持通用表达式,可以实现递归查询。
移动节点和修改层级后的全路径如何维护呢,能否指点一下
一次查出来
php处理也行啊我目前的做法是用parentid . 一次性读取全部目录出来然后再内存递归组装。这种方式没有任何性能问题。只是增加了代码复杂度,要各种拼接。所以我也蛮希望有一种更好的解决方案。不过目前来看,好像你这种方式并不算更优的解决方案,还不如内存组装。
可以考虑使用预排序遍历树实现,感兴趣可以查看: github.com/lazychaser/laravel-nest...
查找所有后代或所有祖先:
组装树形结构:
使用模型的新增更新和删除会自动维护字段,代码无感知,但内部可能会执行多条sql,数据量大时效率低
@91hero 就是将数据一次性全部取出,然后放在内存中进行遍历,只不过这个方法用的是引用传值方式,会相应的减少一些内存(相比较递归来说),而且效率高一些。代码不难,你可以试着理解一下
@91hero
tree字段如果加了索引,SQL其实不需要or id = 2了其实这种就是路径查询 tree建议写成/id/,就是写入的时候加入路径,查询的时候比较方便
请问这种如何支持顶级分页查询?并且支持一定的搜索