六月婷婷综合激情-六月婷婷综合-六月婷婷在线观看-六月婷婷在线-亚洲黄色在线网站-亚洲黄色在线观看网站

明輝手游網中心:是一個免費提供流行視頻軟件教程、在線學習分享的學習平臺!

數據庫表設計-鄰接表、路徑枚舉、嵌套集、閉包表

[摘要]我們在設計數據庫的時候,是否會突破常規,找到最適合自己需求的設計方案,下面來舉個例子: 常用的鄰接表設計,都會添加 一個 parent_id 字段,比如區域表(國、省、市、區):CREATE TABLE Area ([id] [int] NOT NULL,[name] [nvarchar]...
我們在設計數據庫的時候,是否會突破常規,找到最適合自己需求的設計方案,下面來舉個例子:
常用的鄰接表設計,都會添加 一個 parent_id 字段,比如區域表(國、省、市、區):
CREATE TABLE Area ([id] [int]  NOT NULL,[name] [nvarchar]  (50) NULL,[parent_id] [int]  NULL,[type] [int]  NULL );

name:地域的名稱, parent_id 是父ID,省的父ID是國,市的父ID 為省,以此類推。
type 是區域的階級: 1:國,2:省,3:市,4:區
在層級比較確定的情況下,這么設計表格沒有什么問題,調用起來也很方便。
但是使用這種鄰接表設計方式,并不能滿足所有的需求,當我們不確定層級的情況下,假設我有下面一個評論結構:
數據庫表設計-鄰接表、路徑枚舉、嵌套集、閉包表
用鄰接表記錄這個評論的數據(comments 表):
comment_idparent_idauthorcomment
10小明我不大認同這個觀點
21小張我也不認同
32小紅我同意樓上
41小全你為什么不認同呢
54小明我以前遇到過這情況
65小張那也不代表你所說是對的
75小新這個視情況而定吧
大家有沒發現,這么設計表,如果要查詢一個節點的所有后代,是很難實現的,你可以使用關聯查詢來獲取一條評論和他的后代:
SELECT c1.*, c2.* FROM comments c1 LEFT OUTER JOIN comments c2 ON c2.parent_id = c1.comment_id;

然而這個查詢只能獲取兩層的數據。這種樹的特性就是可以任意深地拓展,你需要有相應的方法來獲取它的深度數據。比如,可能需要計算一個評論分支的數量,或者計算一個機械設備的所有的總開銷。
某些情況下,在項目中使用鄰接表正好適用。鄰接表設計的優勢在于能快速的獲取一個給定節點的直接父子節點,它也很容易插入新節點。如果這樣的需求就是你的項目對于分層數據的全部操作,那使用鄰接表就可以很好的工作了。
遇到上述的樹模型,有幾種方案是可以考慮下的:路徑枚舉、嵌套集以及閉包表。這些解決方案通常看上去比鄰接表復雜很多,但它們的確使得某些使用鄰接表比較復雜或很低效的操作變得更簡單。如果你的項目確實需要提供這些操作,那么這些設計會是鄰接表更好的選擇。

一、路徑枚舉

在comments 表中,我們使用類型varchar 的path 字段來替代原來的parent_id 字段。這個path 字段所存儲的內容為當前節點的最頂層祖先到它的自己的序列,就像UNIX的路徑一樣,你甚至可以使用 '/' 作為路徑的分隔符。
comment_idpathauthorcomment
11小明我不大認同這個觀點
21/2小張我也不認同
31/2/3小紅我同意樓上
41/4小全你為什么不認同呢
51/4/5小明我以前遇到過這情況
61/4/5/6小張那也不代表你所說是對的
71/4/5/7小新這個視情況而定吧
你可以通過比較每個節點的路徑來查詢一個節點祖先。比如:要找到評論#7, 路徑是 1/4/5/7一 的祖先,可以這么做:
SELECT * FROM comments AS c WHERE '1/4/5/7' LIKE c.path    '%' ;
這句話查詢語句會匹配到路徑為 1/4/5/%,1/4/% 以及 1/% 的節點,而這些節點就是評論#7的祖先。
同時還可以通過將LIKE 關鍵字兩邊的參數互換,來查詢一個給定節點的所有后代。比如查詢評論#4,路徑path為 ‘1/4’ 的所有后代,可以使用如下語句:
SELECT * FROM comemnts AS c WHERE c.path LIKE '1/4'    '%' ;
這句查詢語句所有能找到的后臺路徑分別是:1/4/5、1/4/5/6、1/4/5/7。
一旦你可以很簡單地獲取一棵子樹或者從子孫節點到祖先節點的路徑,你就可以很簡單地實現更多的查詢,如查詢一顆子樹所有節點上值的總和。
插入一個節點也可以像使用鄰接表一樣地簡單。你所需要做的只是復制一份要插入節點的父親節點路徑,并將這個新節點的ID追加到路徑末尾即可。
路徑枚舉也存在一些缺點,比如數據庫不能確保路徑的格式總是正確或者路徑中的節點確實存在。依賴于應用程序的邏輯代碼來維護路徑的字符串,并且驗證字符串的正確性開銷很大。無論將varchar 的長度設定為多大,依舊存在長度的限制,因而并不能夠支持樹結構無限擴展。
二、 嵌套集
嵌套集解決方案是存儲子孫節點的相關信息,而不是節點的直接祖先。我們使用兩個數字來編碼每個節點,從而表示這一信息,可以將這兩個數字稱為nsleft 和 nsright。
每個節點通過如下的方式確定nsleft 和nsright 的值:nsleft的數值小于該節點所有后代ID,同時nsright 的值大于該節點的所有后代的ID。這些數字和comment_id 的值并沒有任何關聯。
確定這三個值(nsleft,comment_id,nsright)的簡單方法是對樹進行一次深度優先遍歷,在逐層深入的過程中依次遞增地分配nsleft的值,并在返回時依次遞增地分配nsright的值。得到數據如下:
數據庫表設計-鄰接表、路徑枚舉、嵌套集、閉包表
comment_idnsleftnsrightauthorcomment
1114小明我不大認同這個觀點
225小張我也不認同
334小紅我同意樓上
4613小全你為什么不認同呢
5712小明我以前遇到過這情況
689小張那也不代表你所說是對的
71011小新這個視情況而定吧
一旦你為每個節點分配了這些數字,就可以使用它們來找到指定節點的祖先和后代。比如搜索評論#4及其所有后代,可以通過搜索哪些節點的ID在評論 #4 的nsleft 和 nsright 范圍之間,例:
SELECT c2.* FROM comments AS c1 JOIN comments AS c2 ON c2.nsleft BETWEEN c1.nsleftAND c1.nsright WHERE c1.comment_id = 4;
比如搜索評論#6及其所有祖先,可以通過搜索#6的ID在哪些節點的nsleft 和 nsright 范圍之間,例:
SELECT c2.* FROM comments AS c1 JOIN comments AS c2 ON c1.nsleft BETWEEN c2.nsleftAND c2.nsright WHERE c1.comment_id = 6;


使用嵌套集設計的主要優勢是,當你想要刪除一個非葉子節點時,它的后代會自動替代被刪除的節點,成為其直接祖先節點的直接后代。就是說已經自動減少了一層。
然而,某些在鄰接表的設計中表現得很簡單的查詢,比如獲取一個節點的直接父親或者直接后代,在嵌套集設計中會變得比較復雜。在嵌套集中,如果需要查詢一個節點的直接父親,我們會這么做,比如要找到評論#6 的直接父親:
SELECT parent.* FROM comments AS c JOIN comments AS parent ON c.nsleft BETWEEN parent.nsleft AND parent.nsrightLEFT OUTER JOIN comments AS in_between ON c.nsleft BETWEEN in_between.nsleft AND in_between.nsrightAND in_between.nsleft BETWEEN parent.nsleft AND parent.nsright WHERE c.comment_id = 6AND in_between.comment_id IS NULL;
總之有些復雜。
對樹進行操作,比如插入和移動節點,使用嵌套集會比其它設計復雜很多。當插入一個新節點時,你需要重新計算新插入節點的相鄰兄弟節點、祖先節點和它祖先節點的兄弟,來確保他們的左右值都比這個新節點的左值大。同時,如果這個新節點時一個非葉子節點,你還要檢查它的子孫節點。
如果簡單快速查詢是整個程序中最重要的部分,嵌套集是最好的選擇,比操作單獨的節點要方便快捷很多。然而,嵌套集的插入和移動節點是比較復雜的,因為需要重新分配左右值,如果你的應用程序需要頻繁的插入、刪除節點,那么嵌套集可能并不合適。
三、閉包表
閉包表是解決分級存儲的一個簡單而優雅的解決方案,它記錄了樹中所有節點間的關系,而不僅僅只有那些直接的父子節點。
在設計評論系統時,我們額外創建了一個叫 tree_paths 表,它包含兩列,每一列都指向 comments 中的外鍵。
我們不再使用comments 表存儲樹的結構,而是將樹中任何具有(祖先 一 后代)關系的節點對都存儲在treepaths 表里,即使這兩個節點之間不是直接的父子關系;同時,我們還增加一行指向節點自己。
祖先后代祖先后代祖先后代祖先后代
11174677
122247
132355
143356
154457
164566
通過treepaths 表來獲取祖先和后代比使用嵌套集更加的直接。例如要獲取評論#4的后代,只需要在 treepaths 表中搜索祖先是評論 #4的行就行了。同樣獲取后代也是如此。
要插入一個新的葉子節點,比如評論#6的一個子節點,應首先插入一條自己到自己的關系,然后搜索 treepaths 表中后代是評論#6 的節點,增加該節點和新插入節點的“祖先一后代”關系(新節點ID 應該為8):
INSERT INTO treepaths (ancestor, descendant)SELECT t.ancestor, 8FROM treepaths AS tWHERE t.descendant = 6UNION ALL SELECT 8, 8;
要刪除一個葉子節點,比如評論#7, 應刪除所有treepaths 表中后代為評論 #7 的行:
DELETE FROM treepaths WHERE descendant = 7;

要刪除一顆完整的子樹,比如評論#4 和它所有的后代,可刪除所有在 treepaths 表中后代為 #4的行,以及那些以評論#4后代為后代的行。
閉包表的設計比嵌套集更加的直接,兩者都能快捷地查詢給定節點的祖先和后代,但是閉包表能更加簡單地維護分層信息。這兩個設計都比使用鄰接表或者路徑枚舉更方便地查詢給定節點的直接后代和祖先。
然而你可以優化閉包表來使它更方便地查詢直接父親節點或者子節點: 在 treepaths 表中添加一個 path_length 字段。一個節點的自我引用的path_length 為0,到它直接子節點的path_length 為1,再下一層為2,以此類推。這樣查詢起來就方便多了。
總結:你該使用哪種設計:
種設計都各有優劣,如何選擇設計,依賴于應用程序的哪種操作是你最需要性能上的優化。
方案表數量查詢子查詢樹插入刪除引用完整性
鄰接表1簡單困難簡單簡單
枚舉路徑1簡單簡單簡單簡單
嵌套集1困難簡單困難困難
閉包表2簡單簡單簡單簡單
層級數據設計比較
1、鄰接表是最方便的設計,并且很多程序員都了解它
2、如果你使用的數據庫支持WITH 或者 CONNECT BY PRIOR 的遞歸查詢,那能使得鄰接表的查詢更高效。
3、枚舉路徑能夠很直觀地展示出祖先到后代之間的路徑,但同時由于它不能確保引用完整性,使得這個設計非常脆弱。枚舉路徑也使得數據的存儲變得比較冗余。
4、嵌套集是一個聰明的解決方案,但可能過于聰明,它不能確保引用完整性。最好在一個查詢性能要求很高而對其他要求一般的場合來使用它。
5、閉包表是最通用的設計,并且以上的方案也只有它能允許一個節點屬于多棵樹。它要求一張額外的表來存儲關系,使用空間換時間的方案減少操作過程中由冗余的計算所造成的消耗。
這幾種設計方案只是我們日常設計中的一部分,開發中肯定會遇到更多的選擇方案。選擇哪一種方案,是需要切合實際,根據自己項目的需求,結合方案的優劣,選擇最適合的一種。
我遇到一些開發人員,為了敷衍了事,在設計數據庫表時,只考慮能否完成眼下的任務,不太注重以后拓展的問題,不考慮查詢起來是否耗性能。可能前期數據量不多的時候,看不出什么影響,但數據量稍微多一點的話,就已經顯而易見了(例如:可以使用外聯接查詢的,偏偏要使用子查詢)。
我覺得設計數據庫是一個很有趣且充滿挑戰的工作,它有時能體現你的視野有多寬廣,有時它能讓你睡不著覺,總之痛并快樂著。

以上就是數據庫表設計-鄰接表、路徑枚舉、嵌套集、閉包表的詳細內容,更多請關注php中文網其它相關文章!


學習教程快速掌握從入門到精通的SQL知識。




主站蜘蛛池模板: 伊人婷婷色 | 午夜影视在线观看 | 日韩美女一级片 | 永久国产 | 日本最新免费二区三区 | 人人干在线观看 | 青青视频免费在线观看 | 亚洲免费视频在线观看 | 午夜aaaaaaaaa视频在线 | 日日干夜夜操视频 | 日本成片免费高清 | 青青青青娱乐 | 日本欧美一区二区三区视频麻豆 | 天天色天天色天天色 | 色香蕉在线 | 青青青久热国产精品视频 | 最新欧美精品一区二区三区不卡 | 亚洲视频在线观看免费视频 | 中文一区在线观看 | 日本五级床片在线观看 | 日本大片网 | 正在播放一区二区 | 日韩视频在线观看 | 日韩黄色一级毛片 | 日韩精品亚洲精品485页 | 青青青青青青青青草 | 天天看片天天操 | 午夜视频在线观看www中文 | 天天做天天干 | 香港三级理论在线影院 | 日韩亚洲欧美视频 | 五月伊人婷婷 | 日韩影线 | 午夜影视在线视频观看免费 | 屁屁网站在线观看www | 午夜91视频| 色偷偷91综合久久噜噜app | 午夜精品久久久久久久99 | 日本午夜视频在线观看 | 人人看97 | 四虎免费影院在线播放 |