【最常用的递归方法】
先给出递归数据库
create table student
(
id int primary key comment '主键id',
name varchar(50) default '会员姓名',
pid int(11) not null comment '上级id'
)
给出递归Model层代码(这里只包含读取方法,插入省略.. 自行生成):
<?php
namespace app\index\model;
use think\Model;
class Member extends Model
{
/*
* 根据参数$id 获取其下级所有会员
* @param int 参数id
* @return array 结果数组
*/
public function get_member(int $id):array
{
$member=new self();
return self::get_next($member->field('id,pid')->select(),$id);
}
/*
* 获取下一级元素
* @param array 全部数据
* @param int 上级id
* @return array 返回数组
*/
public static function get_next(array $data,int $id):array
{
static $arr;
foreach($data as $k => $v)
{
if($v['pid']==$id)
{
$arr[]=$v['id'];
}
$this->get_next($data,$v['id']);
}
return $arr;
}
}
优缺点分析
优点: 可增加level字段实现层级分明、可实现统一排序。
缺点: 需要比较长io时间,一次性拉取大量数据可能会导致数据库崩溃。
总结: 该方法只适用于小型层级,并且有需要排序的需求。
【设置数据库缓存字段思路】
思路的来源于分散读取,如果我们在插入的时候就给予一定的标记,可轻松实现层级的查找。
示例数据库:
create table student
(
id int primary key comment '主键id',
name varchar(50) default '会员姓名',
lcache text not null comment '下级id缓存'
)
Model层代码示例:
<?php
namespace app\index\model;
use think\Model;
class Member extends Model
{
/*
* 获取参数id 的所有下级会员
* @param int id值
* @return array 结果数组
*/
public function get_member(int $id):array
{
$lcache=self::find($id)['lcache'];
return self::get_next(json_decode($lcache,true));
}
/*
* 获取缓存字段内的所有下级会员
* @param array 缓存数组
* @return array 结果数组
*/
public static function get_next(array $lcache):array
{
static $arr;
foreach($lcache as $v)
{
$arr[]=$v;
$lcache_child=self::find($v)['lcache'];
if(!(bool)$lcache_child)return continue;
self::get_next(json_decode($lcache_child,true));
}
return $arr;
}
}
优缺点分析
优点: 可以配合数据库索引,最快获得下级,分散数据库一次性读取的压力,读取当前id的下一级 有着超高的效率。
缺点: 频繁读取数据库在多次 tcl 消耗和io 消耗上 比较严重。并且大量数据的情况下读取全部层级同样较为费时,同样由于分散了读取的压力导致在插入、修改、删除数据库的时候需多次observer的执行。
总结: 该方法要求快速获取下级时 非常实用,适合分批次加载。
【id 链路实现快速读取】
实现思路同样是分散读取压力,在写入时 做适当的标记
先贴出数据库的设计
create table student
(
id int primary key comment '主键id',
name varchar(50) default '会员姓名',
pid int(11) not null comment '上级id',
chain text not null comment 'id链'
)
在贴出代码的设计
<?php
namespace app\index\model;
use app\exception\ModException;
use think\model;
class Member extends model
{
/**
* 新增一个会员
* @param string $name 插入会员姓名
* @param int $id 插入会员归属 id
* @return array 返回结果数组
*/
public function insert_member(string $name,int $id):bool
{
$mem = new self();
$chain= ($mem->find($id))['chain'];
if ($mem->insert([
'name' => $name,
'pid' => $id,
'chain' => $chain. '-' . $id
])return true;
}
/**
* 读取会员
* @param $id 读取参数id 会员的下属会员
* @param bool 是否只读取下一级
* @return bool 插入状态
*/
public function get_member(int $id,bool $is_one=true):array
{
$member=new self();
//读取下一级
if($is_one)return ($member->where('pid',$id)->select())->toArray();
//读取下属所有会员
$where_str=$member->find($id)['chain'].'-'.$id.'%';
return ($member->where('chain','like',$where_str)->select)->toArray();
}
}
优缺点分析
优点: 该方法综合以上两种思路,既可以实现快速读取下一层,也可以快速读取所有下属层级,也可以实现分页加载效果,最重要的是数据库压力小便于优化。
缺点: 同样需要在修改删除更新等操作时 使用observer 修改id 链路。
总结: 该方法在要求大量数据,并且需要快速读取时很实用,虽然需要observer协助,但是并不是过于复杂,适合大量数据使用。
【总结一下】
方法一适合小型的层级系统,比如树形结构,目录加载等等。通过内置的逻辑基本不需要额外的逻辑操作。
方法二适用于对下级查询要求极高的情况,性价比是三个方法里比较低的一种。
方法三适用于大量数据查询的情况,既分散了数据读取的压力,也不会给写入修改 造成太大的麻烦,大量数据情况下的优选。