<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          超贊,老外的一種避免遞歸查詢所有子部門的樹數(shù)據(jù)表設(shè)計(jì)與實(shí)現(xiàn)!

          共 3425字,需瀏覽 7分鐘

           ·

          2022-04-10 00:52

          點(diǎn)擊下方“IT牧場”,選擇“設(shè)為星標(biāo)”

          |?目錄
          • 問題來了

          • 查出所有子孫部門

          • 查詢子孫部門總數(shù)

          • 判斷是否葉子節(jié)點(diǎn)

          • 要不試試這個方法?

          • 查出所有子孫部門

          • 查詢子孫部門總數(shù)

          • 判斷是否葉子節(jié)點(diǎn)

          • 其他基本操作

          • 完結(jié)



          通常樹形結(jié)構(gòu)的存儲,是在子節(jié)點(diǎn)上存儲父節(jié)點(diǎn)的編號來確定各節(jié)點(diǎn)的父子關(guān)系,例如這樣的組織結(jié)構(gòu):


          與之對應(yīng)的表數(shù)據(jù)(department):


          部門表結(jié)構(gòu)(department)

          id??????????部門編號
          name????????部門名稱
          level???????所在樹層級
          parent_id???上級部門編號

          | 問題來了


          這樣的方式很不錯,可以很直觀的體現(xiàn)各個節(jié)點(diǎn)之間的關(guān)系,通常可以滿足大多數(shù)需求。但是當(dāng)業(yè)務(wù)需求變得多了,數(shù)據(jù)量龐大了,這樣的方式就不再適合用于生產(chǎn)。

          例如:PM加了以下需求:

          1. 查出指定部門下所有子孫部門
          2. 查詢子孫部門總數(shù)
          3. 判斷節(jié)點(diǎn)是否葉子節(jié)點(diǎn)

          查出所有子孫部門


          使用指定部門編號,一層一層使用遞歸往下查,可能是多數(shù)人會想到的方法。盡管在mysql8.0支持了 cte(公共表表達(dá)式),遞歸效率比傳統(tǒng)遞歸方式有明顯提升,但是查詢效率仍會隨著部門樹層級深度的提高而變差。

          另外一種方法,一次性查出所有數(shù)據(jù),放入內(nèi)存中處理(數(shù)據(jù)量少時,可以選用。數(shù)據(jù)量多,不怕挨打的人也可以選這種)~

          查詢子孫部門總數(shù)


          遞歸查詢每一層的數(shù)量,最后相加。

          判斷是否葉子節(jié)點(diǎn)


          方法1:可以加字段?isLeaf?的方式,來表示這個節(jié)點(diǎn)是否是葉子節(jié)點(diǎn)。

          方法2:直接通過查詢parent_id=當(dāng)前id的count是否大于0,大于0表示不是葉子節(jié)點(diǎn),等于0表示為葉子節(jié)點(diǎn)。

          在日常中,可能會經(jīng)常使用上述類似方法去解決類似的問題,但我覺得這樣的方法在效率上不是最優(yōu)解。于是乎開始查找更好的方案去解決這些問題。

          | 要不試試這個方法?


          直到后面查到國外一博客中,見到了所謂的《改進(jìn)后的先序樹遍歷》文章(天哪,竟然是一篇2003年發(fā)表的文章)~

          他具體是怎么做的呢?

          還是回到剛剛的組織架構(gòu)


          我們從根節(jié)點(diǎn)開始,給董事長左值設(shè)為1,下級部門總經(jīng)理左值設(shè)為2,以此類推地沿著邊緣開始遍歷,給每個節(jié)點(diǎn)加上左值,遇到葉子節(jié)點(diǎn)處給節(jié)點(diǎn)加上右值,再繼續(xù)向上沿著邊緣繼續(xù)遍歷,遍歷結(jié)束回到根節(jié)點(diǎn)右側(cè),你將得到類似這樣的結(jié)構(gòu)。


          遍歷完后每一個節(jié)點(diǎn)都有與之對應(yīng)的左右值。這個時候可以去除parent_id字段,添加lft,rgt,來存儲左右值。


          數(shù)據(jù)和結(jié)構(gòu)準(zhǔn)備完畢,我們來試試操作解決上面的需求~

          查出所有子孫部門


          根據(jù)當(dāng)前表結(jié)構(gòu)的規(guī)律,可以發(fā)現(xiàn),要想查出所有子孫部門,只要查左值在 被查尋部門的左\右數(shù)之間的節(jié)點(diǎn),查出來都是他的子節(jié)點(diǎn)。例如:查詢行政總監(jiān)的所有子部門,行政總監(jiān)的左右數(shù)是9和18,因此只需要用9和18做lft字段的between查詢,查詢出的結(jié)果就是【被查部門本身數(shù)據(jù)和所有子孫部門】;

          SET?@lft?:=?9;
          SET?@rgt?:=?18;
          SELECT?*?FROM?department?WHERE?lft?BETWEEN?@lft?AND?@rgt?ORDER?BY?lft?ASC;
          /*例子中用BETWEEN將被查部門本身也查了出來。實(shí)際中可以用大于小于*/

          完美~

          查詢子孫部門總數(shù)


          到這里可能會說,需求1都解決了,查總數(shù)自然也就解決了,直接上select count就可以了,確實(shí)沒有錯,但是沒有那個必要,因?yàn)橛袀€簡單公式可以直接計(jì)算。

          公式:總數(shù) = (右值 - 左值 - 1) / 2

          例如:

          行政總監(jiān)的子孫部門數(shù)?=?(18?-?9?-?1)?/?2?=?4

          董事長的子孫部門數(shù)?=?(20?-?1?-?1)?/?2?=?9

          會計(jì)的子部門數(shù)?=??(14?-?13?-?1)?/?2?=?0

          可以數(shù)數(shù)看,確實(shí)沒錯哦~

          判斷是否葉子節(jié)點(diǎn)


          通過有了上述計(jì)算公式算總數(shù)的經(jīng)驗(yàn)后,現(xiàn)在判斷是否葉子節(jié)點(diǎn),有的小伙伴已經(jīng)知道了怎么做,那就是:

          右值 - 1 == 左值那他就是葉子節(jié)點(diǎn),或者左值 + 1 == 右值那他就是葉子節(jié)點(diǎn),反之則不是葉子節(jié)點(diǎn)。

          例如:

          設(shè)計(jì)部,5 - 1 == 4,因此他是葉子節(jié)點(diǎn)。
          董事長,20 - 1 != 1,因此他不是葉子節(jié)點(diǎn)。

          至此已經(jīng)完美的解決了上述需求問題,接下來再嘗試一下業(yè)務(wù)的基本操作

          | 其他基本操作


          新增部門


          當(dāng)新增一個部門時,需要對新增節(jié)點(diǎn)位置的后續(xù)邊緣進(jìn)行加2操作,因?yàn)槊恳粋€節(jié)點(diǎn)有左右兩個數(shù)值。這個操作通常需要放到事務(wù)中進(jìn)行處理。例如:在研發(fā)部門下添加一個新部門:


          對應(yīng)sql:

          SET?@lft?:=?7;/*新部門的左值*/
          SET?@rgt?:=?8;/*新部門的左值*/
          SET?@level?:=?5;/*新部門的層級*/
          begin;
          /*將插入的后續(xù)邊緣的節(jié)點(diǎn)左右數(shù)+2*/
          UPDATE?department?SET?lft=lft+2?WHERE?lft?>?@lft;
          UPDATE?department?SET?rgt=rgt+2?WHERE?rgt?>=?@lft;
          /*插入數(shù)據(jù)*/
          INSERT?INTO?department(name,lft,rgt,level)?VALUES('新部門',@lft,@rgt,level);
          /*新增影響行數(shù)為0時,必須回滾*/
          commit;
          /*rollback;*/

          刪除部門


          刪除部門與新增部門類似,不同的是需要對刪除節(jié)點(diǎn)的后續(xù)邊緣節(jié)點(diǎn)減2操作。例如:刪除剛剛添加的新部門:


          對應(yīng)sql

          SET?@lft?:=?7;/*要刪除的節(jié)點(diǎn)左值*/
          SET?@rgt?:=?8;/*要刪除的節(jié)點(diǎn)右值*/
          begin;
          UPDATE?department?SET?lft=lft-2?WHERE?lft?>?@lft;
          UPDATE?department?SET?rgt=rgt-2?WHERE?rgt?>?@lft;

          /*刪除節(jié)點(diǎn)*/
          DELETE?FROM?department?WHERE?lft=@lft?AND?rgt=@rgt;
          /*刪除影響行數(shù)為0時,必須回滾*/
          commit;
          /*rollback*/

          查詢直接子部門


          查詢某部門的直接子部門(即不包含孫子部門),例如:查詢總經(jīng)理下的直接子部門。正常需要返回產(chǎn)品部和行政總監(jiān)


          對應(yīng)的sql

          SET?@level?:=?2;/*總經(jīng)理的level*/
          SET?@lft?:=?2;/*總經(jīng)理的左值*/
          SET?@rgt?:=?19;/*總經(jīng)理的右值*/

          SELECT?*?FROM?department?WHERE?lft?>?@lft?AND?rgt?@rgt?AND?level?=?@level+1;

          查詢祖鏈路徑


          查詢某部門的祖鏈路徑。例如:查詢產(chǎn)品部的祖鏈路徑,正常需要返回董事長,總經(jīng)理

          SET?@lft?:=?3;/*產(chǎn)品部左值*/
          SET?@rgt?:=?8;/*產(chǎn)品部右值*/

          SELECT?*?FROM?department?WHERE?lft?@lft?AND?rgt?>?@rgt?ORDER?BY?lft?ASC;

          樹形數(shù)據(jù)展示(JS示例)


          let?list?=?[//模擬sql查出來的列表。
          ????{id:1,name:'root',lft:1,rgt:8,level:1},
          ????{id:2,name:'child',lft:2,rgt:7,level:2},
          ????{id:3,name:'grandson',lft:3,rgt:4,level:3},
          ????{id:4,name:'grandson2',lft:5,rgt:6,level:3}
          ];
          let?rights?=?[]?/*類似于一個棧結(jié)構(gòu)(后進(jìn)先出)*/
          let?mp?=?{}
          //list.sort((a,b)?=> a.lft - b.lft)//如果你在sql中沒有進(jìn)行排序,需要在這里給他排序。
          list.forEach(item?=>?{
          ????if(rights.length?>?0)?{
          ????????while(rights[rights.length-1]?????????????rights.splice(-1,?1)//從rights末尾去除
          ????????}
          ????}
          ????let?_level?=?rights.length;
          ????item._level?=?_level;
          ????mp[_level]?=?item.id
          ????item.parent_id?=?_level?-?1?in?mp???mp[_level?-?1]?:?null;//計(jì)算出上級部門編號
          ????item.is_leaf?=?item.lft?===?item.rgt?-?1;//判斷是否葉子部門
          ????rights.push(item.rgt)
          })

          /*上級部門計(jì)算出來了,和存parent_id的效果就一樣了,后面只需要遞歸即可*/
          /*遞歸函數(shù)?示例*/
          let?recursive?=?(_list,?parent_id?=?null)?=>?{
          ????let?_tree?=?[];
          ????_list.forEach(item?=>?{
          ????????if(item.parent_id?==?parent_id)?{
          ????????????let?childs?=?recursive(_list,?item.id)
          ????????????_tree.push({
          ????????????????...item,
          ????????????????children:?childs.length?>?0???childs?:?(item.isLeaf???null?:?[])
          ????????????})
          ????????}
          ????})
          ????return?_tree
          }
          console.log(recursive(list))

          | 完結(jié)


          在我目前看來,這個方法的唯一缺點(diǎn)就是,每一次的新增或刪除,操作節(jié)點(diǎn)的后續(xù)邊緣走到的節(jié)點(diǎn)都要加/減2操作。

          歡迎指正、交流和評論,一起探討更多解決方案 ...

          干貨分享

          最近將個人學(xué)習(xí)筆記整理成冊,使用PDF分享。關(guān)注我,回復(fù)如下代碼,即可獲得百度盤地址,無套路領(lǐng)??!

          ?001:《Java并發(fā)與高并發(fā)解決方案》學(xué)習(xí)筆記;?002:《深入JVM內(nèi)核——原理、診斷與優(yōu)化》學(xué)習(xí)筆記;?003:《Java面試寶典》?004:《Docker開源書》?005:《Kubernetes開源書》?006:《DDD速成(領(lǐng)域驅(qū)動設(shè)計(jì)速成)》?007:全部?008:加技術(shù)群討論

          加個關(guān)注不迷路

          喜歡就點(diǎn)個"在看"唄^_^

          瀏覽 34
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  一级一级一级一级一级一级 | 翔田千里熟妇息孑交尾 | 日韩另类 | 国产探花视频在线观看 | 欧美成人性生活在线视频打开 |