Mysql 采用树前序算法优化无限层级结构
前序遍历:
特点:前序遍历首先访问根结点然后遍历左子树,最后遍历右子树。在遍历左、右子树时,仍然先访问根结点,然后遍历左子树,最后遍历右子树。
这种方式的表结构,可以很方便的找到对应节点的所有子节点或父节点的信息。
- 具体实现步骤如下,前序遍历二叉树图:
- 数据结构(主要字段)
- 主要思路
- 根据记录左右节点获取子孙节点或父子节点。
- 如:南方集团深圳分部的子孙节点为的条件为:l_node > 2 and r_node < 15
- 如:中医骨科所有的父节点条件为: l_node < 4 and r_node > 9
- 难点:
1
2新增、修改、删除时业务逻辑相对复杂,需要对影响到列所有数据的左右节点做修改。
如果业务中已经存在数据,只需要新增两个节点字段 l_node ,r_node 然后根据parent_id 初始化相应的值 - 初始化已存在数据的存储过程:
1
2
31、该存储过程适用于原始数据按照parent_id 升序排列时 当前记录的父级记录一定要在前面
可以在记录中记录该情况的数据,当其它记录数据处理完成后再处理一次这些记录的数据。
2、查询集合时要过滤掉删除的记录和不存在父级的记录(可以到该存储过程中的查询语句中加相应的条件)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50DELIMITER $$
DROP PROCEDURE IF EXISTS `reset_node`$$
CREATE PROCEDURE `reset_node`()
BEGIN
DECLARE _id BIGINT DEFAULT 0;
DECLARE _pid BIGINT DEFAULT 0;
DECLARE _lnode BIGINT DEFAULT 0;
DECLARE _rnode BIGINT DEFAULT 0;
DECLARE _num BIGINT DEFAULT 1;
DECLARE pidtemp BIGINT DEFAULT 0;
DECLARE _done BIGINT DEFAULT 0;
DECLARE _count BIGINT DEFAULT 0;
DECLARE _isnull INT DEFAULT 0;
DECLARE _cur CURSOR FOR SELECT id,parent_id FROM tree_sort WHERE parent_id >0 ORDER BY parent_id ASC;
DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET _done = 1;#错误定义,标记循环结束
OPEN _cur;
REPEAT
FETCH _cur INTO _id,_pid;
SET _isnull = 0;
IF NOT _done THEN
IF _pid != pidtemp THEN -- 当前记录的pid = 初始化的pid
SELECT l_node,r_node INTO _lnode,_rnode FROM tree_sort WHERE id = _pid;
IF _lnode IS NOT NULL THEN
SET _isnull = 0;
SET pidtemp = _pid;
SET _num = 0; -- 记录当前父节点有没有子节点 0没有 1 有
ELSE
SET _isnull = 1;
END IF;
END IF;
IF _isnull = 0 THEN
IF _num = 0 THEN
UPDATE tree_sort SET l_node = l_node +2 WHERE l_node > _lnode;
UPDATE tree_sort SET r_node = r_node +2 WHERE r_node >= _rnode ;
UPDATE tree_sort SET l_node = _rnode,r_node = _rnode+1 WHERE id = _id;
SET _rnode = _rnode+1;
ELSE
UPDATE tree_sort SET l_node = l_node +2 WHERE l_node > _rnode;
UPDATE tree_sort SET r_node = r_node +2 WHERE r_node > _rnode;
UPDATE tree_sort SET l_node = _rnode+1,r_node = _rnode+2 WHERE id = _id;
SET _rnode = _rnode +2;
END IF;
END IF;
END IF;
UNTIL _done END REPEAT; #当_done=1时退出被循
CLOSE _cur;
END$$
DELIMITER ;
调用过程
CALL reset_node(); - 新增元素存储过程:删除一个节点或多个节点,只需要关注比大大的左右节点即可,然后对响应的节点做减操作即可。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24DROP PROCEDURE IF EXISTS drop_node;
DELIMITER $$
CREATE PROCEDURE drop_node(IN leftnode BIGINT,IN rightnode BIGINT,OUT result INT)
BEGIN
DECLARE sql_error INT DEFAULT 0;
DECLARE CONTINUE HANDLER FOR SQLEXCEPTION SET sql_error = 1;
START TRANSACTION;
DELETE FROM tree_sort WHERE l_node >= leftnode AND r_node <= rightnode;
UPDATE tree_sort SET l_node = l_node-(rightnode-leftnode+1) WHERE l_node > leftnode;
UPDATE tree_sort SET r_node = r_node-(rightnode-leftnode+1) WHERE r_node > rightnode;
IF (sql_error = 1) THEN
ROLLBACK;
ELSE
COMMIT;
END IF;
SET result = sql_error;
END
$$
DELIMITER ;
调用过程
SET @result = 0;
-- 参数分别为 删除元素的左节点值和右节点值 返回值 0 正常 1 异常
CALL drop_node(2,7,@result); - 编辑元素存储过程:
编辑元素相较于前面两者会相对的复杂一些。
主要思路(个人):如果有感觉比较好的想法的欢迎留言,谢谢!- 对需要移动的节点外的所有节点重新排列节点顺序,类似删除操作。
- 更新需要移动的节点的编号 更新为 如下方式(编号为1开始)
- 然后找到需要移动到的父节点位置,到该位置做类似于添加的操作即可
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47DROP PROCEDURE IF EXISTS update_node;
DELIMITER $$
CREATE PROCEDURE update_node(IN move_id BIGINT,IN target_id BIGINT,OUT result INT)
BEGIN
DECLARE sql_error INT DEFAULT 0;
DECLARE leftnode BIGINT DEFAULT 0;
DECLARE rightnode BIGINT DEFAULT 0;
DECLARE temp BIGINT DEFAULT 0;
DECLARE cnt INT DEFAULT 0;
DECLARE move_ids VARCHAR(1000) DEFAULT '';
DECLARE update_num INT DEFAULT 0;
DECLARE CONTINUE HANDLER FOR SQLEXCEPTION SET sql_error = 1;
SELECT l_node,r_node INTO leftnode,rightnode FROM tree_sort WHERE id = move_id;
SET update_num = rightnode-leftnode +1; -- 要修改的 节点数 修改元素个数为修改节点数/2
SET temp = leftnode - 1;
SELECT GROUP_CONCAT(id) INTO move_ids FROM tree_sort WHERE l_node >= leftnode AND r_node <= rightnode;
START TRANSACTION;
UPDATE tree_sort SET l_node = 0-(l_node - temp),r_node = 0-(r_node - temp) WHERE l_node >= leftnode AND r_node <= rightnode; -- 将修改的元素的节点数重新排序 并置为负数,防止影响到其它节点
UPDATE tree_sort SET l_node = l_node-(rightnode-leftnode+1) WHERE l_node > leftnode;
UPDATE tree_sort SET r_node = r_node-(rightnode-leftnode+1) WHERE r_node > rightnode;
SELECT l_node,r_node INTO leftnode,rightnode FROM tree_sort WHERE id = target_id;
SELECT COUNT(1) INTO cnt FROM tree_sort WHERE l_node > leftnode AND r_node < rightnode;
IF(cnt=0) THEN
UPDATE tree_sort SET l_node = l_node+update_num WHERE l_node > leftnode;
UPDATE tree_sort SET r_node = r_node+update_num WHERE r_node >= rightnode; -- 修改删除(伪) 后节点影响到的节点序号
UPDATE tree_sort SET l_node = 0-l_node +leftnode , r_node = 0- r_node + leftnode WHERE FIND_IN_SET (id,move_ids); -- 将原来置为负数的值回复,并做加 leftnode操作
ELSE
SELECT r_node INTO rightnode FROM tree_sort WHERE l_node>leftnode AND r_node<rightnode ORDER BY r_node DESC LIMIT 1;
UPDATE tree_sort SET l_node = l_node+update_num WHERE l_node > rightnode;
UPDATE tree_sort SET r_node = r_node+update_num WHERE r_node > rightnode;
UPDATE tree_sort SET l_node = 0-l_node+rightnode,r_node = 0- r_node + rightnode WHERE FIND_IN_SET (id,move_ids);
END IF;
IF (sql_error = 1) THEN
ROLLBACK;
ELSE
COMMIT;
END IF;
SET result = sql_error;
END
$$
DELIMITER ;
调用过程
SET @result = 0;
-- 参数分别为 要移动的节点id,移动到的节点id 返回值 0 正常 1 异常
CALL update_node(13,3,@result);
SELECT * FROM tree_sort;
- 编辑效果图:
Mysql 采用树前序算法优化无限层级结构
http://yoursite.com/post/4f91d70a.html/