[心缘地方]同学录
首页 | 功能说明 | 站长通知 | 最近更新 | 编码查看转换 | 代码下载 | 常见问题及讨论 | 《深入解析ASP核心技术》 | 王小鸭自动发工资条VBA版
登录系统:用户名: 密码: 如果要讨论问题,请先注册。

[整理]mysql存储过程实现的无限级分类,前序遍历树---改良版

上一篇:[备忘]mysql存储过程里,暂存记录的ID,以备使用
下一篇:[备忘]HTML 5 Video – Tag and Attributes

添加日期:2013/2/1 14:30:18 快速返回   返回列表 阅读6088次
原版在此:
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值,决定是否报错,这里木有写~~

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

评论 COMMENTS
没有评论 No Comments.

添加评论 Add new comment.
昵称 Name:
评论内容 Comment:
验证码(不区分大小写)
Validation Code:
(not case sensitive)
看不清?点这里换一张!(Change it here!)
 
评论由管理员查看后才能显示。the comment will be showed after it is checked by admin.
CopyRight © 心缘地方 2005-2999. All Rights Reserved