mysql百万层级树杈,查看新闻/公告--[整理]mysql存储过程实现的无限级分类,前序遍历树---改良版...

67f5e7ce695baaf737240c5c9017fd6e.png

原版在此:

http://www.mytju.com/classcode/news_readNews.asp?newsID=422

觉得不够爽,改了下。

删除节点(根节点除外)时,可以删除所有子孙节点。

支持任何一个节点(根节点除外)的移动,会连同子孙节点一起移动。

---------------------------------------------------

(1)表结构

CREATE TABLE `tree_node` (

`id` int(10) NOT NULL AUTO_INCREMENT,

`code` varchar(50) NOT NULL,

`name` varchar(50) NOT NULL,

`pid` int(10) NOT NULL,

`treeId` int(10) NOT NULL,

`leftId` int(10) NOT NULL,

`rightId` int(10) NOT NULL,

`level` int(10) NOT NULL,

PRIMARY KEY (`id`)

) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=7 ;

(2)添加根节点

DROP Procedure IF EXISTS `addTreeRootNode` ;

DELIMITER |

/*

* pid,添加到的目标节点ID

* code,节点代码

* name,节点名称

* resultCode,结果代码,

* resultMsg,结果消息

*/

CREATE PROCEDURE `addTreeRootNode`(vTreeId int,code varchar(50),name varchar(50),OUT resultCode int,OUT resultMsg varchar(50))

ExitLabel:BEGIN

DECLARE vLeftId INT; /* 左节点ID */

DECLARE aff INT; /* SQL 影响记录条数 */

/* 目标节点的右值、treeId和Level值 */

SELECT `leftId` INTO vLeftId

FROM `tree_node`

WHERE `treeId`= vTreeId AND `pid` = 0;

IF vLeftId IS NOT NULL THEN

SET resultCode = 1002;

SET resultMsg = "根节点已存在";

LEAVE ExitLabel;

END IF;

/*----------开始更新--------------*/

START TRANSACTION;

/* 增加节点自己,pid=0,leftId=1,rightId=2,level=0 */

INSERT INTO `tree_node`(`code`,`name`,`pid`,`leftId`,`rightId`,`level`,`treeId`)

VALUES (code,name,0,1,2,0,vTreeId);

/* 影响行数 */

SELECT ROW_COUNT() INTO aff;

IF aff = 1 THEN

COMMIT;

SET resultCode = 1000;

SET resultMsg = "成功";

ELSE

ROLLBACK;

SET resultCode = 1001;

SET resultMsg = "失败";

END IF;

END |

(3)添加节点

DROP Procedure IF EXISTS `addTreeNode` ;

DELIMITER |

/*

* pid,添加到的目标节点ID

* code,节点代码

* name,节点名称

* resultCode,结果代码,

* resultMsg,结果消息

*/

CREATE PROCEDURE `addTreeNode`(pid int,code varchar(50),name varchar(50),OUT resultCode int,OUT resultMsg varchar(50))

ExitLabel:BEGIN

DECLARE vRightId INT; /* 右节点ID */

DECLARE vLevel INT; /*层级*/

DECLARE vTreeId INT; /*树ID*/

DECLARE aff INT; /* SQL 影响记录条数 */

DECLARE af INT DEFAULT 0; /* 总影响记录条数*/

/* 目标节点的右值、treeId和Level值 */

SELECT `rightId`,`treeId`,`level`

INTO vRightId,vTreeId,vLevel

FROM `tree_node`

WHERE `id`= pid;

IF vRightId IS NULL THEN

SET resultCode = 1002;

SET resultMsg = "指定的节点不存在";

LEAVE ExitLabel;

END IF;

/*----------开始更新--------------*/

START TRANSACTION;

/* 更新右侧节点的left值 */

UPDATE `tree_node`

SET `leftId`=`leftId`+2

WHERE `treeId` = vTreeId AND `leftId` > vRightId;

/* 影响行数 */

SELECT ROW_COUNT() INTO aff;

SET af = aff+af;

/* 更新右侧节点的right值 */

UPDATE `tree_node`

SET `rightId`=`rightId`+2

WHERE `treeId` = vTreeId AND `rightId` >= vRightId;

/* 影响行数 */

SELECT ROW_COUNT() INTO aff;

SET af = aff+af;

/* 增加节点自己 */

INSERT INTO `tree_node`(`code`,`name`,`pid`,`leftId`,`rightId`,`level`,`treeId`)

VALUES (code,name,pid,vRightId,vRightId+1,vLevel+1,vTreeId);

/* 影响行数 */

SELECT ROW_COUNT() INTO aff;

SET af = aff+af;

/*至少会插入节点自己,更新根节点,所以最少是2*/

IF af >= 2 THEN

COMMIT;

SET resultCode = 1000;

SET resultMsg = "成功";

ELSE

ROLLBACK;

SET resultCode = 1001;

SET resultMsg = "失败";

END IF;

END |

(4)删除节点

DROP Procedure IF EXISTS `deleteTreeNode` ;

DELIMITER |

/*

* nodeId,要删除的节点ID

* resultCode,结果代码,

* resultMsg,结果消息

*/

CREATE PROCEDURE `deleteTreeNode`(nodeId int,OUT resultCode int,OUT resultMsg varchar(50))

ExitLabel:BEGIN

DECLARE vLeftId INT; /* 左节点ID */

DECLARE vRightId INT; /* 右节点ID */

DECLARE vTreeId INT; /*树ID*/

DECLARE aff INT; /* SQL 影响记录条数 */

DECLARE af INT DEFAULT 0; /* 总影响记录条数*/

/* 查询要删除的节点,变量名不能与字段名一样的哦!! */

SELECT `leftId`,`rightId`,`treeId`

INTO vLeftId,vRightId,vTreeId

FROM `tree_node`

WHERE `id` = nodeId;

IF vLeftId IS NULL THEN

SET resultCode = 1002;

SET resultMsg = "要删除的节点不存在";

LEAVE ExitLabel;

END IF;

IF vLeftId=1 THEN

SET resultCode = 1003;

SET resultMsg = "根节点不能删除";

LEAVE ExitLabel;

END IF;

/*----------开始更新--------------*/

START TRANSACTION;

/* 删除节点及所有子孙节点 */

DELETE

FROM `tree_node`

WHERE `treeId` = vTreeId

AND `leftId` between vLeftId AND vRightId;

/* 影响行数 */

SELECT ROW_COUNT() INTO aff;

SET af = aff+af;

/* 更新右侧节点的left值 */

UPDATE `tree_node`

SET `leftId`=`leftId`-(vRightId-vLeftId+1)

WHERE `treeId` = vTreeId AND `leftId`>vLeftId;

/* 影响行数 */

SELECT ROW_COUNT() INTO aff;

SET af = aff+af;

/* 更新右侧节点的right值 */

UPDATE `tree_node`

SET `rightId`=`rightId`-(vRightId-vLeftId+1)

WHERE `treeId` = vTreeId AND `rightId`>vLeftId;

/* 影响行数 */

SELECT ROW_COUNT() INTO aff;

SET af = aff+af;

/* 删除其它关联数据 */

/*至少会删除自己,更新根节点,所以最少是2*/

IF af >= 2 THEN

COMMIT;

SET resultCode = 1000;

SET resultMsg = "成功";

ELSE

ROLLBACK;

SET resultCode = 1001;

SET resultMsg = "失败";

END IF;

END |

(5)移动节点

DROP Procedure IF EXISTS `moveTreeNode`;

DELIMITER |

/*

* nodeId,移动的节点ID

* targetId,目标节点ID(即放在哪个节点下面)

* resultCode,结果代码,

* resultMsg,结果消息

*/

CREATE PROCEDURE `moveTreeNode`(nodeId int,targetId int,OUT resultCode int,OUT resultMsg varchar(50))

ExitLabel:BEGIN

/* 强烈注意,变量名不要与字段名相同 */

DECLARE vLeftId INT; /* 左节点ID */

DECLARE vRightId INT; /* 右节点ID */

DECLARE vTreeId INT; /*树ID*/

DECLARE vPid INT; /*节点的父ID*/

DECLARE vLevel INT; /* 节点的Level */

DECLARE vTargetLeftId INT; /* 目标节点的左节点ID */

DECLARE vTargetRightId INT; /* 目标节点的右节点ID */

DECLARE vTargetLevel INT; /* 目标节点的Level */

DECLARE vDiff INT; /* 差值*/

DECLARE vLevelDiff INT; /* Level差值*/

DECLARE vGroupTreeId INT; /*没啥用*/

DECLARE vGroupIdStr varchar(1000); /*待移动小树杈的节点列表*/

/* 查询要移动的节点,变量名不能与字段名一样的哦!! */

SELECT `leftId`,`rightId`,`treeId`,`pid`,`level`

INTO vLeftId,vRightId,vTreeId,vPid,vLevel

FROM `tree_node`

WHERE `id`= nodeId;

/*leftID不为空*/

IF vLeftId IS NULL THEN

SET resultCode = 1002;

SET resultMsg = "要移动的节点不存在";

LEAVE ExitLabel;

END IF;

IF vLeftId=1 THEN

SET resultCode = 1003;

SET resultMsg = "根节点不能移动";

LEAVE ExitLabel;

END IF;

IF nodeId = targetId THEN

SET resultCode = 1004;

SET resultMsg = "不能移动到自己";

LEAVE ExitLabel;

END IF;

/* 如果目标ID是要移动节点的父节点,不需要移动(同级认为无顺序)*/

IF vPid = targetId THEN

SET resultCode = 1000; /*认为是成功操作*/

SET resultMsg = "目标节点是要移动节点的父节点,不需要移动";

LEAVE ExitLabel;

END IF;

/* 查询要移动的节点,变量名不能与字段名一样的哦!! */

SELECT `leftId`,`rightId`,`level`

INTO vTargetLeftId,vTargetRightId,vTargetLevel

FROM `tree_node`

WHERE `treeId` = vTreeId AND `id`= targetId;

IF vTargetLeftId IS NULL THEN

SET resultCode = 1006;

SET resultMsg = "目标节点不存在";

LEAVE ExitLabel;

END IF;

IF vTargetLeftId > vLeftId AND vTargetLeftId < vRightId THEN

SET resultCode = 1007;

SET resultMsg = "目标节点不能是要移动节点的子节点";

LEAVE ExitLabel;

END IF;

/*---------------开始更新-------------------------*/

START TRANSACTION;

/* 保存小树杈的ID值*/

SELECT `treeId`, group_concat(CAST(`id` as char)) as idStr

INTO vGroupTreeId,vGroupIdStr

FROM `tree_node`

WHERE `treeId` = vTreeId

AND `leftId` between vLeftId AND vRightId

GROUP BY `treeId`;

/* 目标节点在右边 */

/* 目标节点是当前节点所在路径上的节点的话,比较特殊,与移动到右侧节点的处理相同 */

IF vTargetLeftId>vLeftId OR (vTargetLeftId < vLeftId AND vTargetRightId > vRightId) THEN

/*目标节点的当前right值-1就是移动节点的新right值*/

SET vDiff = vTargetRightId - 1 - vRightId; /*左右值的差值*/

SET vLevelDiff = vTargetLevel + 1 - vLevel; /*Level的差值*/

/* 更新小树杈的left、right和level*/

UPDATE `tree_node`

SET `leftId`=`leftId` +vDiff,

`rightId`=`rightId` + vDiff,

`level` = `level` + vLevelDiff

WHERE `treeId` = vTreeId

AND `leftId` between vLeftId AND vRightId;

/* 更新移动节点的父ID */

UPDATE `tree_node`

SET `pid` = targetId

WHERE `id`= nodeId;

/*插入位置左侧的节点的left值和right值要减小*/

SET vDiff = vRightId-vLeftId+1;

/* left>移动节点原right值 and left<目标节点原right值,并且不是小树杈上的节点*/

UPDATE `tree_node`

SET `leftId`=`leftId`- vDiff

WHERE `treeId` = vTreeId

AND `leftId`>vRightId AND `leftId`< vTargetRightId

AND NOT FIND_IN_SET(CAST(`id` as char),vGroupIdStr);

/* right>移动节点原right值 and right<目标节点原right值,并且不是小树杈上的节点*/

UPDATE `tree_node`

SET `rightId`=`rightId`- vDiff

WHERE `treeId` = vTreeId

AND `rightId`>vRightId AND `rightId`< vTargetRightId

AND NOT FIND_IN_SET(CAST(`id` as char),vGroupIdStr);

ELSE

/* 目标节点在左边 */

/*目标节点的当前right值就是移动节点的新left值*/

SET vDiff = vLeftId - vTargetRightId; /*左右值的差值*/

SET vLevelDiff = vTargetLevel + 1 - vLevel; /*Level的差值*/

/* 更新小树杈的left、right和level*/

UPDATE `tree_node`

SET `leftId`=`leftId` -vDiff,

`rightId`=`rightId` - vDiff,

`level` = `level` + vLevelDiff

WHERE `treeId` = vTreeId

AND `leftId` between vLeftId AND vRightId;

/* 更新移动节点的父ID */

UPDATE `tree_node`

SET `pid` = targetId

WHERE `id`= nodeId;

/*插入位置右侧的节点的left值和right值要增大*/

SET vDiff = vRightId-vLeftId+1;

/* left>目标节点原right值 and left<移动节点原right值,并且不是小树杈上的节点*/

UPDATE `tree_node`

SET `leftId`=`leftId`+ vDiff

WHERE `treeId` = vTreeId

AND `leftId`>vTargetRightId AND `leftId`< vRightId

AND NOT FIND_IN_SET(CAST(`id` as char),vGroupIdStr);

/* right>=目标节点原right值 and right<移动节点原right值,并且不是小树杈上的节点*/

UPDATE `tree_node`

SET `rightId`=`rightId`+ vDiff

WHERE `treeId` = vTreeId

AND `rightId`>=vTargetRightId AND `rightId`< vRightId

AND NOT FIND_IN_SET(CAST(`id` as char),vGroupIdStr);

END IF;

/* 成功,这个费劲啊 */

COMMIT;

SET resultCode = 1000;

SET resultMsg = "成功";

END |

(6)编辑节点

DROP Procedure IF EXISTS `editTreeNode` ;

DELIMITER |

/*

* nodeId,编辑的节点ID

* pid,移动到的目标节点ID

* code,节点代码

* name,节点名称

* resultCode,结果代码,

* resultMsg,结果消息

*/

CREATE PROCEDURE `editTreeNode`(nodeId int,pid int,code varchar(50),name varchar(50),OUT resultCode int,OUT resultMsg varchar(50))

ExitLabel:BEGIN

call moveTreeNode(nodeId,pid,resultCode,resultMsg);

IF resultCode=1000 THEN

/* 节点信息 */

UPDATE `tree_node`

SET `code` = code,`name` = name

WHERE `id`= nodeId;

COMMIT;

END IF;

END |

(7)同级节点排序(这个是后加的,没放一起试。前台把父id,子id逗号拼接Str)

DROP PROCEDURE IF EXISTS `sortTreeNode`;

DELIMITER ;;

CREATE DEFINER=`root`@`localhost` PROCEDURE `sortTreeNode`(vPid int,vSubNodeIdStr varchar(2000),OUT resultCode int,OUT resultMsg varchar(50))

ExitLabel:BEGIN

/* 强烈注意,变量名不要与字段名相同 */

DECLARE vLeftId INT; /* 父节点的left */

DECLARE vRightId INT; /* 父节点的right */

DECLARE vTreeId INT; /*树ID*/

DECLARE vSubNodeId INT; /* 子节点ID */

DECLARE vSubNodeLeftId INT; /* 子节点left */

DECLARE vSubNodeRightId INT; /* 子节点right */

DECLARE vDiff INT; /* 差值*/

DECLARE vNextLeftId INT; /* 下一个left值 */

DECLARE vNodePid INT; /* 子节点的父ID*/

DECLARE vGroupIdStr varchar(2000); /*待更新小树杈的节点列表*/

/* 父节点 */

SELECT `leftId`,`rightId`,`treeId`

INTO vLeftId,vRightId,vTreeId

FROM `product_sort_tree_node`

WHERE `id`= vPid;

/*父节点不存在*/

IF vLeftId IS NULL THEN

SET resultCode = 1002;

SET resultMsg = "指定的父节点不存在";

LEAVE ExitLabel;

END IF;

/*子节点ID列表为空*/

IF vSubNodeIdStr IS NULL OR LENGTH(vSubNodeIdStr)=0 THEN

SET resultCode = 1003;

SET resultMsg = "未指定子节点ID列表";

LEAVE ExitLabel;

END IF;

/*临时表*/

SET group_concat_max_len=200000; /*group_concat默认最大是1024,太小*/

DROP TEMPORARY TABLE if exists tmp_table;

CREATE TEMPORARY TABLE tmp_table

SELECT A.`id`,A.`leftId`,A.`rightId`,A.`pid`,

(

SELECT group_concat(CAST(B.`id` as char)) as idStr

FROM `product_sort_tree_node` as B

WHERE B.`treeId` = A.`treeId`

AND B.`leftId` between A.`leftId` AND A.`rightId`

) as idStr

FROM `product_sort_tree_node` as A

WHERE FIND_IN_SET(CAST(`id` as char),vSubNodeIdStr);

/*---------------开始更新-------------------------*/

START TRANSACTION;

/* 下一个left值 */

SET vNextLeftId = (vLeftId + 1);

while LENGTH(vSubNodeIdStr)>0 do

/*取串里的第一个ID,无逗号了,则返回整个串*/

SET vSubNodeId = SUBSTRING_INDEX(vSubNodeIdStr, ',', 1);

SET vSubNodeIdStr = SUBSTRING(vSubNodeIdStr,LENGTH(vSubNodeId)+2);

/* 子节点 */

SELECT `leftId`,`rightId`,`pid`,`idStr`

INTO vSubNodeLeftId,vSubNodeRightId,vNodePid,vGroupIdStr

FROM `tmp_table`

WHERE `id`= vSubNodeId;

/*父节点不符--还有一点,子节点ID列表必须包含了所有的子节点,省事就不check了。*/

IF vNodePid<>vPid THEN

SET resultCode = 1004;

SET resultMsg = "子节点的父节点与参数中的父节点不符";

ROLLBACK;

LEAVE ExitLabel;

END IF;

/* 旧left与新left的差值 */

SET vDiff = vSubNodeLeftId - vNextLeftId;

/* 更新该子节点的小树杈的left、right*/

if vDiff<>0 then

UPDATE `product_sort_tree_node`

SET `leftId`=`leftId` - vDiff,

`rightId`=`rightId` - vDiff

WHERE FIND_IN_SET(CAST(`id` as char),vGroupIdStr);

end IF;

/* 下一个left值 */

SET vNextLeftId = vSubNodeRightId - vDiff + 1;

end while;

/* 成功 */

COMMIT;

SET resultCode = 1000;

SET resultMsg = "成功";

END

;;

------------------------用法--------------------------

由于tree_node存储了多颗树的节点,所以注意总是带上treeId这个条件。

(0)得到整个树形结构:

select id,concat(repeat("-",level),name),treeId,leftId,rightId

from tree_node

where treeId=1

order by leftId;

(1)获取节点的所有子孙节点:

---------------------------

如节点7的lft是1,rgt是4

select leftId,rightId from tree_node where id = 7;

select id,concat(repeat("-",level),name),treeId,leftId,rightId

from tree_node

where treeId=1 and leftId between 1 and 4

order by leftId;

----------------------------

(2)获取节点所在路径:

---------------------------

如节点8的lft是2,rgt是3

select leftId,rightId from tree_node where id = 8;

select id,concat(repeat("-",level),name),treeId,leftId,rightId

from tree_node

where treeId=1 and leftId<2 and rightId>3

order by leftId;

会得到从根节点到该节点的所有节点,拼接即可

---------------------------

(3)获取所有子孙节点的个数

个数= (right – left - 1) / 2

(4)存储过程的调用方法:

set @code=0;

set @msg= '';

call moveTreeNode(55,52,@code,@msg);

select @code,@msg;

(5)java中的调用方法:

CallableStatement callStmt = null;

try {

callStmt = conn.prepareCall("{call deleteTreeNode(?,?,?)}");

callStmt.setInt(1, nodeId);

callStmt.registerOutParameter(2, java.sql.Types.INTEGER);

callStmt.registerOutParameter(3, java.sql.Types.VARCHAR);

callStmt.executeUpdate();

int code = callStmt.getInt(2);

String msg = callStmt.getString(3);

System.out.println(code+msg);

} catch (Exception e) {

e.printStackTrace(System.out);

} finally {

if (null != callStmt) {

callStmt.close();

}

}

用registerOutParameter方法注册输出参数即可。

应该根据返回的code值,决定是否报错,这里木有写~~

初步代码,简单测试了好像木有问题,有待进一步测试。

有问题敬请指出。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值