日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 运维知识 > 数据库 >内容正文

数据库

数据库无限层级分类设计

發(fā)布時間:2023/12/10 数据库 28 豆豆
生活随笔 收集整理的這篇文章主要介紹了 数据库无限层级分类设计 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

轉(zhuǎn)2019-07-15 數(shù)據(jù)庫無限層級分類設(shè)計 - 云+社區(qū) - 騰訊云

起步

在大多數(shù)的系統(tǒng)中,對內(nèi)容進(jìn)行分類是必要的。比如電商的商品分類;論壇的板塊等。

需求分析

分類之間的關(guān)系是怎樣的? 很明顯,一個分類下面可以是多個下級分類。反過來呢,一個下級分類能夠?qū)儆趲讉€上級分類呢?這個并不確定,得看具體的業(yè)務(wù)需求。如果是多個實現(xiàn)上會更加復(fù)雜,為了討論層級設(shè)計,這里先限定每個分類僅有一個上級分類。

對于某個分類,需要支持的操作如下:

  • 對單個分類的 CURD;
  • 查詢該分類的直屬下級或所有下級分類;
  • 查詢該分類的上級分類至頂級分類中的所有分類,并且是有序的;
  • 移動該分類,就是將節(jié)點移動到另一個節(jié)點下面,這是當(dāng)分類結(jié)構(gòu)不合理時,允許修改時使用;
  • 查詢某一級的所有分類(很少會用到)。
  • 從使用頻率來看,查詢是大多數(shù),畢竟分類也不會改來改去。所以性能考慮以查詢操作優(yōu)先,特別是操作2 和操作 3。

    方案一:記錄父分類的引用

    這是一種比較常見且維護(hù)的一個方案,添加一個 pid 指向父分類的 id :

    20190523135518.png

    表中 pid 即直屬上級分類的 id,頂級分類設(shè)為 0.這種方案簡單易懂,僅用一個字典,沒有冗余信息,存儲空間占用極小,在數(shù)據(jù)庫層面是一個很好的設(shè)計。(本博客系統(tǒng)的分類就是采用這種結(jié)構(gòu))

    對于分類的移動也非常友好,只要修改 pid 即可。而刪除分類也只需將其直屬子分類的 pid 改成該分類的 pid即可。但對于查詢該分類的所有上級至頂級分類或查詢就不友好了,需要進(jìn)行遞歸。

    查找所有父分類至頂級分類

    SELECTID.LEVEL,t.* FROM(SELECT@id AS _id,( SELECT @id := pid FROM lab_goods WHERE id = @id) AS _pid,@l := @l + 1 AS LEVELFROMlab_goods,(SELECT @id := 100, @l := 0) as bWHERE@id > 0) as ID,lab_goods as t WHEREID._id = t.id ORDER BY `LEVEL`;

    復(fù)制

    經(jīng)實驗該語句可用,但當(dāng)表中數(shù)據(jù)有一萬條,當(dāng)查詢的層級有1000時,耗時 2 秒。也就是當(dāng)分類的數(shù)量很多的時候,這個方案的性能并不好,但真實業(yè)務(wù)上會有這么多的分類嗎?

    查詢某分類的所有下級分類為:

    SELECTID. LEVEL,DATA .* FROM(SELECT@ids AS _ids,(SELECT @ids := GROUP_CONCAT(id) FROM lab_goods WHERE FIND_IN_SET(pid, @ids)) AS cids,@l := @l + 1 AS LEVELFROMlab_goods,(SELECT @ids := 10000, @l := 0) bWHERE@ids IS NOT NULL) id,lab_goods DATA WHEREFIND_IN_SET(DATA .id, ID._ids) ORDER BY `LEVEL`, id

    復(fù)制

    當(dāng)層級太多的時候性能是不佳的。而對于查詢某一級的所有分類,這個設(shè)計簡直是災(zāi)難。

    其實這個方案也是一開始就能想到的,在層級不深的情況下,這個方案不失為一個好的選擇。

    方案二:添加路徑列表

    針對方案一的短板,我們表中不僅僅記錄父分類id,還將它到頂級分類所有分類的id都保存下來。

    id

    name

    pid

    path

    1

    家用電器

    0

    -

    2

    電腦辦公

    0

    -

    3

    大家電

    1

    1

    4

    生活電器

    1

    1

    5

    電風(fēng)扇

    4

    1,4

    6

    電腦整機

    2

    1,2

    每個分類都保存從頂級分類到父分類的所有id。查詢某某分下的所有下級就可以用 like 進(jìn)行匹配:

    SELECT id,name FROM pathlist WHERE path LIKE '1,%'

    復(fù)制

    查詢某個分類的所有下級:

    SELECT id,name FROM pathlist WHERE path LIKE '%2'

    復(fù)制

    這個方案在各個方面都具有可以接受的性能,在上述例子中 1w 的數(shù)據(jù),1k 的層級,僅耗時 0.0009s 。

    但具有兩點不足:1. 不遵守數(shù)據(jù)庫范式,DBA看了想打人;2. 就是字段長度是有限的,但 longtext 最大能存儲 4G 的文本,我想沒有這么變態(tài)的層級數(shù)。所以這個分類在許多系統(tǒng)中使用。

    方案三:基于ClosureTable的無限級分類存儲

    另建一張表存儲節(jié)點之間的關(guān)系,其中包含了任何兩個有關(guān)系的節(jié)點的關(guān)聯(lián)信息:

    2696840886-5acc5fd7a44f3.png

    定義關(guān)系表 CategoryTree,其包含3個字段:

    • ancestor 祖先:上級節(jié)點的id
    • descendant 子代:下級節(jié)點的id
    • distance 距離:子代到祖先中間隔了幾級

    這三個字段的組合是唯一的,因為在樹中,一條路徑可以標(biāo)識一個節(jié)點,所以可以直接把它們的組合作為主鍵。以圖為例,節(jié)點6到它上一級的節(jié)點(節(jié)點4)距離為1在數(shù)據(jù)庫中存儲為ancestor=4,descendant=6,distance=1,到上兩級的節(jié)點(節(jié)點1)距離為2,于是有 ancestor=1,descendant=6,distance=2 ,到根節(jié)點的距離為3也是如此,最后還要包含一個到自己的連接,當(dāng)然距離就是0了。

    這樣一來,不盡表中包含了所有的路徑信息,還在帶上了路徑中每個節(jié)點的位置(距離),對于樹結(jié)構(gòu)常用的查詢都能夠很方便的處理。下面看看如何用用它來實現(xiàn)我們的需求。

    子節(jié)點查詢

    查詢id為5的節(jié)點的直屬子節(jié)點:

    SELECT descendant FROM CategoryTree WHERE ancestor=5 AND distance=1

    復(fù)制

    查詢所有子節(jié)點:

    SELECT descendant FROM CategoryTree WHERE ancestor=5 AND distance>0

    復(fù)制

    查詢某個上級節(jié)點的子節(jié)點,換句話說就是查詢具有指定上級節(jié)點的節(jié)點,也就是 ancestor 字段等于上級節(jié)點id即可,第二個距離 distance 決定了查詢的對象是由上級往下那一層的,等于1就是往下一層(直屬子節(jié)點),大于0就是所有子節(jié)點。這兩個查詢都是一句完成。

    路徑查詢

    查詢由根節(jié)點到id為10的節(jié)點之間的所有節(jié)點,按照層級由小到大排序

    SELECT ancestor FROM CategoryTree WHERE descendant=10 ORDER BY distance DESC

    復(fù)制

    查詢id為10的節(jié)點(含)到id為3的節(jié)點(不含)之間的所有節(jié)點,按照層級由小到大排序

    SELECT ancestor FROM CategoryTree WHERE descendant=10 AND distance<(SELECT distance FROM CategoryTree WHERE descendant=10 AND ancestor=3) ORDER BY distance DESC

    復(fù)制

    查詢路徑,只需要知道 descendant 即可,因為 descendant 字段所在的行就是記錄這個節(jié)點與其上級節(jié)點的關(guān)系。如果要過濾掉一些則可以限制 distance 的值。

    查詢節(jié)點所在的層級(深度)

    查詢id為5的節(jié)點是第幾級的

    SELECT distance FROM CategoryTree WHERE descendant=5 AND ancestor=0

    復(fù)制

    查詢id為5的節(jié)點是id為10的節(jié)點往下第幾級

    SELECT distance FROM CategoryTree WHERE descendant=5 AND ancestor=10

    復(fù)制

    查詢層級(深度)非常簡單,因為distance字段就是。直接以上下級的節(jié)點id為條件,查詢距離即可。

    查詢某一層的所有節(jié)點

    查詢所有第三層的節(jié)點

    SELECT descendant FROM CategoryTree WHERE ancestor=0 AND distance=3

    復(fù)制

    這個就不詳細(xì)說了,非常簡單。

    插入

    插入和移動就不是那么方便了,當(dāng)一個節(jié)點插入到某個父節(jié)點下方時,它將具有與父節(jié)點相似的路徑,然后再加上一個自身連接即可。

    所以插入操作需要兩條語句,第一條復(fù)制父節(jié)點的所有記錄,并把這些記錄的 distance 加一,因為子節(jié)點到每個上級節(jié)點的距離都比它的父節(jié)點多一。當(dāng)然 descendant 也要改成自己的。

    例如把id為10的節(jié)點插入到id為5的節(jié)點下方(這里用了Mysql的方言)

    INSERT INTO CategoryTree(ancestor,descendant,distance) (SELECT ancestor,10,distance+1 FROM CategoryTree WHERE descendant=5)

    復(fù)制

    然后就是加入自身連接的記錄。

    INSERT INTO CategoryTree(ancestor,descendant,distance) VALUES(10,10,0)

    復(fù)制

    刪除

    如果刪除分類同時也刪除其所有下級分類那好辦,先找出該節(jié)點所有子級節(jié)點逐個刪除:

    DELETE FROM CategoryTree WHERE descendant in (select descendant from CategoryTree where ancestor=4) ...

    復(fù)制

    而如果節(jié)點刪除后是需要將所有下級分類都劃分到該節(jié)點的直系上級。比如刪除節(jié)點4,那么需要把4 的所有子節(jié)點都?xì)w到該節(jié)點的直接上級:

    select descendant from CategoryTree where ancestor=4 // 查詢4的所有子節(jié)點// 當(dāng)子節(jié)點的父節(jié)點中超過該節(jié)點到 4節(jié)點距離時,距離- 1 update CategoryTree set distance = distance-1 where descendant=6 and distance > (select b.distance from (select * from CategoryTree where ancestor=4 and descendant=6 ) b); ...DELETE FROM CategoryTree WHERE ancestor=4 // 待下級節(jié)點的距離都修復(fù)完畢后刪除節(jié)點與下級節(jié)點的聯(lián)系 DELETE FROM CategoryTree WHERE descendant=4 // 刪除4節(jié)點本身

    復(fù)制

    移動

    節(jié)點的移動沒有很好的解決方法,因為新位置所在的深度、路徑都可能不一樣,這就導(dǎo)致移動操作不是僅靠UPDATE語句能完成的,這里選擇刪除+插入實現(xiàn)移動。

    另外,在有子樹的情況下,上級節(jié)點的移動還將導(dǎo)致下級節(jié)點的路徑改變,所以移動上級節(jié)點之后還需要修復(fù)下級節(jié)點的記錄,這就需要遞歸所有下級節(jié)點。

    刪除id=5節(jié)點的所有記錄

    DELETE FROM CategoryTree WHERE descendant=5

    復(fù)制

    然后配合上面一節(jié)的插入操作實現(xiàn)移動。

    總結(jié)

    ClosureTable是一種比較完美的解決方案,對于無限分層有很好的適應(yīng)性,比較適用于大型系統(tǒng)。

    總結(jié)

    以上是生活随笔為你收集整理的数据库无限层级分类设计的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。