<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>

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

          共 3273字,需瀏覽 7分鐘

           ·

          2022-04-12 23:35

          點(diǎn)擊上方藍(lán)色字體,選擇“設(shè)為星標(biāo)”


          回復(fù)”學(xué)習(xí)資料“獲取學(xué)習(xí)寶典


          文章來(lái)源:https://sourl.cn/aCCTwr


          |?目錄
          • 問(wèn)題來(lái)了

          • 查出所有子孫部門(mén)

          • 查詢(xún)子孫部門(mén)總數(shù)

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

          • 要不試試這個(gè)方法?

          • 查出所有子孫部門(mén)

          • 查詢(xún)子孫部門(mén)總數(shù)

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

          • 其他基本操作

          • 完結(jié)



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


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


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

          id??????????部門(mén)編號(hào)
          name????????部門(mén)名稱(chēng)
          level???????所在樹(shù)層級(jí)
          parent_id???上級(jí)部門(mén)編號(hào)

          | 問(wèn)題來(lái)了


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

          例如:PM加了以下需求:

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

          查出所有子孫部門(mén)


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

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

          查詢(xún)子孫部門(mén)總數(shù)


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

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


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

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

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

          | 要不試試這個(gè)方法?


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

          他具體是怎么做的呢?

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


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


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


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

          查出所有子孫部門(mén)


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

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

          完美~

          查詢(xún)子孫部門(mén)總數(shù)


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

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

          例如:

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

          董事長(zhǎng)的子孫部門(mén)數(shù)?=?(20?-?1?-?1)?/?2?=?9

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

          可以數(shù)數(shù)看,確實(shí)沒(méi)錯(cuò)哦~

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


          通過(guò)有了上述計(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)。
          董事長(zhǎng),20 - 1 != 1,因此他不是葉子節(jié)點(diǎn)。

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

          | 其他基本操作


          新增部門(mén)


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


          對(duì)應(yīng)sql:

          SET?@lft?:=?7;/*新部門(mén)的左值*/
          SET?@rgt?:=?8;/*新部門(mén)的左值*/
          SET?@level?:=?5;/*新部門(mén)的層級(jí)*/
          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('新部門(mén)',@lft,@rgt,level);
          /*新增影響行數(shù)為0時(shí),必須回滾*/
          commit;
          /*rollback;*/

          刪除部門(mén)


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


          對(duì)應(yīng)sql

          SET?@lft?:=?7;/*要?jiǎng)h除的節(jié)點(diǎn)左值*/
          SET?@rgt?:=?8;/*要?jiǎng)h除的節(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時(shí),必須回滾*/
          commit;
          /*rollback*/

          查詢(xún)直接子部門(mén)


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


          對(duì)應(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;

          查詢(xún)祖鏈路徑


          查詢(xún)某部門(mén)的祖鏈路徑。例如:查詢(xún)產(chǎn)品部的祖鏈路徑,正常需要返回董事長(zhǎng),總經(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ù)形數(shù)據(jù)展示(JS示例)


          let?list?=?[//模擬sql查出來(lái)的列表。
          ????{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?=?[]?/*類(lèi)似于一個(gè)棧結(jié)構(gòu)(后進(jìn)先出)*/
          let?mp?=?{}
          //list.sort((a,b)?=> a.lft - b.lft)//如果你在sql中沒(méi)有進(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ì)算出上級(jí)部門(mén)編號(hào)
          ????item.is_leaf?=?item.lft?===?item.rgt?-?1;//判斷是否葉子部門(mén)
          ????rights.push(item.rgt)
          })

          /*上級(jí)部門(mén)計(jì)算出來(lái)了,和存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é)


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

          -------------? END??-------------
          掃描下方二維碼,加入技術(shù)群。暗號(hào):加群


          瀏覽 47
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

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

          手機(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>
                  一级A片电影 | 性欧美成人18 | 婷婷午夜精品久久久久久性色AV | 亚洲狼友视频 | 想要xx.mp4 |