原理说明可以看这个帖子: http://jakezzz.blog.sohu.com/53099673.html
把代码整理一下,试了一下,比较好使,根据实际需要改改就基本OK。
(1)建表
DROP TABLE IF EXISTS `pcms_channel`;
CREATE TABLE IF NOT EXISTS `pcms_channel` ( `cid` tinyint(3) unsigned NOT NULL auto_increment, `name` char(10) NOT NULL COMMENT '频道名称', `parentid` tinyint(4) NOT NULL COMMENT '父级ID', `lft` tinyint(4) NOT NULL COMMENT '左值', `rgt` tinyint(4) NOT NULL COMMENT '右值', `lv` tinyint(3) unsigned NOT NULL default '0' COMMENT '级层', `themeid` tinyint(3) unsigned NOT NULL default '1' COMMENT '使用的主题的ID', PRIMARY KEY (`cid`), KEY `parentid` (`parentid`,`lft`,`rgt`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=7 ;
(2)导入测试数据
INSERT INTO `pcms_channel` (`cid`, `name`, `parentid`, `lft`, `rgt`, `lv`, `themeid`) VALUES (1, 'phpoocms', 0, 1, 12, 0, 1), (2, 'test', 1, 2, 7, 1, 1), (3, 'te', 2, 3, 6, 2, 1), (4, 'tes', 1, 8, 9, 1, 1), (5, 'dd', 3, 4, 5, 3, 1), (6, 'fromphp', 1, 10, 11, 1, 1);
(3)添加节点的存储过程 调用方法:call addChannel (1,"aaaa") 返回1000才是成功,其它是失败了。
DROP Procedure `addChannel` ;
DELIMITER |
/* * pid,添加到的目标节点ID * name,节点名称 */ CREATE PROCEDURE `addChannel`(in pid int,in name varchar(20)) BEGIN DECLARE pr INT;/*右值*/ DECLARE lvv INT;/*层级*/ DECLARE aff INT; /* SQL 影响记录条数 */ DECLARE af INT DEFAULT 0; /* 总影响记录条数*/ SET @result = null; /* 目标节点的右值,Level值 */ SELECT `rgt`,`lv` INTO pr,lvv FROM `pcms_channel` WHERE `cid` = pid; IF pr THEN START TRANSACTION; /* 更新右侧节点的left值 */ UPDATE `pcms_channel` SET `lft`=`lft`+2 WHERE `lft`>pr; SELECT ROW_COUNT() INTO aff; SET af = aff+af; /* 更新右侧节点的right值 */ UPDATE `pcms_channel` SET `rgt`=`rgt`+2 WHERE `rgt`>=pr; SELECT ROW_COUNT() INTO aff; SET af = aff+af; /* 增加节点自己 */ INSERT INTO `pcms_channel` (`name`,`parentid`,`lft`,`rgt`,`lv`) VALUES (name,pid,pr,pr+1,lvv+1); SELECT ROW_COUNT() INTO aff; SET af = aff+af; IF af >= 2 THEN COMMIT; SET @result = 1000; SELECT 1000 AS result; ELSE ROLLBACK; SET @result = 1002; SELECT 1002 AS result; END IF; ELSE SET @result = 1001; SELECT 1001 AS result; END IF; END |
(4)删除节点(只能删除叶节点,有子节点的不允许删除) 调用方法:call delChannel (5)
DROP Procedure `delChannel` ;
DELIMITER |
/* * pid,要删除的节点ID * 节点有子节点时,不允许删除, * 所以问题简单了,只删除自己就可以了,不需要删除所有子节点。 */ CREATE PROCEDURE `delChannel`(in pid int) BEGIN
DECLARE pl INT; /* 左节点ID */ DECLARE pn INT; /* 子节点数量 */ DECLARE aff INT; /* SQL 影响记录条数 */ DECLARE af INT DEFAULT 0; /* 总影响记录条数*/
SET @result = null; SET @parentid = null; SET @name = null;
/* 查询要删除的节点,及它的子节点 ,得到左节点ID,子节点数量,父节点ID和节点名称是给移动节点使用的。 */ SELECT a.`lft`,IFNULL(COUNT(b.`cid`),0),a.`parentid`,a.`name` INTO pl,pn,@parentid,@name FROM `pcms_channel` AS a LEFT JOIN `pcms_channel` AS b ON a.`cid`=b.`parentid` WHERE a.`cid`=pid GROUP BY b.`parentid`;
/* 如果子节点数量为0 */ IF pl&&!pn THEN IF pl!=1 THEN /* 左节点是1的认为是根节点,不让删除*/ START TRANSACTION;
/* 更新右侧节点的left值 */ UPDATE `pcms_channel` SET `lft`=`lft`-2 WHERE `lft`>pl; SELECT ROW_COUNT() INTO aff; SET af = aff+af;
/* 更新右侧节点的right值 */ UPDATE `pcms_channel` SET `rgt`=`rgt`-2 WHERE `rgt`>pl; SELECT ROW_COUNT() INTO aff; SET af = aff+af; /* 删除节点自己 */ DELETE FROM `pcms_channel` WHERE `cid` = pid; SELECT ROW_COUNT() INTO aff; SET af = aff+af;
IF af >= 2 THEN COMMIT; SET @result = 1000; SELECT 1000 AS result; ELSE ROLLBACK; SET @result = 1002; SELECT 1002 AS result; END IF; ELSE SET @result = 1004; SELECT 1004 AS result; END IF; ELSEIF pn&&pl THEN /* 子节点数量>0则报错 */ SET @result = 1003; SELECT 1003 AS result; ELSE SET @result = 1001; SELECT 1001 AS result; END IF; END |
(5)移动节点(只能移动叶节点,是通过先删除,再添加的办法实现的。) 调用方法:call moveChannel (5,4)
DROP Procedure `moveChannel` ;
DELIMITER |
/* * pid,移动的节点ID * tid,目标节点ID */ CREATE PROCEDURE `moveChannel`(pid int,tid int) BEGIN /* 节点ID是为1的是根节点,不让移动(这么判断不对吧?左节点是1的不让删除吧) */ IF pid=1 THEN SELECT 1004 AS result; ELSE /* 不能移动到自己 */ IF pid!=tid THEN call delChannel (pid); /* 先删除该节点 */ IF @result=1000 THEN call addChannel (tid,@name); /* 再添加到目标节点下 */ IF @result THEN SELECT 1000 AS result; ELSE call addChannel (@parentid,@name); /* 添加失败的话,再添加到原来的位置*/ SELECT @result AS result; END IF; ELSE SELECT @result AS result; END IF; ELSE SELECT 1005 AS result; END IF; END IF; SET @result=null; SET @parentid=null; SET @name=null; END |
------------------------用法-------------------------- (0)得到整个树形结构: select cid,concat(repeat("-",lv),name) from pcms_channel order by lft;
(1)获取节点的所有子孙节点: --------------------------- 如节点2的lft是2,rgt是7
select lft,rgt from pcms_channel where cid = 2;
select cid,concat(repeat("-",lv),name) from pcms_channel where lft between 2 and 7 order by lft; ----------------------------
(2)获取节点所在路径: --------------------------- 如节点5的lft是4,rgt是5
select lft,rgt from pcms_channel where cid = 2;
select cid,concat(repeat("-",lv),name) from pcms_channel where lft<4 and rgt>5 order by lft;
会得到从根节点到该节点的所有节点,拼接即可 ---------------------------
(3)获取所有子孙节点的个数 个数= (right – left - 1) / 2
|