前言:
關於多級別菜單欄或者權限系統中部門上下級的樹形遍歷,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);
測試結果: