1. 程式人生 > 實用技巧 >mysql遞迴查詢

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個位元組。

這個對於遞迴查詢還是非常致命的。因為一般遞迴的話,關係層級都比較深,很有可能超過最大長度。(儘管一般拼接的都是數字字串,即單位元組)

兩種方法解決這個問題:

  1. 修改 MySQL 配置檔案 my.cnf ,增加group_concat_max_len = 102400 #你要的最大長度

  2. 執行以下任意一個語句。SET GLOBAL group_concat_max_len=102400;或者SET SESSION group_concat_max_len=102400;

  3. 他們的區別在於,global是全域性的,任意開啟一個新的會話都會生效,但是注意,已經開啟的當前會話並不會生效。而 session 是隻會在當前會話生效,其他會話不生效。

  4. 共同點是,它們都會在 MySQL 重啟之後失效,以配置檔案中的配置為準。所以,建議直接修改配置檔案。102400 的長度一般也夠用了。假設一個id的長度為10個位元組,也能拼上一萬個id了。

  5. 使用 group_concat 函式還有一個限制,就是不能同時使用 limit
  6. 如果需要的話,可以通過子查詢來實現
    select group_concat(id) from (select * from dept limit 5) d;