自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

同事問(wèn)我MySQL怎么遞歸查詢,我懵逼了...

數(shù)據(jù)庫(kù) MySQL
最近在做的業(yè)務(wù)場(chǎng)景涉及到了數(shù)據(jù)庫(kù)的遞歸查詢。我們公司用的 Oracle ,眾所周知,Oracle 自帶有遞歸查詢的功能,所以實(shí)現(xiàn)起來(lái)特別簡(jiǎn)單。

前言

最近在做的業(yè)務(wù)場(chǎng)景涉及到了數(shù)據(jù)庫(kù)的遞歸查詢。我們公司用的 Oracle ,眾所周知,Oracle 自帶有遞歸查詢的功能,所以實(shí)現(xiàn)起來(lái)特別簡(jiǎn)單。

但是,我記得 MySQL 是沒(méi)有遞歸查詢功能的,那 MySQL 中應(yīng)該怎么實(shí)現(xiàn)呢?

于是,就有了這篇文章。

文章主要知識(shí)點(diǎn):

  • Oracle 遞歸查詢, start with connect by prior 用法
  • find_in_set 函數(shù)
  • concat,concat_ws,group_concat 函數(shù)
  • MySQL 自定義函數(shù)
  • 手動(dòng)實(shí)現(xiàn) MySQL 遞歸查詢

Oracle 遞歸查詢

在 Oracle 中是通過(guò) start with connect by prior 語(yǔ)法來(lái)實(shí)現(xiàn)遞歸查詢的。

按照 prior 關(guān)鍵字在子節(jié)點(diǎn)端還是父節(jié)點(diǎn)端,以及是否包含當(dāng)前查詢的節(jié)點(diǎn),共分為四種情況。

prior 在子節(jié)點(diǎn)端(向下遞歸)

第一種情況:start with 子節(jié)點(diǎn)id = ' 查詢節(jié)點(diǎn) ' connect by prior 子節(jié)點(diǎn)id = 父節(jié)點(diǎn)id

  1. select * from dept start with id='1001' connet by prior id=pid; 

這里,按照條件 id='1001' 對(duì)當(dāng)前節(jié)點(diǎn)以及它的子節(jié)點(diǎn)遞歸查詢。查詢結(jié)果包含自己及所有子節(jié)點(diǎn)。

 

 


 

 

第二種情況:start with 父節(jié)點(diǎn)id= ' 查詢節(jié)點(diǎn) ' connect by prior 子節(jié)點(diǎn)id = 父節(jié)點(diǎn) id

  1. select * from dept start with pid='1001' connect by prior id=pid; 

這里,按照條件 pid='1001' 對(duì)當(dāng)前節(jié)點(diǎn)的所有子節(jié)點(diǎn)遞歸查詢。查詢結(jié)果只包含它的所有子節(jié)點(diǎn),不包含自己。

 

其實(shí)想一想也對(duì),因?yàn)殚_(kāi)始條件是以父節(jié)點(diǎn)為根節(jié)點(diǎn),且向下遞歸,自然不包含當(dāng)前節(jié)點(diǎn)。

prior 在父節(jié)點(diǎn)端(向上遞歸)

第三種情況:start with 子節(jié)點(diǎn)id= ' 查詢節(jié)點(diǎn) ' connect by prior 父節(jié)點(diǎn)id = 子節(jié)點(diǎn)id

  1. select * from dept start with id='1001' connect by prior pid=id;

這里按照條件 id='1001' ,對(duì)當(dāng)前節(jié)點(diǎn)及其父節(jié)點(diǎn)遞歸查詢。查詢結(jié)果包括自己及其所有父節(jié)點(diǎn)。

 

第四種情況:start with 父節(jié)點(diǎn)id= ' 查詢節(jié)點(diǎn) ' connect by prior 父節(jié)點(diǎn)id = 子節(jié)點(diǎn)id

  1. select * from dept start with pid='1001' connect by prior pid=id; 

這里按照條件 pid='1001',對(duì)當(dāng)前節(jié)點(diǎn)的第一代子節(jié)點(diǎn)以及它的父節(jié)點(diǎn)遞歸查詢。查詢結(jié)果包括自己的第一代子節(jié)點(diǎn)以及所有父節(jié)點(diǎn)。(包括自己)

 

其實(shí)這種情況也好理解,因?yàn)椴樵冮_(kāi)始條件是以 父節(jié)點(diǎn)為根節(jié)點(diǎn),且向上遞歸,自然需要把當(dāng)前父節(jié)點(diǎn)的第一層子節(jié)點(diǎn)包括在內(nèi)。

以上四種情況初看可能會(huì)讓人迷惑,容易記混亂,其實(shí)不然。

我們只需要記住 prior 的位置在子節(jié)點(diǎn)端,就向下遞歸,在父節(jié)點(diǎn)端就向上遞歸。

  • 開(kāi)始條件若是子節(jié)點(diǎn)的話,自然包括它本身的節(jié)點(diǎn)。
  • 開(kāi)始條件若是父節(jié)點(diǎn)的話,則向下遞歸時(shí),自然不包括當(dāng)前節(jié)點(diǎn)。而向上遞歸,需要包括當(dāng)前節(jié)點(diǎn)及其第一代子節(jié)點(diǎn)。

MySQL 遞歸查詢

可以看到,Oracle 實(shí)現(xiàn)遞歸查詢非常的方便。但是,在 MySQL 中并沒(méi)有幫我們處理,因此需要我們自己手動(dòng)實(shí)現(xiàn)遞歸查詢。

為了方便,我們創(chuàng)建一個(gè)部門(mén)表,并插入幾條可以形成遞歸關(guān)系的數(shù)據(jù)。

  1. DROP TABLE IF EXISTS `dept`; 
  2. CREATE TABLE `dept`  ( 
  3.   `id` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL
  4.   `namevarchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL
  5.   `pid` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL
  6.   PRIMARY KEY (`id`) USING BTREE 
  7. ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic
  8.  
  9. INSERT INTO `dept`(`id`, `name`, `pid`) VALUES ('1000''總公司'NULL); 
  10. INSERT INTO `dept`(`id`, `name`, `pid`) VALUES ('1001''北京分公司''1000'); 
  11. INSERT INTO `dept`(`id`, `name`, `pid`) VALUES ('1002''上海分公司''1000'); 
  12. INSERT INTO `dept`(`id`, `name`, `pid`) VALUES ('1003''北京研發(fā)部''1001'); 
  13. INSERT INTO `dept`(`id`, `name`, `pid`) VALUES ('1004''北京財(cái)務(wù)部''1001'); 
  14. INSERT INTO `dept`(`id`, `name`, `pid`) VALUES ('1005''北京市場(chǎng)部''1001'); 
  15. INSERT INTO `dept`(`id`, `name`, `pid`) VALUES ('1006''北京研發(fā)一部''1003'); 
  16. INSERT INTO `dept`(`id`, `name`, `pid`) VALUES ('1007''北京研發(fā)二部''1003'); 
  17. INSERT INTO `dept`(`id`, `name`, `pid`) VALUES ('1008''北京研發(fā)一部一小組''1006'); 
  18. INSERT INTO `dept`(`id`, `name`, `pid`) VALUES ('1009''北京研發(fā)一部二小組''1006'); 
  19. INSERT INTO `dept`(`id`, `name`, `pid`) VALUES ('1010''北京研發(fā)二部一小組''1007'); 
  20. INSERT INTO `dept`(`id`, `name`, `pid`) VALUES ('1011''北京研發(fā)二部二小組''1007'); 
  21. INSERT INTO `dept`(`id`, `name`, `pid`) VALUES ('1012''北京市場(chǎng)一部''1005'); 
  22. INSERT INTO `dept`(`id`, `name`, `pid`) VALUES ('1013''上海研發(fā)部''1002'); 
  23. INSERT INTO `dept`(`id`, `name`, `pid`) VALUES ('1014''上海研發(fā)一部''1013'); 
  24. INSERT INTO `dept`(`id`, `name`, `pid`) VALUES ('1015''上海研發(fā)二部''1013'); 

沒(méi)錯(cuò),剛才 Oracle 遞歸,就是用的這張表。

圖1

 

另外,在這之前,我們需要復(fù)習(xí)一下幾個(gè) MYSQL中的函數(shù),后續(xù)會(huì)用到。

find_in_set 函數(shù)

函數(shù)語(yǔ)法:find_in_set(str,strlist)

str 代表要查詢的字符串 , strlist 是一個(gè)以逗號(hào)分隔的字符串,如 ('a,b,c')。

此函數(shù)用于查找 str 字符串在字符串 strlist 中的位置,返回結(jié)果為 1 ~ n 。若沒(méi)有找到,則返回0。

舉個(gè)栗子:

  1. select FIND_IN_SET('b','a,b,c,d'); 

結(jié)果返回 2 。因?yàn)?b 所在位置為第二個(gè)子串位置。

 

此外,在對(duì)表數(shù)據(jù)進(jìn)行查詢時(shí),它還有一種用法,如下:

  1. select * from dept where FIND_IN_SET(id,'1000,1001,1002'); 

結(jié)果返回所有 id 在 strlist 中的記錄,即 id = '1000' ,id = '1001' ,id = '1002' 三條記錄。

 

看到這,對(duì)于我們要解決的遞歸查詢,不知道你有什么啟發(fā)沒(méi)。

以向下遞歸查詢所有子節(jié)點(diǎn)為例。我想,是不是可以找到一個(gè)包含當(dāng)前節(jié)點(diǎn)和所有子節(jié)點(diǎn)的以逗號(hào)拼接的字符串 strlist,傳進(jìn) find_in_set 函數(shù)。就可以查詢出所有需要的遞歸數(shù)據(jù)了。

那么,現(xiàn)在問(wèn)題就轉(zhuǎn)化為怎樣構(gòu)造這樣的一個(gè)字符串 strlist 。

這就需要用到以下字符串拼接函數(shù)了。

concat,concat_ws,group_concat

函數(shù)一、字符串拼接函數(shù)中,最基本的就是 concat 了。它用于連接N個(gè)字符串,如,

  1. select CONCAT('M','Y','S','Q','L'from dual;  

結(jié)果為 'MYSQL' 字符串。

 

二、concat 是以逗號(hào)為默認(rèn)的分隔符,而 concat_ws 則可以指定分隔符,第一個(gè)參數(shù)傳入分隔符,如以下劃線分隔。

 

三、group_concat 函數(shù)更強(qiáng)大,可以分組的同時(shí),把字段以特定分隔符拼接成字符串。

用法:group_concat( [distinct] 要連接的字段 [order by 排序字段 asc/desc ] [separator '分隔符'] )

可以看到有可選參數(shù),可以對(duì)將要拼接的字段值去重,也可以排序,指定分隔符。若沒(méi)有指定,默認(rèn)以逗號(hào)分隔。

對(duì)于 dept 表,我們可以把表中的所有 id 以逗號(hào)拼接。(這里沒(méi)有用到 group by 分組字段,則可以認(rèn)為只有一組)

 

MySQL 自定義函數(shù),實(shí)現(xiàn)遞歸查詢

可以發(fā)現(xiàn)以上已經(jīng)把字符串拼接的問(wèn)題也解決了。那么,問(wèn)題就變成怎樣構(gòu)造有遞歸關(guān)系的字符串了。

我們可以自定義一個(gè)函數(shù),通過(guò)傳入根節(jié)點(diǎn)id,找到它的所有子節(jié)點(diǎn)。

以向下遞歸為例。 (講解自定義函數(shù)寫(xiě)法的同時(shí),講解遞歸邏輯)

  1. delimiter $$  
  2. drop function if exists get_child_list$$  
  3. create function get_child_list(in_id varchar(10)) returns varchar(1000)  
  4. begin  
  5.  declare ids varchar(1000) default '';  
  6.  declare tempids varchar(1000);  
  7.   
  8.  set tempids = in_id;  
  9.  while tempids is not null do  
  10.   set ids = CONCAT_WS(',',ids,tempids);  
  11.   select GROUP_CONCAT(id) into tempids from dept where FIND_IN_SET(pid,tempids)>0;   
  12.  end while;  
  13.  return ids;  
  14. end   
  15. $$  
  16. delimiter ;  

(1) delimiter $$ ,用于定義結(jié)束符。我們知道 MySQL 默認(rèn)的結(jié)束符為分號(hào),表明指令結(jié)束并執(zhí)行。但是在函數(shù)體中,有時(shí)我們希望遇到分號(hào)不結(jié)束,因此需要暫時(shí)把結(jié)束符改為一個(gè)隨意的其他值。我這里設(shè)置為 $$,意思是遇到 $$ 才結(jié)束,并執(zhí)行當(dāng)前語(yǔ)句。

(2)drop function if exists get_child_list$$ 。若函數(shù) get_child_list 已經(jīng)存在了,則先刪除它。注意這里需要用 當(dāng)前自定義的結(jié)束符 $$ 來(lái)結(jié)束并執(zhí)行語(yǔ)句。因?yàn)?,這里需要數(shù)和下邊的函體單獨(dú)區(qū)分開(kāi)來(lái)執(zhí)行。

(3)create function get_child_list 創(chuàng)建函數(shù)。并且參數(shù)傳入一個(gè)根節(jié)點(diǎn)的子節(jié)點(diǎn)id,需要注意一定要注明參數(shù)的類(lèi)型和長(zhǎng)度,如這里是 varchar(10)。returns varchar(1000) 用來(lái)定義返回值參數(shù)類(lèi)型。

(4)begin 和 end 中間包圍的就是函數(shù)體。用來(lái)寫(xiě)具體的邏輯。

(5)declare 用來(lái)聲明變量,并且可以用 default 設(shè)置默認(rèn)值。

這里定義的 ids 即作為整個(gè)函數(shù)的返回值,是用來(lái)拼接成最終我們需要的以逗號(hào)分隔的遞歸串的。

而 tempids 是為了記錄下邊 while 循環(huán)中臨時(shí)生成的所有子節(jié)點(diǎn)以逗號(hào)拼接成的字符串。

(6) set 用來(lái)給變量賦值。此處把傳進(jìn)來(lái)的根節(jié)點(diǎn)賦值給 tempids 。

(7) while do ... end while; 循環(huán)語(yǔ)句,循環(huán)邏輯包含在內(nèi)。注意,end while 末尾需要加上分號(hào)。

循環(huán)體內(nèi),先用 CONCAT_WS 函數(shù)把最終結(jié)果 ids 和 臨時(shí)生成的 tempids 用逗號(hào)拼接起來(lái)。

然后以 FIND_IN_SET(pid,tempids)>0 為條件,遍歷在 tempids 中的所有 pid ,尋找以此為父節(jié)點(diǎn)的所有子節(jié)點(diǎn) id ,并且通過(guò) GROUP_CONCAT(id) into tempids 把這些子節(jié)點(diǎn) id 都用逗號(hào)拼接起來(lái),并覆蓋更新 tempids 。

等下次循環(huán)進(jìn)來(lái)時(shí),就會(huì)再次拼接 ids ,并再次查找所有子節(jié)點(diǎn)的所有子節(jié)點(diǎn)。循環(huán)往復(fù),一層一層的向下遞歸遍歷子節(jié)點(diǎn)。直到判斷 tempids 為空,說(shuō)明所有子節(jié)點(diǎn)都已經(jīng)遍歷完了,就結(jié)束整個(gè)循環(huán)。

這里,用 '1000' 來(lái)舉例,即是:(參看圖1的表數(shù)據(jù)關(guān)系)

  1. 第一次循環(huán): 
  2.   tempids=1000 ids=1000 tempids=1001,1002 (1000的所有子節(jié)點(diǎn)) 
  3. 第二次循環(huán): 
  4.   tempids=1001,1002  ids=1000,1001,1002  tempids=1003,1004,1005,1013 (1001和1002的所有子節(jié)點(diǎn)) 
  5. 第三次循環(huán): 
  6.   tempids=1003,1004,1005,1013  
  7.   ids=1000,1001,1002,1003,1004,1005,1013  
  8.   tempids=1003和1004和1005及1013的所有子節(jié)點(diǎn) 
  9. ... 
  10. 最后一次循環(huán),因找不到子節(jié)點(diǎn),tempids=null,就結(jié)束循環(huán)。 

(8)return ids; 用于把 ids 作為函數(shù)返回值返回。

(9)函數(shù)體結(jié)束以后,記得用結(jié)束符 $$ 來(lái)結(jié)束整個(gè)邏輯,并執(zhí)行。

(10)最后別忘了,把結(jié)束符重新設(shè)置為默認(rèn)的結(jié)束符分號(hào) 。

自定義函數(shù)做好之后,我們就可以用它來(lái)遞歸查詢我們需要的數(shù)據(jù)了。如,我查詢北京研發(fā)部的所有子節(jié)點(diǎn)。

 

以上是向下遞歸查詢所有子節(jié)點(diǎn)的,并且包括了當(dāng)前節(jié)點(diǎn),也可以修改邏輯為不包含當(dāng)前節(jié)點(diǎn),我就不演示了。

手動(dòng)實(shí)現(xiàn)遞歸查詢(向上遞歸)

相對(duì)于向下遞歸來(lái)說(shuō),向上遞歸比較簡(jiǎn)單。

因?yàn)橄蛳逻f歸時(shí),每一層遞歸一個(gè)父節(jié)點(diǎn)都對(duì)應(yīng)多個(gè)子節(jié)點(diǎn)。

而向上遞歸時(shí),每一層遞歸一個(gè)子節(jié)點(diǎn)只對(duì)應(yīng)一個(gè)父節(jié)點(diǎn),關(guān)系比較單一。

同樣的,我們可以定義一個(gè)函數(shù) get_parent_list 來(lái)獲取根節(jié)點(diǎn)的所有父節(jié)點(diǎn)。

  1. delimiter $$  
  2. drop function if exists get_parent_list$$  
  3. create function get_parent_list(in_id varchar(10)) returns varchar(1000)  
  4. begin  
  5.  declare ids varchar(1000);  
  6.  declare tempid varchar(10);  
  7.    
  8.  set tempid = in_id;  
  9.  while tempid is not null do  
  10.   set ids = CONCAT_WS(',',ids,tempid);  
  11.   select pid into tempid from dept where id=tempid;  
  12.  end while;  
  13.  return ids;  
  14. end  
  15. $$  
  16. delimiter ;  

查找北京研發(fā)二部一小組,以及它的遞歸父節(jié)點(diǎn),如下:

 

注意事項(xiàng)

我們用到了 group_concat 函數(shù)來(lái)拼接字符串。但是,需要注意它是有長(zhǎng)度限制的,默認(rèn)為 1024 字節(jié)??梢酝ㄟ^(guò) show variables like "group_concat_max_len"; 來(lái)查看。

注意,單位是字節(jié),不是字符。在 MySQL 中,單個(gè)字母占1個(gè)字節(jié),而我們平時(shí)用的 utf-8下,一個(gè)漢字占3個(gè)字節(jié)。

這個(gè)對(duì)于遞歸查詢還是非常致命的。因?yàn)橐话氵f歸的話,關(guān)系層級(jí)都比較深,很有可能超過(guò)最大長(zhǎng)度。(盡管一般拼接的都是數(shù)字字符串,即單字節(jié))

所以,我們有兩種方法解決這個(gè)問(wèn)題:

修改 MySQL 配置文件 my.cnf ,增加 group_concat_max_len = 102400 #你要的最大長(zhǎng)度 。

執(zhí)行以下任意一個(gè)語(yǔ)句。SET GLOBAL group_concat_max_len=102400; 或者 SET SESSION group_concat_max_len=102400;

他們的區(qū)別在于,global是全局的,任意打開(kāi)一個(gè)新的會(huì)話都會(huì)生效,但是注意,已經(jīng)打開(kāi)的當(dāng)前會(huì)話并不會(huì)生效。而 session 是只會(huì)在當(dāng)前會(huì)話生效,其他會(huì)話不生效。

共同點(diǎn)是,它們都會(huì)在 MySQL 重啟之后失效,以配置文件中的配置為準(zhǔn)。所以,建議直接修改配置文件。102400 的長(zhǎng)度一般也夠用了。假設(shè)一個(gè)id的長(zhǎng)度為10個(gè)字節(jié),也能拼上一萬(wàn)個(gè)id了。

除此之外,使用 group_concat 函數(shù)還有一個(gè)限制,就是不能同時(shí)使用 limit 。如,

 

 

本來(lái)只想查5條數(shù)據(jù)來(lái)拼接,現(xiàn)在不生效了。

不過(guò),如果需要的話,可以通過(guò)子查詢來(lái)實(shí)現(xiàn),

本文轉(zhuǎn)載自微信公眾號(hào)「 煙雨星空」,可以通過(guò)以下二維碼關(guān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系 煙雨星空公眾號(hào)。

 

責(zé)任編輯:武曉燕 來(lái)源: 煙雨星空
相關(guān)推薦

2022-10-17 00:04:30

索引SQL訂單

2022-03-02 15:59:37

HarmonyOS操作系統(tǒng)鴻蒙

2022-02-17 08:54:44

Service開(kāi)發(fā)Mybatis

2022-04-10 18:10:24

CURD鏈表

2024-01-12 16:20:04

2021-04-06 06:23:18

MVCC并發(fā)事務(wù)

2019-05-23 09:30:22

網(wǎng)絡(luò)框架數(shù)據(jù)

2022-01-07 13:36:00

MySQL數(shù)據(jù)庫(kù)分頁(yè)

2023-01-26 02:16:17

2020-12-23 09:04:56

開(kāi)發(fā)雙引號(hào)數(shù)據(jù)

2021-06-28 07:13:35

SQL語(yǔ)句索引

2021-12-02 08:19:06

MVCC面試數(shù)據(jù)庫(kù)

2019-08-28 14:25:00

線程安全容器

2024-04-09 08:07:00

遞歸查詢MySQL數(shù)據(jù)庫(kù)

2022-03-23 08:01:04

Python語(yǔ)言代碼

2010-04-02 15:04:14

Oracle遞歸查詢

2023-12-11 08:21:02

數(shù)據(jù)的可靠性一致性Kafka

2020-04-07 08:00:02

Redis緩存數(shù)據(jù)

2021-12-03 11:57:27

代碼##語(yǔ)言

2021-01-18 11:27:03

Istio架構(gòu)云環(huán)境
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)