數據庫學習,樹形結構的數據庫表Schema設計方案

2019獨角獸企業重金招聘Python工程師標準>>> hot3.png

程序設計過程中,我們常常用樹形結構來表征某些數據的關聯關系,如企業上下級部門、欄目結構、商品分類等等,通常而言,這些樹狀結構需要借助于數據庫完成持久化。然而目前的各種基于關系的數據庫,都是以二維表的形式記錄存儲數據信息,因此是不能直接將Tree存入DBMS,設計合適的Schema及其對應的CRUD算法是實現關系型數據庫中存儲樹形結構的關鍵。

?? ?理想中樹形結構應該具備如下特征:數據存儲冗余度小、直觀性強;檢索遍歷過程簡單高效;節點增刪改查CRUD操作高效。無意中在網上搜索到一種很巧妙的設計,原文是英文,看過后感覺有點意思,于是便整理了一下。本文將介紹兩種樹形結構的Schema設計方案:一種是直觀而簡單的設計思路,另一種是基于左右值編碼的改進方案。

一、基本數據

?? ?本文列舉了一個食品族譜的例子進行講解,通過類別、顏色和品種組織食品,樹形結構圖如下:


二、繼承關系驅動的Schema設計

?? ?對樹形結構最直觀的分析莫過于節點之間的繼承關系上,通過顯示地描述某一節點的父節點,從而能夠建立二維的關系表,則這種方案的Tree表結構通常設計為:{Node_id,Parent_id},上述數據可以描述為如下圖所示:


這種方案的優點很明顯:設計和實現自然而然,非常直觀和方便。缺點當然也是非常的突出:由于直接地記錄了節點之間的繼承關系,因此對Tree的任何CRUD操作都將是低效的,這主要歸根于頻繁的“遞歸”操作,遞歸過程不斷地訪問數據庫,每次數據庫IO都會有時間開銷。當然,這種方案并非沒有用武之地,在Tree規模相對較小的情況下,我們可以借助于緩存機制來做優化,將Tree的信息載入內存進行處理,避免直接對數據庫IO操作的性能開銷。

三、基于左右值編碼的Schema設計

?? ?在基于數據庫的一般應用中,查詢的需求總要大于刪除和修改。為了避免對于樹形結構查詢時的“遞歸”過程,基于Tree的前序遍歷設計一種全新的無遞歸查詢、無限分組的左右值編碼方案,來保存該樹的數據。

?? ?第一次看見這種表結構,相信大部分人都不清楚左值(Lft)和右值(Rgt)是如何計算出來的,而且這種表設計似乎并沒有保存父子節點的繼承關系。但當你用手指指著表中的數字從1數到18,你應該會發現點什么吧。對,你手指移動的順序就是對這棵樹進行前序遍歷的順序,如下圖所示。當我們從根節點Food左側開始,標記為1,并沿前序遍歷的方向,依次在遍歷的路徑上標注數字,最后我們回到了根節點Food,并在右邊寫上了18。


依據此設計,我們可以推斷出所有左值大于2,并且右值小于11的節點都是Fruit的后續節點,整棵樹的結構通過左值和右值存儲了下來。然而,這還不夠,我們的目的是能夠對樹進行CRUD操作,即需要構造出與之配套的相關算法。

?四、樹形結構CRUD算法

(1)獲取某節點的子孫節點

?? ?只需要一條SQL語句,即可返回該節點子孫節點的前序遍歷列表,以Fruit為例:SELECT* FROM Tree WHERE Lft BETWEEN 2 AND 11 ORDER BY Lft ASC。查詢結果如下所示:


那么某個節點到底有多少的子孫節點呢?通過該節點的左、右值我們可以將其子孫節點圈進來,則子孫總數 = (右值 – 左值– 1) / 2,以Fruit為例,其子孫總數為:(11 –2 – 1) / 2 = 4。同時,為了更為直觀地展現樹形結構,我們需要知道節點在樹中所處的層次,通過左、右值的SQL查詢即可實現,以Fruit為例:SELECTCOUNT(*) FROM Tree WHERE Lft <= 2 AND Rgt >=11。為了方便描述,我們可以為Tree建立一個視圖,添加一個層次數列,該列數值可以寫一個自定義函數來計算,函數定義如下:

CREATE FUNCTION dbo.CountLayer
(@node_id int
)
RETURNS int
AS
begindeclare @result intset @result = 0declare @lft intdeclare @rgt intif exists(select Node_id from Tree where Node_id = @node_id)beginselect @lft = Lft, @rgt = Rgt from Tree where node_id = @node_idselect @result = count(*) from Tree where Lft <= @lft and Rgt >= @rgtendreturn @result
end
GO
基于層次計算函數,我們創建一個視圖,添加了新的記錄節點層次的數列:

CREATE VIEW dbo.TreeView
AS
SELECT Node_id, Name, Lft, Rgt, dbo.CountLayer(Node_id) AS Layer FROM dbo.Tree ORDER BY Lft
GO
創建存儲過程,用于計算給定節點的所有子孫節點及相應的層次:

CREATE PROCEDURE [dbo].[GetChildrenNodeList]
(@node_id int
)
AS
declare @lft int
declare @rgt int
if exists(select Node_id from Tree where node_id = @node_id)beginselect @lft = Lft, @rgt = Rgt from Tree where Node_id = @node_idselect * from TreeView where Lft between @lft and @rgt order by Lft ASCend
GO
現在,我們使用上面的存儲過程來計算節點Fruit所有子孫節點及對應層次,查詢結果如下:

從上面的實現中,我們可以看出采用左右值編碼的設計方案,在進行樹的查詢遍歷時,只需要進行2次數據庫查詢,消除了遞歸,再加上查詢條件都是數字的比較,查詢的效率是極高的,隨著樹規模的不斷擴大,基于左右值編碼的設計方案將比傳統的遞歸方案查詢效率提高更多。當然,前面我們只給出了一個簡單的獲取節點子孫的算法,真正地使用這棵樹我們需要實現插入、刪除同層平移節點等功能。

?(2)獲取某節點的族譜路徑

?? ?假定我們要獲得某節點的族譜路徑,則根據左、右值分析只需要一條SQL語句即可完成,以Fruit為例:SELECT* FROM Tree WHERE Lft < 2 AND Rgt > 11 ORDER BY Lft ASC?,相對完整的存儲過程:

CREATE PROCEDURE [dbo].[GetParentNodePath]
(@node_id int
)
AS
declare @lft int
declare @rgt int
if exists(select Node_id from Tree where Node_id = @node_id)beginselect @lft = Lft, @rgt = Rgt from Tree where Node_id = @node_idselect * from TreeView where Lft < @lft and Rgt > @rgt order by Lft ASCend
GO

(3)為某節點添加子孫節點

?? ?假定我們要在節點“Red”下添加一個新的子節點“Apple”,該樹將變成如下圖所示,其中紅色節點為新增節點。

仔細觀察圖中節點左右值變化,相信大家都應該能夠推斷出如何寫SQL腳本了吧。我們可以給出相對完整的插入子節點的存儲過程:

CREATE PROCEDURE [dbo].[AddSubNode]
(@node_id int,@node_name varchar(50)
)
AS
declare @rgt int
if exists(select Node_id from Tree where Node_id = @node_id)beginSET XACT_ABORT ONBEGIN TRANSCTIONselect @rgt = Rgt from Tree where Node_id = @node_idupdate Tree set Rgt = Rgt + 2 where Rgt >= @rgtupdate Tree set Lft = Lft + 2 where Lft >= @rgtinsert into Tree(Name, Lft, Rgt) values(@node_name, @rgt, @rgt + 1)COMMIT TRANSACTIONSET XACT_ABORT OFFend
GO

(4)刪除某節點

?? ?如果我們想要刪除某個節點,會同時刪除該節點的所有子孫節點,而這些被刪除的節點的個數為:(被刪除節點的右值 – 被刪除節點的左值+ 1) / 2,而剩下的節點左、右值在大于被刪除節點左、右值的情況下會進行調整。來看看樹會發生什么變化,以Beef為例,刪除效果如下圖所示。


?則我們可以構造出相應的存儲過程:

CREATE PROCEDURE [dbo].[DelNode]
(@node_id int
)
AS
declare @lft int
declare @rgt int
if exists(select Node_id from Tree where Node_id = @node_id)beginSET XACT_ABORT ONBEGIN TRANSCTIONselect @lft = Lft, @rgt = Rgt from Tree where Node_id = @node_iddelete from Tree where Lft >= @lft and Rgt <= @rgtupdate Tree set Lft = Lft – (@rgt - @lft + 1) where Lft > @lftupdate Tree set Rgt = Rgt – (@rgt - @lft + 1) where Rgt > @rgtCOMMIT TRANSACTIONSET XACT_ABORT OFFend
GO

五、總結

?? ?我們可以對這種通過左右值編碼實現無限分組的樹形結構Schema設計方案做一個總結:

?? ?(1)優點:在消除了遞歸操作的前提下實現了無限分組,而且查詢條件是基于整形數字的比較,效率很高。

?? ?(2)缺點:節點的添加、刪除及修改代價較大,將會涉及到表中多方面數據的改動。

?? ?當然,本文只給出了幾種比較常見的CRUD算法的實現,我們同樣可以自己添加諸如同層節點平移、節點下移、節點上移等操作。有興趣的朋友可以自己動手編碼實現一下,這里不在列舉了。值得注意的是,實現這些算法可能會比較麻煩,會涉及到很多條update語句的順序執行,如果順序調度考慮不周詳,出現Bug的話將會對整個樹形結構表產生驚人的破壞。因此,在對樹形結構進行大規模修改的時候,可以采用臨時表做中介,以降低代碼的復雜度,同時,強烈推薦在做修改之前對表進行完整備份,以備不時之需。在以查詢為主的絕大多數基于數據庫的應用系統中,該方案相比傳統的由父子繼承關系構建的數據庫Schema更為適用。

轉載于:https://my.oschina.net/u/3647620/blog/1552319

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/454816.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/454816.shtml
英文地址,請注明出處:http://en.pswp.cn/news/454816.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

[轉載] 手工制作Win7 OEM版

只要往微軟MSDN原版ISO的sources目錄加個“$OEM$”文件夾&#xff0c;再刪除sources下面的ei.cfg文件就可以了。 來源&#xff1a;http://zxkh19501.blog.163.com/blog/static/1237851792010629113427594/轉載于:https://www.cnblogs.com/784040932/p/win7oem.html

mysql dbo_mysql-雙重分組

我的表有兩列&#xff1a;名稱和等級.看起來像這樣&#xff1a;NAME | GRADEAdam | 1Adam | 2Adam | 2Adam | 3Frank | 2Frank | 1現在,我想創建如下所示的視圖&#xff1a;NAME | GRADE 1 | GRADE 2 | GRADE 3Adam | 1 | 2 | 1Frank | 1 | 1 | 0我寫了這個&#xff1a;SELECT …

課堂作業整理三 (集合:list接口)

集合中 list的方法列表&#xff08;Arraylist和Linkedlist&#xff09; 方法名功能說明ArrayList()構造方法&#xff0c;用于創建一個空的數組列表add&#xff08;E&#xff0c;e&#xff09;將指定的元素添加到此列表的尾部get&#xff08;int index&#xff09;返回此列表中指…

LINUX系統移植(史上最全最細,強烈推薦)

Linux系統移植 目 錄 第一部分 前言...................................................................................................................................8 1 硬件環境................................................................................…

The serializable class XXX does not declare a static final serialVersionUID field of type long的警告...

原文: http://blog.csdn.net/ultrakang/article/details/41820543轉載于:https://www.cnblogs.com/Baronboy/p/7465508.html

Ubuntu17.04 之 systemd 設置開機啟動

Ubuntu從16.04開始不再使用 initd 管理系統&#xff0c;改用 systemd。 和 Centos 一樣&#xff0c;升級到 Centos7 之后使用 systemd 替代 init.d 為了像以前一樣&#xff0c;在/etc/rc.local中設置開機啟動程序&#xff0c;需要以下幾步&#xff1a; 1、鏈接文件 systemd 默…

replaceselection();java'_Java JTextComponent.replaceSelection方法代碼示例

import javax.swing.text.JTextComponent; //導入方法依賴的package包/類public void actionPerformed(final ActionEvent evt, final JTextComponent target) {if (target ! null) {if (!target.isEditable() || !target.isEnabled()) {target.getToolkit().beep();return;}Ed…

Systemd 入門教程之命令篇

Systemd 是 Linux 系統工具&#xff0c;用來啟動守護進程&#xff0c;已成為大多數發行版的標準配置。 本文介紹它的基本用法&#xff0c;分為上下兩篇。今天介紹它的主要命令&#xff0c;下一篇介紹如何用于實戰。 一、由來 歷史上&#xff0c;Linux 的啟動一直采用init進程。…

GCC生成的匯編代碼

假設我們寫了一個C代碼文件 code.c包含下面代碼&#xff1a; int accum 0; int sum(int x, int y) { int t x y; accum t; return t; } 這是用echo命令輸入源碼的效果&#xff0c;簡單的就是最好的&#xff1a;&#xff09;一、查看GCC生成的匯編代碼在命令行…

php __FILE__,__CLASS__等魔術變量,及實例

php __FILE__,__CLASS__等魔術變量,及實例 今天看到一個魔術變量&#xff0c;是以前沒見過的&#xff0c;__DIR__&#xff0c;我查了查&#xff0c;發現原來是php5.3新增的&#xff0c;順便舉幾個例子&#xff0c;解釋一下php的魔術變量 1&#xff0c;__FILE__ 文件的完整路徑和…

java虛方法和抽象方法_虛方法和抽象方法--基礎回顧

抽象方法是只有定義、沒有實際方法體的函數&#xff0c;它只能在抽象函數中出現&#xff0c;并且在子類中必須重寫&#xff1b;虛方法則有自己的函數體&#xff0c;已經提供了函數實現&#xff0c;但是允許在子類中重寫或覆蓋。重寫的子類虛函數就是被覆蓋了。抽象方法使用abst…

jQuery高度及位置操作

1. 獲取滑輪位置&#xff0c;scrolltop:上下滾動的意思。 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>Title</title> </head> <body><div style"height:100px;width:10…

you have mixed tabs and spaces fix this

http://blog.csdn.net/tonyyan19781/article/details/60882443Vs2013 IDE下&#xff0c;編輯C的工程源碼&#xff0c;在打開文件的時候&#xff0c;會出現 “ you have mixed tabs and spaces fix this ”&#xff0c; 然后給出三個選項 Tabify、Untabify、Dont show again。尤…

Systemd 入門教程之實戰篇

一、開機啟動 對于那些支持 Systemd 的軟件&#xff0c;安裝的時候&#xff0c;會自動在/usr/lib/systemd/system目錄添加一個配置文件。 如果你想讓該軟件開機啟動&#xff0c;就執行下面的命令&#xff08;以httpd.service為例&#xff09;。$ sudo systemctl enable httpd上…

從VC++到GCC移植:談兩者的語法差異

從VC到GCC移植&#xff1a;談兩者的語法差異 許式偉 &#xff08;版權聲明&#xff09; 2007-1-28 類型引用 template <classT>classFoo { typedef T::SomeType SomeType; };這段代碼在VC中一點問題也沒有&#xff0c;但是GCC并不允許&#xff0c;因為它不知道T::S…

牛客網Java刷題知識點之關鍵字static、static成員變量、static成員方法、static代碼塊和static內部類...

不多說&#xff0c;直接上干貨&#xff01; 牛客網Java刷題知識點之關鍵字static static代表著什么 在Java中并不存在全局變量的概念&#xff0c;但是我們可以通過static來實現一個“偽全局”的概念&#xff0c;在Java中static表示“全局”或者“靜態”的意思&#xff0c;用來修…

30天自制操作系統(二)匯編語言學習與Makefile入門

1 介紹文本編輯器這部分可直接略過2 繼續開發helloos.nas中核心程序之前的內容和啟動區以外的內容先不講了&#xff0c;因為還涉及到一些軟盤方面的知識。然后來講的是helloos.nas這個文件; hello-os ; TAB4ORG 0x7c00 ; 指明程序的裝載地址; 以下這部分記錄…

java房產源碼_基于jsp的房屋交易管理系統-JavaEE實現房屋交易管理系統 - java項目源碼...

基于jspservletpojomysql實現一個javaee/javaweb的房屋交易管理系統, 該項目可用各類java課程設計大作業中, 房屋交易管理系統的系統架構分為前后臺兩部分, 最終實現在線上進行房屋交易管理系統各項功能,實現了諸如用戶管理, 登錄注冊, 權限管理等功能, 并實現對各類房屋交易管…

Docker 精通之入門

Docker 精通系列 Docker 精通之入門Docker 精通之微服務Docker 精通之常用命令Docker 精通之 Dockerfile 2013年發布至今&#xff0c; Docker 一直廣受矚目&#xff0c;被認為可能會改變軟件行業。 但是&#xff0c;許多人并不清楚 Docker 到底是什么&#xff0c;要解決什么問…

bzoj3156 防御準備 - 斜率優化

Input 第一行為一個整數N表示戰線的總長度。 第二行N個整數&#xff0c;第i個整數表示在位置i放置守衛塔的花費Ai。 Output 共一個整數&#xff0c;表示最小的戰線花費值。 Sample Input 102 3 1 5 4 5 6 3 1 2 Sample Output 18 HINT 1<N<10^6,1<Ai<10^9 這題還是…