mysql遞迴查詢
find_in_set 函式
函式語法:find_in_set(str,strlist)
str 代表要查詢的字串 , strlist 是一個以逗號分隔的字串,如 ('a,b,c')。
此函式用於查詢 str 字串在字串 strlist 中的位置,返回結果為 1 ~ n 。若沒有找到,則返回0。
concat:它用於連線N個字串
concat_ws :concat 是以逗號為預設的分隔符,而 concat_ws 則可以指定分隔符,第一個引數傳入分隔符,如以下劃線分隔。
group_concat:可以分組的同時,把欄位以特定分隔符拼接成字串
用法:group_concat( [distinct] 要連線的欄位 [order by 排序欄位 asc/desc ] [separator '分隔符'] )
先開啟自定義函式的功能:
delimiter $$ drop function if exists get_child_list$$ create function get_child_list(in_id varchar(10)) returns varchar(1000) begin declare ids varchar(1000) default ''; declare tempids varchar(1000); set tempids = in_id; while tempids is not null do set ids = CONCAT_WS(',',ids,tempids); select GROUP_CONCAT(id) into tempids from dept where FIND_IN_SET(pid,tempids)>0; end while; return ids; end $$ delimiter ;
(1) delimiter $$ ,用於定義結束符。我們知道 MySQL 預設的結束符為分號,表明指令結束並執行。但是在函式體中,有時我們希望遇到分號不結束,因此需要暫時把結束符改為一個隨意的其他值。我這裡設定為 $$,意思是遇到 $$ 才結束,並執行當前語句。
(2)drop function if exists get_child_list$$ 。若函式 get_child_list 已經存在了,則先刪除它。注意這裡需要用 當前自定義的結束符 $$ 來結束並執行語句。因為,這裡需要數和下邊的函體單獨區分開來執行
(3)create function get_child_list 建立函式。並且引數傳入一個根節點的子節點id,需要注意一定要註明引數的型別和長度,如這裡是 varchar(10)。returns varchar(1000) 用來定義返回值引數型別。
(4)begin 和 end 中間包圍的就是函式體。用來寫具體的邏輯。
(5)declare 用來宣告變數,並且可以用 default 設定預設值。這裡定義的 ids 即作為整個函式的返回值,是用來拼接成最終我們需要的以逗號分隔的遞迴串的。而 tempids 是為了記錄下邊 while 迴圈中臨時生成的所有子節點以逗號拼接成的字串。
(6) set 用來給變數賦值。此處把傳進來的根節點賦值給 tempids 。
(7) while do ... end while; 迴圈語句,迴圈邏輯包含在內。注意,end while 末尾需要加上分號。
迴圈體內,先用 CONCAT_WS 函式把最終結果 ids 和 臨時生成的 tempids 用逗號拼接起來。
然後以 FIND_IN_SET(pid,tempids)>0 為條件,遍歷在 tempids 中的所有 pid ,尋找以此為父節點的所有子節點 id ,並且通過 GROUP_CONCAT(id) into tempids 把這些子節點 id 都用逗號拼接起來,並覆蓋更新 tempids 。
等下次迴圈進來時,就會再次拼接 ids ,並再次查詢所有子節點的所有子節點。迴圈往復,一層一層的向下遞迴遍歷子節點。直到判斷 tempids 為空,說明所有子節點都已經遍歷完了,就結束整個迴圈。
(8)return ids; 用於把 ids 作為函式返回值返回。
(9)函式體結束以後,記得用結束符 $$ 來結束整個邏輯,並執行。
(10)最後別忘了,把結束符重新設定為預設的結束符分號 。
以上是向下遞迴查詢所有子節點的,並且包括了當前節點.
向上遞迴:
向上遞迴時,每一層遞迴一個子節點只對應一個父節點
delimiter $$ drop function if exists get_parent_list$$ create function get_parent_list(in_id varchar(10)) returns varchar(1000) begin declare ids varchar(1000); declare tempid varchar(10); set tempid = in_id; while tempid is not null do set ids = CONCAT_WS(',',ids,tempid); select pid into tempid from dept where id=tempid; end while; return ids; end $$ delimiter ;
用到了 group_concat 函式來拼接字串。但是,需要注意它是有長度限制的,預設為 1024 位元組。可以通過show variables like "group_concat_max_len";
來檢視。
單位是位元組,不是字元。在 MySQL 中,單個字母佔1個位元組,而我們平時用的 utf-8下,一個漢字佔3個位元組。
這個對於遞迴查詢還是非常致命的。因為一般遞迴的話,關係層級都比較深,很有可能超過最大長度。(儘管一般拼接的都是數字字串,即單位元組)
兩種方法解決這個問題:
-
修改 MySQL 配置檔案 my.cnf ,增加
group_concat_max_len = 102400 #你要的最大長度
。 -
執行以下任意一個語句。
SET GLOBAL group_concat_max_len=102400;
或者SET SESSION group_concat_max_len=102400;
-
他們的區別在於,global是全域性的,任意開啟一個新的會話都會生效,但是注意,已經開啟的當前會話並不會生效。而 session 是隻會在當前會話生效,其他會話不生效。
-
共同點是,它們都會在 MySQL 重啟之後失效,以配置檔案中的配置為準。所以,建議直接修改配置檔案。102400 的長度一般也夠用了。假設一個id的長度為10個位元組,也能拼上一萬個id了。
- 使用 group_concat 函式還有一個限制,就是不能同時使用 limit
- 如果需要的話,可以通過子查詢來實現
select group_concat(id) from (select * from dept limit 5) d;