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

          共 3423字,需瀏覽 7分鐘

           ·

          2022-04-11 16:56

          點(diǎn)擊上方“碼農(nóng)突圍”,馬上關(guān)注

          這里是碼農(nóng)充電第一站,回復(fù)“666”,獲取一份專屬大禮包

          真愛,請(qǐng)?jiān)O(shè)置“星標(biāo)”或點(diǎn)個(gè)“在看”


          來源:https://sourl.cn/aCCTwr


          |?目錄
          • 問題來了

          • 查出所有子孫部門

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

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

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

          • 查出所有子孫部門

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

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

          • 其他基本操作

          • 完結(jié)



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


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


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

          id??????????部門編號(hào)
          name????????部門名稱
          level???????所在樹層級(jí)
          parent_id???上級(jí)部門編號(hào)

          | 問題來了


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

          例如:PM加了以下需求:

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

          查出所有子孫部門


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

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

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


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

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


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

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

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

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


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

          他具體是怎么做的呢?

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


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


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


          數(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ù)


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

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

          例如:

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

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

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

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

          判斷是否葉子節(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)。
          董事長(zhǎng),20 - 1 != 1,因此他不是葉子節(jié)點(diǎn)。

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

          | 其他基本操作


          新增部門


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


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

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

          刪除部門


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


          對(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*/

          查詢直接子部門


          查詢某部門的直接子部門(即不包含孫子部門),例如:查詢總經(jīng)理下的直接子部門。正常需要返回產(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;

          查詢祖鏈路徑


          查詢某部門的祖鏈路徑。例如:查詢產(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ù)據(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?=?[]?/*類似于一個(gè)棧結(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ì)算出上級(jí)部門編號(hào)
          ????item.is_leaf?=?item.lft?===?item.rgt?-?1;//判斷是否葉子部門
          ????rights.push(item.rgt)
          })

          /*上級(jí)部門計(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é)


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

          (完)

          碼農(nóng)突圍資料鏈接

          1、臥槽!字節(jié)跳動(dòng)《算法中文手冊(cè)》火了,完整版 PDF 開放下載!
          2、計(jì)算機(jī)基礎(chǔ)知識(shí)總結(jié)與操作系統(tǒng) PDF 下載
          3、艾瑪,終于來了!《LeetCode Java版題解》.PDF
          4、Github 10K+,《LeetCode刷題C/C++版答案》出爐.PDF

          歡迎添加魚哥個(gè)人微信:smartfish2020,進(jìn)粉絲群或圍觀朋友圈。

          瀏覽 42
          點(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>
                  欧美爱爱免费看 | 99视频精品6 | 在线观看色网 | 日韩无码毛片 | 久久久久久久97 |