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 '分隔符'] )

先開啟自定義函數的功能:

在.ini中添加:
log-bin-trust-function-creators=1
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;
