mysql 实现非递归树_(无限级、非递归)树形分类

记得之前有一次去面试,被问了无限级怎么做。我想很简单,就说了最基本的结构:id、name、parentid。

又被问就这样吗?显然不被满意。后面自然就没通过面试。

遇上技术型面试官,如果问的技术问题不被满意,大抵就没有下文了。遇上那些一副老子技术天下第一,狗眼看人低的面试官,那就自认倒霉吧。

遇上追问你如何规划人生,比如五年规划什么的。笔者现在只堪堪在温饱线上挣扎,只想提高点技术实力,多拿点工资。如果笔者有大能耐,就完全可以和面试官侃侃而谈雄心壮志,也可以完全不甩面试官,甚至可以发出一个鄙夷的眼光。奈何笔者没什么能耐,不得不认真回答面试官,却又因为回答的空泛无力,还要被面试官指手画脚一番。结果自然又是没有下文。

回去之后,在百度上翻一下非递归版的无限级。就是多了几个排序字段,在CRUD操作时,使每条保存在数据库的记录,按树形排序。避免了在获取数据时要通过递归组成树形结果的性能消耗。

毕竟树形分类在网站系统中,查询是最频繁的操作,反而在增删改操作极少。所以在保存到数据库时,为了按树形排序,增加了增删改的复杂度,却可以换来查询上性能的提升。

此方案的发明者无疑是充满智慧的。

不过我想,我只是刚好在实际工作中没有用到它而已。

没用过的技术或方案多了去。下次再遇上这种拿几个技术问题唧唧歪歪的面试官,哥决定了不甩他。

本方案是在mysql+ci php mvc下设计的,所以命名习惯随俗。

下面是表的设计结构

字段

说明

id

分类编号

parent_id

分类的父亲节点

name

分类名称

parent_path

父节点的路径,用于找到一个节点的子节点和子子节点或所有子节点。也可以找到一个节点的所有父节点,比如在删除节点时同时删除其下所有子节点

order_path

所有节点按树形排序,可以一个sql语句提取树形排序的分类,而不需要递归

level

第几级节点,可以和css配合,美化和层次化显示效果

order_id

辅助order_path完成同一级别下的排序

添加数据

有两种情况:1.添加根结点 2.添加子节点

1.添加根结点

需要注意,parent_path=‘0,’ ,

level=’0’ ,

order_id,'select max(order_id) as mOrder_id from info_class where

parent_id=0’

,mOrder_id存在,则order_id=mOrder_id+1,否则order_id=1 ,

order_path等于order_id前面补零,凑足四位。每一个级别4位的编码,可以存放0001~9999个分类,完全足够使用。

2.添加子节点

需要注意,parent_path=父节点的parent_path+父节点的id+‘,' ,

level,通过把parent_path转换为数组,得到数组长度来计算级别 ,

order_id,'select max(order_id) as mOrder_id from info_class where

parent_id=父节点’

,mOrder_id存在,则order_id=mOrder_id+1,否则order_id=1

order_path=父节点的order_path+order_id补零4位

代码如下:

function insert()

{

$data['name']=$this->input->post('info_class');

$data['style']=$this->input->post('info_style');

//if 'style' array is not empty, set it as string separate with ','. if($data['style'][0]!='') $data['style']=implode(',',$data['style']);

$data['parent_id']=$this->input->post('info_parent');

if($data['parent_id']=='0'){

//if add a root node $data['parent_path']='0,';

$this->level='0';

$this->db->select_max('order_id');

$this->db->where('parent_id',0);

$query=$this->db->get('mini_info_class');

$data['order_id']=$query->row()->order_id+1;

$data['order_path']=substr(strval($data['order_id']+1000),1,3);

}else{

//if add a child node $query=$this->info_class_model->GetClass(array('id'=>$data['parent_id']));

$data['order_path']=$query->order_path;

$data['parent_path']=$query->parent_path.$data['parent_id'].',';

$data['level']=count(explode(',',$data['parent_path']))-2;

$this->db->select_max('order_id');

$this->db->where('parent_id',$data['parent_id']);

$query=$this->db->get('mini_info_class');

$data['order_id']=$query->row()->order_id+1;

$data['order_path']=$data['order_path'].substr(strval($data['order_id']+1000),1,3);

}

$continue_add=$this->input->post('continue_add');

if($this->info_class_model->AddClass($data)){

if($continue_add=='1'){

$this->session->set_userdata(array('pre_url'=>'mini_admin/info_class_add'));

}

redirect('index.php/submit_success','refresh');

}

}

修改数据

有三种情况,1.根结点、或升级为根结点 2.不更改父节点 3.更改父节点

1.根结点、或升级为根结点

需要注意,parent_id=0 ,

parent_path=’0,’ ,

level=0 ,

如果是升级为根节点:

order_id,'select max(order_id) as mOrder_id from info_class where

parent_id=0’

,mOrder_id存在,则order_id=mOrder_id+1,否则order_id=1 ,

order_path等于order_id前面补零,凑足四位。每一个级别4位的编码,可以存放0001~9999个分类,完全足够使用。

2.不更改父节点

需要注意,parent_path=父节点的parent_path+父节点的id+‘,' ,

level,通过把parent_path转换为数组,得到数组长度来计算级别 ,

3.更改父节点

需要注意,parent_path=父节点的parent_path+父节点的id+‘,' ,

level,通过把parent_path转换为数组,得到数组长度来计算级别 ,

///

old_order_path=该节点的原order_path,如果该节点有子节点,修改成功后,将子节点的order_path中old_order_path部分替换为新生成的order_path

order_id,'select max(order_id) as mOrder_id from info_class where

parent_id=新父节点’

,mOrder_id存在,则order_id=mOrder_id+1,否则order_id=1

temp_order_path等于order_id前面补零,凑足四位。

order_path=新父节点的order_path+temp_order_path

代码如下:

function update($id)

{

$data['id']=$id;

$data['name']=$this->input->post('info_class');

$style=$this->input->post('info_style');

if($style[0]!='') $data['style']=implode(',',$style);

$data['parent_id']=$this->input->post('info_parent');

$order_path='';

$myRecord=$this->info_class_model->GetClass(array('id'=>$id));

$newParentRecord=$this->info_class_model->GetClass(array('id'=>$data['parent_id']));

if($data['parent_id']=='0'){

$data['parent_id']='0';

$data['parent_path']='0,';

$data['level']='0';

//if it's a children node before if($myRecord->parent_id!='0')

{

$this->db->select_max('order_id');

$this->db->where('parent_id',$data['parent_id']);

$query=$this->db->get('mini_info_class');

$data['order_id']=$query->row()->order_id+1;

$order_path=$order_path.substr(strval($data['order_id']+1000),1,3);

$data['order_path']=$order_path;

}

}else{

$data['parent_path']=$newParentRecord->parent_path.$data['parent_id'].',';

$data['level']=count(explode(',',$data['parent_path']))-2;

//get this node's parent_id if($myRecord->parent_id!=$data['parent_id']){

//if you change it's parent node, must update it's 'order_path' in synchronization. $old_order_path=$myRecord->order_path;

$new_order_path=$newParentRecord->order_path;

$order_path=$new_order_path;

$this->db->select_max('order_id');

$this->db->where('parent_id',$data['parent_id']);

$query=$this->db->get('mini_info_class');

$data['order_id']=$query->row()->order_id+1;

$order_path=$order_path.substr(strval($data['order_id']+1000),1,3);

$data['order_path']=$order_path;

$new_order_path=$order_path;

}

}

if($this->info_class_model->UpdateClass($data)){

if(isset($new_order_path)){

//if you change it's parent node, must update children's 'order_path' in synchronization. $data1=array('order_path'=>"replace('order_path','".$old_order_path."','".$new_order_path."')");

$this->db->where('parent_id',$id);

$this->db->update('mini_info_class',$data1);

}

redirect('index.php/submit_success','refresh');

}

}

上移下移、或插入

只能在同一级别内,进行上移下移、或插入

1.上移

假设待上移节点为currentNode

根据currentNode获取上一个节点,再根据上一个节点,获取上上一个节点就是待插入位置的节点,假设为brotherNode,然后就可以把currentNode上移到brotherNode之下了。

接下来,

获取currentNode的父节点的order_path,假设为father_order_path

设定currentNode的order_id=brotherNode的order_id+1

设定currentNode的order_path=father_order_path+currentNode的order_id补零四位

设定currentNode同一级别的其他节点,满足order_id大于brotherNode的order_id(不包括currentNode)条件的节点,order_id+1,order_path=father_order_path+order_id补零四位。

2.下移、插入

下移、插入都是在某一个节点下插入,区别是下移是在下一个节点下插入,而插入是选择在某一节点下插入

获取待下移节点为currentNode

获取待插入位置的节点为brotherNode

接下来,

获取currentNode父节点的order_path,假设为father_order_path

设定currentNode的order_id=brotherNode的order_id+1

设定currentNode的order_path=father_order_path+currentNode的order_id补零四位

设定currentNode同一级别的其他节点,满足order_id大于brotherNode的order_id(不包括currentNode)条件的节点,order_id+1,order_path=father_order_path+order_id补零四位。

上移和 下移、插入算法基本一致,只有获取待插入位置节点稍微有点不同。

查询数据

select * from mini_info_class order by order_path

id

parent_id

parent_path

name

order_path

order_id

level

20

0

0,

天庭

002

2

0

27

20

0,20,

日游神

002001

1

1

6

0

0,

冥府

004

4

0

26

6

0,6,

鬼差

004002

2

1

11

0

0,

妖域

005

5

0

7

0

0,

魔界

006

6

0

12

0

0,

人间道

007

7

0

29

0

0,

修罗道

008

8

0

28

29

0,29,

阿修罗

008001

1

1

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值