mysql 實現樹形的遍歷


前言:
關於多級別菜單欄或者權限系統中部門上下級的樹形遍歷,oracle中有connect by來實現,mysql沒有這樣的便捷途徑,所以MySQL遍歷數據表是我們經常會遇到的頭痛問題,下面通過存儲過程來實現。

1、建立測試表和數據:

DROP TABLE IF EXISTS test.channel;
CREATE TABLE test.channel (   
  id INT(11) NOT NULL AUTO_INCREMENT,     
  cname VARCHAR(200) DEFAULT NULL,   
  parent_id INT(11) DEFAULT NULL,   
  PRIMARY KEY (id)   
) ENGINE=INNODB DEFAULT CHARSET=utf8;   
INSERT  INTO channel(id,cname,parent_id)    
VALUES (13,'首頁',-1),   
       (14,'TV580',-1),   
       (15,'生活580',-1),   
       (16,'左上幻燈片',13),   
       (17,'幫忙',14),   
       (18,'欄目簡介',17);  

2、用臨時表和遞歸過程實現樹的遍歷(mysql的UDF不能遞歸調用):

2.1、遞歸過程輸出某節點id路徑,類似Oracle SYS_CONNECT_BY_PATH的功能

-- 遞歸輸出某節點id路徑
DELIMITER //
DROP PROCEDURE IF EXISTS pro_cre_pathlist;
CREATE PROCEDURE pro_cre_pathlist(IN nid INT,IN delimit VARCHAR(10),
INOUT pathstr VARCHAR(1000))
BEGIN
    DECLARE done INT DEFAULT 0;
    DECLARE parentid INT DEFAULT 0;
    DECLARE cur1 CURSOR FOR 
    SELECT t.parent_id,CONCAT(CAST(t.parent_id AS CHAR),delimit,pathstr) 
        from channel AS t WHERE t.id = nid;
    -- 下面這行表示若沒有數據返回,程序繼續,並將變量done設為1
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
    -- mysql中可以利用系統參數 max_sp_recursion_depth 來控制遞歸調用的層數上限。
    SET max_sp_recursion_depth=12;

    OPEN cur1;
    -- 游標向下走一步
    FETCH cur1 INTO parentid,pathstr;
    WHILE done=0 DO
        CALL pro_cre_pathlist(parentid,delimit,pathstr);
        -- 游標向下走一步
        FETCH cur1 INTO parentid,pathstr;
    END WHILE;

    CLOSE cur1;
END //

DELIMITER ;

測試:

SET @str='16';
CALL pro_cre_pathlist(16,'/',@str);
SELECT @str;

測試結果:

2.2、遞歸過程輸出某節點name路徑

-- 遞歸輸出某節點name路徑
DELIMITER //
DROP PROCEDURE IF EXISTS pro_cre_pnlist;
CREATE PROCEDURE pro_cre_pnlist(IN nid INT,IN delimit VARCHAR(10),
INOUT pathstr VARCHAR(1000))
BEGIN
    DECLARE done INT DEFAULT 0;
    DECLARE parentid INT DEFAULT 0;
    DECLARE cur1 CURSOR FOR 
    SELECT t.parent_id,CONCAT(t.cname,delimit,pathstr) 
        from channel AS t WHERE t.id = nid;
    -- 下面這行表示若沒有數據返回,程序繼續,並將變量done設為1
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
    -- mysql中可以利用系統參數 max_sp_recursion_depth 來控制遞歸調用的層數上限。
    SET max_sp_recursion_depth=12;

    OPEN cur1;
    -- 游標向下走一步
    FETCH cur1 INTO parentid,pathstr;
    WHILE done=0 DO
        CALL pro_cre_pnlist(parentid,delimit,pathstr);
        -- 游標向下走一步
        FETCH cur1 INTO parentid,pathstr;
    END WHILE;

    CLOSE cur1;
END //

DELIMITER ;

測試:

SET @str='';
CALL pro_cre_pnlist(16,'/',@str);
SELECT @str;

測試結果:

2.3、調用函數輸出id路徑

-- 調用函數輸出id路徑
DELIMITER //
DROP FUNCTION IF EXISTS fn_tree_path;
CREATE FUNCTION fn_tree_path(nid INT,delimit VARCHAR(10)) 
RETURNS VARCHAR(2000) CHARSET utf8
BEGIN 
    DECLARE pathid VARCHAR(1000);
    
    SET pathid = CAST(nid AS CHAR);
    CALL pro_cre_pathlist(nid,delimit,pathid);
    
    RETURN pathid;
END //
DELIMITER ;

測試:

SELECT fn_tree_path(16,'/') AS id;

測試結果:

2.4、調用函數輸出name路徑

-- 調用函數輸出name路徑  
DELIMITER //
DROP FUNCTION IF EXISTS fn_tree_pathname;
CREATE FUNCTION fn_tree_pathname(nid INT,delimit VARCHAR(10)) 
RETURNS VARCHAR(2000) CHARSET utf8
BEGIN
    DECLARE pathid VARCHAR(1000);
    SET pathid='';
    CALL pro_cre_pnlist(nid,delimit,pathid);
    RETURN pathid;
END //
DELIMITER ;

測試:

SELECT fn_tree_pathname(16,'/') AS name;

測試結果:

2.5、調用過程輸出子節點

-- 調用過程輸出子節點   
DELIMITER //
DROP PROCEDURE IF EXISTS pro_show_childlist;
CREATE PROCEDURE pro_show_childlist(IN rootId INT)
BEGIN
    DROP TEMPORARY TABLE IF EXISTS tmpList;
    CREATE TEMPORARY TABLE IF NOT EXISTS tmpList(
        sno INT PRIMARY KEY AUTO_INCREMENT,
        id INT,
        depth INT);

    CALL pro_cre_childlist(rootId,0);

    SELECT channel.id,CONCAT(SPACE(tmpList.depth*2),'--',channel.cname)NAME,
    channel.parent_id,tmpList.depth,fn_tree_path(channel.id,'/')path,
    fn_tree_pathname(channel.id,'/')pathname FROM tmpList,channel 
    WHERE tmpList.id=channel.id ORDER BY tmpList.sno;
END //

DELIMITER ;

2.6、從某節點向下遍歷子節點,遞歸生成臨時表數據

DELIMITER //
DROP PROCEDURE IF EXISTS pro_cre_childlist;
CREATE PROCEDURE pro_cre_childlist(IN rootId INT,IN nDepth INT)
BEGIN
    DECLARE done INT DEFAULT 0;
    DECLARE b INT;
    DECLARE cur1 CURSOR FOR SELECT id FROM channel WHERE parent_id=rootId;
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
    SET max_sp_recursion_depth=12;

    INSERT INTO tmpList VALUES(NULL,rootId,nDepth);

    OPEN cur1;

    FETCH cur1 INTO b;
    WHILE done=0 DO
        CALL pro_cre_childlist(b,nDepth+1);
        FETCH cur1 INTO b;
    END WHILE

    CLOSE cur1;
END //

DELIMITER ;

2.7、調用過程輸出父節點

-- 調用過程輸出父節點   
DELIMITER //
DROP PROCEDURE IF EXISTS pro_show_parentlist;
CREATE PROCEDURE pro_show_parentlist(IN rootId INT)
BEGIN
    DROP TEMPORARY TABLE IF EXISTS tmpList;
    CREATE TEMPORARY TABLE IF NOT EXISTS tmpList(
        sno INT PRIMARY KEY AUTO_INCREMENT,
        id INT,
        depth INT);

    CALL pro_cre_parentlist(rootId,0);
    SELECT channel.id,CONCAT(SPACE(tmpList.depth*2),'--',channel.cname)NAME,
    channel.parent_id,tmpList.depth,fn_tree_path(channel.id,'/')path,
    fn_tree_pathname(channel.id,'/')pathname FROM tmpList,channel 
    WHERE tmpList.id=channel.id ORDER BY tmpList.sno;
END //

DELIMITER ;

2.8、從某節點向上追溯根節點,遞歸生成臨時表數據

DELIMITER //
DROP PROCEDURE IF EXISTS pro_cre_parentlist;
CREATE PROCEDURE pro_cre_parentlist(IN rootId INT,IN nDepth INT)
BEGIN
    DECLARE done INT DEFAULT 0;
    DECLARE b INT;
    DECLARE cur1 CURSOR FOR SELECT parent_id FROM channel WHERE id=rootId;
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
    SET max_sp_recursion_depth=12;

    INSERT INTO tmpList VALUES(NULL,rootId,nDepth);

    OPEN cur1;

    FETCH cur1 INTO b;
    WHILE done=0 DO
        CALL pro_cre_parentlist(b,nDepth+1);
        FETCH cur1 INTO b;
    END WHILE;

    CLOSE cur1;
END //

DELIMITER ;

3、開始測試

3.1、從根節點開始顯示,顯示子節點集合:

CALL pro_show_childlist(-1);

測試結果:

3.2、顯示首頁下面的子節點

CALL pro_show_childlist(13);

測試結果:

3.3、顯示TV580下面的所有子節點

CALL pro_show_childlist(14);

測試結果:

3.4、“幫忙”節點有一個子節點,顯示出來:

CALL pro_show_childlist(17);

測試結果:

3.5、“欄目簡介”沒有子節點,所以只顯示最終節點:

3.6、顯示“首頁”的父節點

CALL pro_show_parentlist(13);

測試結果:

3.7、顯示“TV580”的父節點,parent_id為-1

CALL pro_show_parentlist(14);

測試結果:

3.8、顯示“幫忙”節點的父節點

CALL pro_show_parentlist(17);

測試結果:

3.9、顯示最低層節點“欄目簡介”的父節點

CALL pro_show_parentlist(18);

測試結果:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM