樹型結構的四種建模方法


對於組織架構中的員工層次關系我們應該怎么建模呢?

 

如下圖所示:

 

此類結構通常有兩個主要特點:

1、一個孩子有且只有一個父親

2、樹的深度不確定 

為了解決這種結構,我們一般會建一張下面的表:

方案一(Adjacency List)

CREATE TABLE Employees(
employee_id int,

employee_name varchar2(100),

parent_id int

);

 

每個員工在Employees表中會有一條記錄,並通過parent_id來記錄其直屬領導的employee_id,這樣做很簡單明了,但是卻存在一些弊端。

考慮如下問題:

1、如何得到某個員工的直屬領導?

2、如何得到某個領導的直屬下屬?

3、如何得到某個領導全部下屬(下屬的下屬)?

 

問題1、2都很簡單,一次自連接就解決了:

1、

[sql]  view plain  copy
 print?
  1. select par.employee_id,par.employee_name   
  2. from employees par,employees self  
  3. where self.parent_id=par.employee_id  
  4. and self.employee_id=3201  

2、 

[sql]  view plain  copy
 print?
  1. select child.employee_id,child.employee_name   
  2. from employees child,employees self   
  3. where child.parent_id=self.employee_id   
  4. and self.employee_id=1010  

 

但問題3呢?

兩種人會有兩種做法,一種覺得可以在程序里做,把問題2的SQL循環執行最終把結果拼起來就OK了;

一種是覺得我可以使用多次自連接,比如我知道這下領導最多有兩級下屬,我就可以這樣做:

select child.employee_id,child.employee_name,child1.employee_id,child1.employee_name

from employees self  inner join employees child on child.parent_id=self.employee_id

left join employees child1 on child1.parent_id=child.employee_id and

where self.employee_id=1010

 

上面兩種方法看似都可以解決問題,但是別忘了此類樹結構的一個很重要的特點,那就是深度的不確定性(就算確定,如果層次很深,20級),

性能及可擴展性將是一個很大的問題。

 

那怎么辦呢?一時間好像看起來別無他法啊。

好消息是使用Oracle 10g及以上或者SQL Server 2005及以上的朋友可以直接使用數據庫特有的SQL特性來解決這個問題了。

例如在Oracle中可以使用層次查詢

[sql]  view plain  copy
 print?
  1. select EMPLOYEE_ID, employee_name  
  2.   from employees   
  3.  start with employee_id = 1  
  4.  connect by  prior employee_id = parent_id  

 

那使用MySQL或者其不支持層次查詢的數據庫怎么辦呢?難道只能用前面兩種笨方法?

答案是否定的,你需要重新設計你的表模型。

How to design?

方案二(Path Enumeration)

CREATE TABLE Employees_Path(
employee_id int,

employee_name varchar2(100),

path varchar2(1000)

);

此種方案借助了unix文件目錄的思想,如下圖所示:

 

我們需要做的就是正確的維護這個PATH值,現在如果我們要查詢任意領導(比如Michele)的所有下屬就只需要這樣即可:

[sql]  view plain  copy
 print?
  1. select * from Employees_Path where path like '/1/_%'  


同樣的,如果我們需要查詢任意員工(比如Chris)的所有領導也只需要這樣即可:

[sql]  view plain  copy
 print?
  1. select * from Employees_Path where '/1/2/5/' like path||'%' and path<>'/1/2/5/'  

缺點:

1、PATH值由程序來維護,無法在數據庫一級確保數據的有效性

2、當樹的層級太深有可能會超過PATH字段的長度,所以其能支持的最大深度並非無限的。

 

方案三(Nested Sets)

CREATE TABLE EMPLOYEES_NESTEDSETS(
EMPLOYEE_ID INT,
EMPLOYEE_NAME VARCHAR2(100),
NSLEFT INT,
NSRIGHT INT
);

該方案采用深度優先遍歷給樹中的每個節點分配兩個值,分別存在NSLEFT和NSRIGHT中。如下圖所示

 

每個節點左邊的的值存放在NSLEFT中,右邊的值存放在NSRIGHT中;節點左邊的值比該節點的所有子孫節點值都要小,節點右邊的值比該節點的所有子孫節點值都要大。

例如Hell Mayes左邊的值為2,其比Hell Mayes的所有子孫節點的值都要小(3,4,5,10,6,7,8,9)

Hell Mayes右邊的值為11,其比Hell Mayes的所有子孫節點的值都要大(3,4,5,10,6,7,8,9)

 

有了這個規則之后,如果想要查找某個節點的子孫或都祖先就非常容易了。

回到我們前面的題目中來,假設我要查找Helen Mayes的所有下屬員工,我們可以這樣:

[sql]  view plain  copy
 print?
  1. select *  
  2.   from EMPLOYEES_NESTEDSETS par,   
  3.   EMPLOYEES_NESTEDSETS child  
  4.  where child.nsleft > par.nsleft  
  5.    and child.nsleft < par.nsright  
  6.    and par.EMPLOYEE_NAME = 'Helen Mayes'  


那如果我們要查找Helen Mayes的所有領導呢?

[sql]  view plain  copy
 print?
  1. select *  
  2.   from EMPLOYEES_NESTEDSETS par,   
  3.   EMPLOYEES_NESTEDSETS child  
  4.  where child.nsleft > par.nsleft  
  5.    and child.nsleft < par.nsright  
  6.    and child.EMPLOYEE_NAME = 'Angela Richards'  


 

Nested Sets這種方案還有一個優點就是,當你刪除了一個非葉子節點的時候,該節點的所有子孫節點會自動成為該節點父節點的子孫,並同樣滿足前面所說的條件。

缺點:

在Adjacency List方案中很好回答的問題,在Nested Sets中卻變得困難起來

比如我想要查找任意領導(比如Helen Mayes)的直屬下屬,在Nested Sets中你需要這樣做

[sql]  view plain  copy
 print?
  1. select *  
  2.   from EMPLOYEES_NESTEDSETS par  
  3.  inner join EMPLOYEES_NESTEDSETS child  
  4.     on child.nsleft > par.nsleft  
  5.    and child.nsleft < par.nsright  
  6.   left join EMPLOYEES_NESTEDSETS tmp  
  7.     on child.nsleft > tmp.nsleft  
  8.    and child.nsleft < tmp.nsright  
  9.    and tmp.nsleft > par.nsleft  
  10.    and tmp.nsleft < par.nsright  
  11.  where par.EMPLOYEE_NAME = 'Helen Mayes'  
  12.    and tmp.employee_id is null  


怎么樣,夠復雜吧? 其邏輯就是 首先找到Helen Mayes的所有下屬,然后在去查找這些下屬沒有屬於Helen Mayes下屬的上級.........WTF...........

 

另外,移動和新增加節點也比較復雜

比如我們要在Helen Mayes和Chris Jones之間插入一名員工Scott,如下圖所示

 

 

從上圖基本上就可以看出來我們要更改的地方了......需要重新生成比新插入節點的nsleft值大的所有節點的nsleft和nsright值

非常復雜......

方案四(Closure Table)

CREATE TABLE Employees(
employee_id int,

employee_name varchar2(100)

);

 

CREATE TABLE TreePaths (
ancestor_key int,

member_key  int,

Distance int,

is_leaf int

);

 

Closure Table將樹中每個節點與其子孫節點的關系都存儲了下來,如下圖所示:

注意:每個節點都有一條到其本身的記錄,如上表的第一條、第四條、第六條記錄

Distance是祖先節點到其本身之間的深度,ancestor_key是祖先節點的key,member_key是該節點的成員節點的key,IS_LEAF用於標識該節點是否為葉子節點

有了這張關系表之后,下面讓我們用具體例子來感受其帶來的好處

查找某個領導(Helen Mayes)的所有下屬員工信息

select * from Employees a, TreePaths b where a.employee_id = b.ancestor_key and a.employee_name = 'Helen Mayes' and b.distance<>0 

查找某個領導(Helen Mayes)的所有直屬員工信息

select * from Employees a, TreePaths b where a.employee_id = b.ancestor_key and a.employee_name = 'Helen Mayes' and b.distance=1

 查找某個員工的所有領導

select * from Employees a, TreePaths b where a.employee_id = b.member_key and a.employee_name = 'Helen Mayes' and b.distance<>0

 

結語

四種方案中,通常會結合方案一和方案四來使用(BIEE 11g的父子維就使用的該方案);方案三只適合插入和更新極少,查詢占主要的應用;方案二不怎么推薦。

轉自: http://blog.csdn.net/biplusplus/article/details/7433625


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM