mysql巧妙化解遞歸查詢樹形數(shù)據(jù) | 純sql

前言
開發(fā)中樹形結(jié)構(gòu)應(yīng)該是很常見的一種數(shù)據(jù)結(jié)構(gòu)了。而在數(shù)據(jù)庫方面往往也都伴隨相應(yīng)的樹形設(shè)計(jì)。在?
mysql
?中通過?parent_id
?來綁定其上游,從而達(dá)到樹形結(jié)構(gòu)的存儲,但是在查詢的過程中就需要我們將?List
?列表轉(zhuǎn)成我們理想中的?Tree
?樹。
構(gòu)建樹
相信大部分我們 都是通過
Java
來處理的。 其中getParentLocation
就是用遞歸不斷的去構(gòu)建上下級關(guān)系。這種方式也是我比較推薦的,因?yàn)檫@樣就把職責(zé)分的很清楚Java
負(fù)責(zé)處理業(yè)務(wù) ,數(shù)據(jù)庫
就僅僅用來做數(shù)據(jù)的持久化,這也方便我們對數(shù)據(jù)庫切換與升級。否則在更換其他數(shù)據(jù)庫時(shí)還需要考慮是否支持遞歸屬性。
有的時(shí)候?yàn)榱四軌蛄慨a(chǎn)化,這里我也針對項(xiàng)目的場景利用泛型進(jìn)行一次封裝。有興趣可以自己針對自己的項(xiàng)目場景進(jìn)行封裝。這樣樹形與列表的轉(zhuǎn)換就無須每次冗余了。然而今天我們的重點(diǎn)是如何利用
mysql
來實(shí)現(xiàn)遞歸查詢, 雖然這種方式個(gè)人不推薦使用,但我們還是需要了解mysql
的特性的。

如圖我們先在數(shù)據(jù)庫中創(chuàng)建一張?
test
?表, 表里構(gòu)建一棵樹出來。?祖父→父親→孫子
?。
請根據(jù)選中節(jié)點(diǎn)查詢其上游關(guān)系
啥意思呢?就是根據(jù)你我想找到你家血脈關(guān)系。

這個(gè)業(yè)務(wù)本身沒有難點(diǎn),我們只需要一步一步的去尋找即可。但是從技術(shù)層面上看就有點(diǎn)搞頭了。首先我們不確定是多少層,這就無法通過代碼堆砌的方法進(jìn)行實(shí)現(xiàn)了。只能動(dòng)態(tài)的去遞歸查詢。

通過上述代碼我們即可完成家族血脈的查詢。能夠清楚的查出家族的具體支脈。
解析
上面的?
sql
?雖然能夠?qū)崿F(xiàn)遞歸,但是好像和我們平時(shí)接觸的?sql
?不大一樣。這里我們簡單分析下
首先?
mysql
?是支持我們用戶定義對象的。比如下面這個(gè)?sql
執(zhí)行上面的sql 你會發(fā)現(xiàn)查出來的就是
123
。 這就是使用到mysql
中變量定義功能。 通過@
來進(jìn)行對象的定義以及賦值。

首先
mysql
的執(zhí)行順序是由里及外 。 就是說越在內(nèi)部越先執(zhí)行。所以針對上面的SQL
我們將它整理下順序能夠得出。

注意
(SELECT @r := parent_id FROM test WHERE id = _id) AS parent_id,
這段sql 實(shí)際上歸屬于第二層。圖示中并沒有列出,太占地方了。
第三層
有了上面的兩個(gè)知識點(diǎn)鋪墊,現(xiàn)在我們直接看
SELECT @r := 3, @l := 0
是不是就很容易明白了呢?r、l
就是mysql
中定義出來的兩個(gè)變量。分別是3,0
。和我們上面演示案列中select @name from dual
是一個(gè)意思。至于
(SELECT @r := 3, @l := 0) vars, test AS h
這段就更好理解了。test本身就是一張物理表 , 我們定義出來的兩個(gè)變量也可以理解成一張表。在mysql中查詢兩種表是為了區(qū)分開來正常都是需要各自取別名的。所以我們定義的變量就是vars
別名 。test
別名h
。
第二層
第二層可以說是遞歸的核心 。這里我們需要明白 上述代碼為什么會產(chǎn)生遞歸的效果呢?首先我們知道?
mysql
?存在?左外連接
、右外連接
、內(nèi)連接
?。除了這三種還有一種就是?笛卡爾積
連接。什么意思呢?

也就是說上面我們的sql?
FROM (SELECT @r := 3, @l := 0) vars, test AS h
?就是為了給我們構(gòu)建出一個(gè)笛卡爾積 ,而我們定義的變量其實(shí)就是一張表里的一條數(shù)據(jù),所以這里就是將?test
?表所有記錄都提取出來。


也就是說我們第二層的sql 就是將 test表全部查出來,然后將其字段進(jìn)行擴(kuò)展。說白了這種方式并不是一種真正的遞歸。但是因?yàn)橐肓艘粋€(gè)變量,所以在擴(kuò)充的時(shí)候和遞歸一個(gè)效果。
但是笛卡爾積查詢出來的數(shù)據(jù)是無法保證兩張表的關(guān)聯(lián)性的,所以我們并沒有將
h
表相關(guān)字段查出來,因?yàn)槟歉緵]用。剩下的就是我們一開始說的一步一步的查詢擴(kuò)充字段了
第一層
第一層就簡單很多了 ,因?yàn)榈诙泳鸵呀?jīng)查詢出來相關(guān)的樹形數(shù)據(jù)了,但是因?yàn)榈诙邮褂玫牡芽柗e
h
表信息沒法使用,僅僅使用了其數(shù)量。那么我們的名稱這個(gè)時(shí)候還沒有,第一層的作用就是將節(jié)點(diǎn)名稱擴(kuò)充出來。
總結(jié)
最后我們簡單總結(jié)下,
mysql
的查詢遞歸正常使用存儲過程來實(shí)現(xiàn)。但是上面提到的方法巧妙的實(shí)現(xiàn)了遞歸的效果。理論上上述方法和存儲過程相比存在一個(gè)優(yōu)點(diǎn)就是不會死循環(huán)。
