原文鏈接:https://www.jianshu.com/p/9e7c2a481e49
在日常開發中我們經常會遇到樹形結構數據的處理,如:組織機構之類的情況。在表結構通常會采用id
、parent_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的值表示str
在strlist
中的位置。
該函數結合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>
需要指定statementType
為CALLABLE
表示需要執行的是一個存儲過程,statementType
默認值為PREPARED
。
總結
存儲過程和函數的方式雖然簡化了代碼處理邏輯,但是使用函數和存儲過程也有其缺點,主要體現在函數和存儲過程在線上業務中其性能不容易監控,針對慢查詢優化等方面從DBA角度來講不是那么方便,所以在使用函數和存儲過程時需要進行相應的權衡。
參考
作者:Daved
鏈接:https://www.jianshu.com/p/9e7c2a481e49
來源:簡書
簡書著作權歸作者所有,任何形式的轉載都請聯系作者獲得授權並注明出處。