mysql 儲存層級關係 在資料庫中儲存層級結構

2021-10-17 18:12:14 字數 3250 閱讀 8816

位於分類 技巧集錦

1)首先是鄰接表模型。

鄰接表相當簡單。只需要寫乙個遞迴函式來遍歷這個樹。我們的食品商店的例子用鄰接表模型儲存時看起來就像是這樣:

通過鄰接表模型儲存法中,我們可以看到pear,它的父節點是green,而green的父節點又是fruit,以此類推。而根節點是沒有父節點的。這裡為了方便**,parent欄位使用的字串,實際應用中只要使用每個節點的id即可。

現在已經在資料庫中插入完畢資料,接下來開始先顯示這棵樹。

列印這棵樹:

這裡我們只需要寫乙個簡單的遞迴函式就可以實現。列印某節點時,如果該節點有子節點就列印其子節點。源**如下:

要列印整棵樹,我們只要執行**:

這個函式列印出以下結果:food

fruit

green

pear

redcherry

yellow

banana

meat

beef

pork

求節點的路徑

有時候我們需要知道某個節點所在的路徑。舉例來說,「cherry」所在的路徑為food > fruit > red > cherry。在這裡,我們可以從cherry開始查起,然後遞迴查詢查詢節點前的節點,直到某節點的父節點id為0。源**如下:

我們用以下**來列印結果:

得到以下結果:[0] ==> food

[1] ==> fruit

[2] ==> red

[3] ==> cherry

缺點我們可以看到,用鄰接表模型確實是個不錯的方法。它簡單易懂,而且實現的**寫起來也很容易。那麼,缺點是什麼呢?那就是,鄰接表模型執行起來效率低下。我們對於每個結果,期望只需要一次查詢;可是當使用鄰接表模型時巢狀的遞迴使用了多次查詢,當樹很大的時候,這種慢就會表現得尤為明顯。另外,對於一門程式語言來說,除了lisp這種,大多數不是為了遞迴而設計。當乙個節點深度為4時,它得同時生成4個函式例項,它們都需要花費時間、占用一定的記憶體空間。所以,鄰接表模型效率的低下可想而知。

就像在程式世界經常遇到的一樣。上帝是公平的,當在執行時效率低下,意味著可以增加預處理的程度。那麼就讓我們來看另外一種儲存樹形結構的方法。如之前所講,我們希望能夠減少查詢的數量,最好是只做到查詢一次資料庫。

先來講解一下原理。現在我們把樹「橫」著放。如下圖所示,我們首先從根節點(「food」)開始,先在它左側標記「1」,然後我們到「fruit」,左側標記「2」,接著按照前序遍歷的順序遍歷完樹,依次在每個節點的左右側標記數字。

相信你也在圖中發現一些規律,沒錯。比如,「red」節點左邊的數為3、右邊的數為6,它是food(1-18)的後代。同樣的,我們可以注意到,左數大於2、右數小於11的節點都是「fruit」的子孫。現在,所有的節點將以左數-右數的方式儲存,這種通過遍歷乙個樹、然後給每乙個節點標註左數、右數的方式稱為修改過的前序遍歷演算法。

2)修改過的前序遍歷演算法

在看完了介紹之後,我們要來討論具體的實現。在這之前,先來看一下,資料庫中表儲存這些數的情況。

在這種儲存方式中,我們實際上是不需要parent這個欄位的。

列印樹:

如之前的介紹。如果要想列印樹,你只需要知道你要檢索的節點。比如,想要列印「fruit」的子樹,可以查詢左數大於2而小於11的節點。sql語句就像這樣:

返回結果如下:

有時候,如果進行過增、刪的操作,表中的資料可能就不是正確的順序。沒問題,只要使用「order by」語句就可以了,就像這樣:

現在唯一的問題是縮排問題。

正如我們面對樹的問題常常會想到的方案——棧。這裡,我們可以維護乙個只儲存右數的棧。當當前節點的右數值大於棧頂元素的值(說明棧頂元素的子樹都以遍歷完畢),這個時候彈出棧頂值。再迴圈檢查棧頂值,直到棧頂值小於當前查詢節點的右數值。這個時候只要檢查棧中元素,有多少個元素說明當前查詢節點有多少個祖先節點(設為n)。只需要列印n個空格即可。**如下:

執行**,列印結果和之前鄰接表模型列印的結果一樣。但是新方法更快,原因就是:沒有遞迴,且一共只使用兩次查詢。

求節點的路徑:

在修改過的前序遍歷演算法的實現中,我們同樣需要求節點的路徑。不過這不是很困難,對於某節點,我們只需求出左數值小於其左數值、右數大於其右數的所有節點。比如說「cherry」這個節點(4-5),我們可以這麼寫sql查詢:

這裡同樣別忘了新增「order by」語句。執行以後返回結果:

求有多少子孫:

已知某節點的左數和右數,它的子孫的求法也就相當簡單了,用如下方法:

descendants = (right - left - 1) / 2

自動生成表:

這兒的自動生成表指的是:如何把乙個表從鄰接表模型轉換成修改過的前序遍歷模型。我們在開始的臨界表上增加"lft「和」rgt「字段。執行以下**,完成轉換:

開始執行只要執行以下**:

我們所寫的執行函式是乙個遞迴函式。對於某一節點,如果其沒有子孫節點,那麼他的右數值等於左數值+1;如果有那麼返回其子樹右數值+1。這個函式稍微有點複雜,不過梳理通了以後就不難理解。

這個函式將會從根節點開始遍歷整個樹。執行了可以發現和我們之前手動所建的表一樣。這裡有個快速檢查的方法:那就是檢查根節點的右數值,如果它等於節點總數的2倍,那就是正確的。

增加節點:

增加節點有兩種方法:1)保留parentid欄位,當增加節點後,執行一遍「rebuildtree」方法。這麼做看起來很簡單,不過你應該知道,這麼做效率低下,尤其是大樹時。那麼第二種方法呢?2)首先我們得為新增的節點騰出空間。比如,我們想新增「strawberry「到」red「節點下,那麼「red」節點的右數就得從6到8,而「yellow」就得從7-10變成9-12,以此類推。更新red節點就意味著大於5的左數和右數都要增加2。

我們先執行以下sql語句:

現在我們可以新增「strawberry」到「red」下,其左數為6、右數為7。

再次執行「displaytree」方法,會發現「strawberry」已被新增其中。刪除節點有著差不多的步驟,這裡就略去不提了。各位感興趣的話可以自己實現。

缺點:首先,修改過的前序遍歷演算法似乎更難理解。但是它有著鄰接表模型無法比擬的速度優勢,雖然,在增或著刪資料的時候步驟多了些,但是,查詢的時候只需要一條sql語句。不過,這裡我要提醒,當使用前序遍歷演算法儲存樹的時候,要注意臨界區問題,就是在增或者刪的時候,不要出現其他的資料庫操作。

關於在資料庫中儲存層級資料的內容就講到這裡。如果你使用的python語言的django框架,應該覺得慶幸。因為已經有開源外掛程式幫你實現了。專案名字叫mptt,主頁在這裡。以後,我會對mptt的用法以及原始碼實現作詳細說明。在此之前,如果能力夠,參考官方文件就可以了。

備註一:

這個資料夾是唯讀的,修改許可權用chmod命令。

分享到

mysql資料庫表關係 資料庫 表關係

上節回顧 1.建表語法 注意點 2.資料型別 今日內容 1.表之間的關係 多對一,多對多,一對一 2.複製表 分表 為什麼要分表?乙個表中 要儲存個人資訊又要儲存部門資訊 會導致大量的資料冗餘 所有資料存放在同乙個表中 將導致以下幾個問題 1.浪費空間 不致命 2.結構混亂 3.修改資料時 如果有一...

mysql關聯式資料庫 關聯式資料庫概述

為什麼需要資料庫?因為應用程式需要儲存使用者的資料,比如word需要把使用者文件儲存起來,以便下次繼續編輯或者拷貝到另一台電腦。要儲存使用者的資料,乙個最簡單的方法是把使用者資料寫入檔案。例如,要儲存乙個班級所有學生的資訊,可以向檔案中寫入乙個csv檔案 id,name,gender,score 1...

mysql中性別 在資料庫中儲存性別(性別)

8 個答案 答案 0 得分 164 已經有iso標準 無需發明自己的方案 根據標準,該列應該被稱為 而 最接近 的資料型別將是tinyint,並且具有check約束或查詢表。答案 1 得分 74 我將該欄目稱為 性別 data type bytes taken number range of val...