数据库擅长操作的数据是无层次的数据,根据不同的sql语句提取特定的行运算。对于树型结构的存储第一次应用是做省市县三级列表。当时仅仅使用了三个字段id,pid,name。pid表示name的所属的父id。然后可以在服务器端组织好数据结构给客户端,也可以让客户端自行处理。但是现在需要做一个背单词的联想功能,层数是不确定的。根据一个单词需要把它的所有的子节点全部查出来,并且以树的形式展示出来,这就不能够用上面的存储方式,首先是因为上面的存储会导致数据库递归查找,效率不高。第二就是想要获取关于某个节点的子树的任何信息,不进行遍历是不能达到效果的,也是查询次数过多,效率问题。
https://www.sitepoint.com/hierarchical-data-database/,这篇文章详细介绍了数据库存储树形结构的两种方式,第一种就是上面介绍的哪种,另一种是一种新的存储方式。这种新的存储方式极大的简化了数据库的查询次数。
查询food的所有的节点
SELECT * FROM tree WHERE lft BETWEEN 1 AND 18;
更改Food的名字,直接更改就行
UPDATE tree set name = 'Foods' where name = 'Food'
在Red下加入新的子节点
UPDATE tree SET rgt=rgt+2 WHERE rgt>5;
UPDATE tree SET lft=lft+2 WHERE lft>5;
INSERT INTO tree SET lft=6, rgt=7, name='Strawberry';
删除草莓
DELETE FROM tree where name = 'Strawberry'
UPDATE tree SET rgt=rgt-2 WHERE rgt>7;
UPDATE tree SET lft=lft-2 WHERE lft>7;
操作很简单了,接下来是上面前端的展示,最简单的就是按照层次打印,如图
但是这样显示效果不好,所以网上搜索了下树的展示。参考这里https://jsfiddle.net/Limitlessisa/5Lhb0ron/
发现这里是根据css设置的,而且每一个节点都是独立的。所以可以参考这里把树按照这样的方式展示出来。
实现代码基于TP
public function treeEdit(Request $request)
{
$action_type = $request->param('action_type', '');
$root_name = $request->param('root_name', '');
if (empty($root_name) && $action_type != 'add_root_node'){
return json(['status' => -1,'msg' => '没有根节点名称']);
}
if ($action_type == 'add_root_node') { //添加根节点
$new_name = $request->param('root_name');
Db::table('h_dict_tree')->insert(['name' => $new_name, 'root' => $new_name, 'parent' => '','lft' => 1,'rgt' => 2]);
return json(['status' => 0,'msg' => '添加根节点成功']);
} elseif ($action_type == 'add_child_node' || $action_type == 'edit_node_name' || $action_type == 'add_brother_node') { //添加子节点
$origin_name = $request->param('origin_name', '');
if (empty($origin_name)) {
return json(['status' => -1,'msg' => '请选择节点']);
}
$new_name = $request->param('new_name','');
if (empty($new_name)) {
return json(['status' => -1, '请输入新名称']);
}
$exist_nodes = Db::table('h_dict_tree')->where('root', $root_name)->column('name');
if (in_array($new_name, $exist_nodes)) {
return json(['status' => -1,'msg' => '节点已存在']);
}
if ($action_type == 'add_child_node'){
$parent_node = Db::table('h_dict_tree')->where(['root' => $root_name, 'name' => $origin_name])->find();
Db::table('h_dict_tree')->where(['rgt' => ['gt', $parent_node['rgt']-1],'root' => $root_name])->update(['rgt' => array('exp', 'rgt+2')]);
Db::table('h_dict_tree')->where(['lft' => ['gt', $parent_node['rgt']-1],'root' => $root_name])->update(['lft' => array('exp', 'lft+2')]);
Db::table('h_dict_tree')->insert(['name' => $new_name, 'lft' => $parent_node['rgt'],'rgt' => $parent_node['rgt']+1, 'root' => $root_name, 'parent' => $origin_name]);
return json(['status' => 0,'msg' => '添加子节点成功']);
}else if($action_type == 'edit_node_name'){
Db::table('h_dict_tree')->where(['name' => $origin_name, 'root' => $root_name])->update(['name' => $new_name]);
Db::table('h_dict_tree')->where(['parent' => $origin_name, 'root' => $root_name])->update(['parent' => $new_name]);
if ($root_name == $origin_name){
Db::table('h_dict_tree')->where(['root' => $root_name])->update(['root' => $new_name]);
}
return json(['status' => 0,'msg' => '更改节点名成功']);
}else {
if ($origin_name == $root_name) {
return json(['status' =>-1,'msg' => '根节点不能添加兄弟节点']);
}
$add_pos = $request->param('add_pos','left');
$brother_node = Db::table('h_dict_tree')->where(['root' => $root_name, 'name' => $origin_name])->find();
if ($add_pos == 'right') {
Db::table('h_dict_tree')->where(['lft' => ['gt', $brother_node['rgt']],'root' => $root_name])->update(['lft' => array('exp', 'lft+2')]);
Db::table('h_dict_tree')->where(['rgt' => ['gt', $brother_node['rgt']],'root' => $root_name])->update(['rgt' => array('exp', 'rgt+2')]);
Db::table('h_dict_tree')->insert(['name' => $new_name, 'lft' => $brother_node['rgt']+1,'rgt' => $brother_node['rgt']+2, 'root' => $root_name, 'parent' => $brother_node['parent']]);
}elseif ($add_pos == 'left') {
Db::table('h_dict_tree')->where(['lft' => ['gt', $brother_node['lft']-1],'root' => $root_name])->update(['lft' => array('exp', 'lft+2')]);
Db::table('h_dict_tree')->where(['rgt' => ['gt', $brother_node['lft']-1],'root' => $root_name])->update(['rgt' => array('exp', 'rgt+2')]);
Db::table('h_dict_tree')->insert(['name' => $new_name, 'lft' => $brother_node['lft'],'rgt' => $brother_node['lft']+1, 'root' => $root_name, 'parent' => $brother_node['parent']]);
}
return json(['status' => 0, '加入成功']);
}
} elseif ($action_type == 'delete_node') { //删除节点
$del_name = $request->param('origin_name','');
$count = Db::table('h_dict_tree')->where(['parent' => $del_name, 'root' => $root_name])->count('*');
if ($count == 0){
$data = Db::table('h_dict_tree')->where(['name'=> $del_name,'root' => $root_name])->find();
Db::table('h_dict_tree')->where(['lft' => ['gt',$data['rgt']],'root' => $root_name])->update(['lft' => array('exp', 'lft-2')]);
Db::table('h_dict_tree')->where(['rgt' => ['gt',$data['rgt']],'root' => $root_name])->update(['rgt' => array('exp', 'rgt-2')]);
Db::table('h_dict_tree')->where(['name' => $data['name'], 'root' => $root_name])->delete();
return json(['status' => 0,'msg' => '删除节点成功']);
}else{
return json(['status' => -1,'msg' => '不能删除非叶子节点']);
}
}
}
{extend name='include/head'/}
{block name='title'}单词树{/block}
{block name="menu"}添加单词{/block}
{block name='content'}
{$tree_div}
{if $has_root == 1}
已选择
节点内容
操作
{else/}
根节点名
{/if}
{/block}