[轉] MySQL樹結構遞歸查詢處理


原文鏈接:https://www.jianshu.com/p/9e7c2a481e49

在日常開發中我們經常會遇到樹形結構數據的處理,如:組織機構之類的情況。在表結構通常會采用idparent_id這種設計方案。一個常見的需求:查詢某個節點下的所有子節點。

為方便后續說明,在此統一約定表名為:t_org,其定義如下:

字段 類型 說明
id bigint(20) NOT NULL 機構編碼
parent_id bigint(20) 上級機構編碼
desc varchar(200) 備注

查詢實現方案

表中現有如下測試數據

測試數據

組織機構層級數確定時

可以采用自關聯LEFT JOIN方式進行查詢獲取結果。

SELECT t1.id,t1.name,t2.id,t2.parent_id,t2.name,t3.id,t3.parent_id,t3.name
FROM t_org t1
LEFT JOIN t_org t2 ON t1.id = t2.parent_id
LEFT JOIN t_org t3 ON t2.id = t3.parent_id
WHERE t1.id = '1'; 

查詢結果如下;


查詢結果

組織機構層級數不確定時

當組織機構層級數不確定時,無法使用上述方式進行查詢

可以通過自定義函數方式實現查詢
CREATE DEFINER=`root`@`localhost` FUNCTION `findChildren`(rootId INT) RETURNS VARCHAR(4000) CHARSET utf8 BEGIN DECLARE sTemp VARCHAR(4000); DECLARE sTempChd VARCHAR(4000); SET sTemp = '$'; SET sTempChd = CAST(rootId as CHAR); WHILE sTempChd is not null DO SET sTemp = CONCAT(sTemp,',',sTempChd); SELECT GROUP_CONCAT(id) INTO sTempChd FROM t_org WHERE FIND_IN_SET(parent_id,sTempChd)>0; END WHILE; RETURN sTemp; END; 

在上面函數中使用到了兩個MySQL函數

GROUP_CONCAT(expr)
該函數會從expr中連接所有非NULL的字符串。如果沒有非 NULL 的字符串,那么它就會返回NULL。語法如下:

GROUP_CONCAT([DISTINCT] expr [,expr ...]
             [ORDER BY {unsigned_integer | col_name | expr} [ASC | DESC] [,col_name ...]] [SEPARATOR str_val]) 

注意事項:GROUP_CONCAT查詢結果默認最大長度限制為1024,該值是系統變量group_concat_max_len的默認值,可以通過SET [GLOBAL | SESSION] group_concat_max_len = val;更改該值。

FIND_IN_SET(str,strlist)
該函數返回一個1~N的值表示strstrlist中的位置。
該函數結合WHERE使用對結果集進行過過濾(查找str包含在strlist結果集里面的記錄)

函數使用方式

SELECT * FROM t_org WHERE FIND_IN_SET(id,findChildren(1)) > 0; 
方案缺點

返回結果長度受VARCHAR最大長度限制,特別是當組織機構比較龐大時該方案會失效。下面我們可以使用存儲過程結合臨時表來解決這個問題。

存儲過程+臨時表

使用存儲過程結合臨時表的方案需要創建兩個存儲過程,一個用於遞歸查詢所有節點並將數據寫入臨時表中,另一個負責創建臨時表、清空臨時表數據,觸發查詢調用動作。
首先,定義第一個存儲過程,如下:

CREATE DEFINER=`root`@`localhost` PROCEDURE `findOrgChildList`(IN orgId VARCHAR(20)) BEGIN DECLARE v_org VARCHAR(20) DEFAULT ''; DECLARE done INTEGER DEFAULT 0; -- 查詢結果放入游標中 DECLARE C_org CURSOR FOR SELECT d.id FROM t_org d WHERE d.parent_id = orgId; DECLARE CONTINUE HANDLER FOR NOT found SET done=1; SET @@max_sp_recursion_depth = 10; -- 傳入的組織id寫入臨時表 INSERT INTO tmp_org VALUES (orgId); OPEN C_org; FETCH C_org INTO v_org; WHILE (done=0) DO -- 遞歸調用,查找下級 CALL findOrgChildList(v_org); FETCH C_org INTO v_org; END WHILE; CLOSE C_org; END 

如上所示,邏輯比較簡單。接下來定義第二個存儲過程,如下;

CREATE DEFINER=`root`@`localhost` PROCEDURE `findOrgList`(IN orgId VARCHAR(20)) BEGIN DROP TEMPORARY TABLE IF EXISTS tmp_org; -- 創建臨時表 CREATE TEMPORARY TABLE tmp_org(org_id VARCHAR(20)); -- 清空臨時表數據 DELETE FROM tmp_org; -- 發起調用 CALL findOrgChildList(orgId); -- 從臨時表查詢結果 SELECT org_id FROM tmp_org ORDER BY org_id; END 

使用方式如下

CALL findOrgList(org_id); 

至此,我們在可以處理無限層級的樹形結構數據。

MyBatis調用存儲過程

MyBatis中我們可以使用如下方式對存儲過程進行調用

<select id="selectOrgChildList" resultType="java.lang.String" statementType="CALLABLE"> <![CDATA[ CALL findOrgList( #{orgId,mode=IN,jdbcType=VARCHAR}, ]]> </select> 

需要指定statementTypeCALLABLE表示需要執行的是一個存儲過程,statementType默認值為PREPARED

總結

存儲過程和函數的方式雖然簡化了代碼處理邏輯,但是使用函數和存儲過程也有其缺點,主要體現在函數和存儲過程在線上業務中其性能不容易監控,針對慢查詢優化等方面從DBA角度來講不是那么方便,所以在使用函數和存儲過程時需要進行相應的權衡。

參考

MySQL遞歸查找存儲過程

MySQL 5.7 Reference Manual

MyBatis-3 Reference



作者:Daved
鏈接:https://www.jianshu.com/p/9e7c2a481e49
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯系作者獲得授權並注明出處。


免責聲明!

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



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