http://blog.csdn.net/tonyzhou2008/article/details/5100683函數
本文主要講述三個內容:
1.如何建立hierarychyid的表,插入數據及基本遞歸查詢。
2.介紹hierarchyid的10種專有函數。
3.介紹hierarchyid特有的深度優先索引(Depth-First Indexing)和廣度優先索引(Breadth-First Indexing)測試
在上一節中.net
http://blog.csdn.net/tjvictor/archive/2009/07/30/4395677.aspx
咱們已經演示瞭如何在SQL Server中經過主鍵和外鍵來存儲以下圖所示的樹型結構數據
blog
雖然經過主鍵和外鍵的相互搭配能夠知足咱們的查詢、存儲需求,可是這種方式並不易於管理和維護,幸運的是,在SQL Server 2008中提供了一種新的數據類型hierarchyid和相關的操做方法來存儲和查詢這種樹型層次關係數據。遞歸
首先建立數據表:
create database TestDb
go
use TestDb
go
Create table EmployeeTreeTable
(
NodeId hierarchyid PRIMARY KEY,
NodeLevel AS NodeId.GetLevel(),
EmployeeId int UNIQUE NOT NULL,
EmployeeName nvarchar(32) NOT NULL,
)
NodeId是記錄樹型層次的Id,是hierarchyid類型。NodeLevel是個計算列,用於存儲當前樹是深度值,根節點爲0。關於NodeId.GetLevel()方法將在下面章節中詳細介紹。索引
按照上圖所示的層次關係爲表插入數據:
--插入數據
declare @DepthNode hierarchyid;--深度Id
declare @BreadthNode hierarchyid;--廣度Id
--插入根節點
insert into EmployeeTreeTable values(hierarchyid::GetRoot(),1,'項目經理')
--計算深度並插入子節點2
select @DepthNode = NodeId from EmployeeTreeTable where [EmployeeId] = 1;
insert into EmployeeTreeTable values(@DepthNode.GetDescendant(null,null),2,'技術經理');
--計算節點2廣度,在節點2右邊插入節點3
select @BreadthNode = NodeId from EmployeeTreeTable where [EmployeeId] = 2;
insert into EmployeeTreeTable values(@DepthNode.GetDescendant(@BreadthNode,null),3,'產品經理');
--計算節點3廣度,在節點3右邊插入節點4
select @BreadthNode = NodeId from EmployeeTreeTable where [EmployeeId] = 3;
insert into EmployeeTreeTable values(@DepthNode.GetDescendant(@BreadthNode,null),4,'測試經理');
--計算節點2深度並插入子節點5
select @DepthNode = NodeId from EmployeeTreeTable where [EmployeeId] = 2;
insert into EmployeeTreeTable values(@DepthNode.GetDescendant(null,null),5,'技術組長1');
--計算節點5廣度,在節點5右邊插入節點6
select @BreadthNode = NodeId from EmployeeTreeTable where [EmployeeId] = 5;
insert into EmployeeTreeTable values(@DepthNode.GetDescendant(@BreadthNode,null),6,'技術組長2');
--計算節點4深度並插入子節點7
select @DepthNode = NodeId from EmployeeTreeTable where [EmployeeId] = 4;
insert into EmployeeTreeTable values(@DepthNode.GetDescendant(null,null),7,'測試員工1');
--計算節點5深度並插入子節點8
select @DepthNode = NodeId from EmployeeTreeTable where [EmployeeId] = 5;
insert into EmployeeTreeTable values(@DepthNode.GetDescendant(null,null),8,'技術員工1');
--計算節點8廣度,在節點8右邊插入節點9
select @BreadthNode = NodeId from EmployeeTreeTable where [EmployeeId] = 8;
insert into EmployeeTreeTable values(@DepthNode.GetDescendant(@BreadthNode,null),9,'技術員工2');
--計算節點9廣度,在節點9右邊插入節點10
select @BreadthNode = NodeId from EmployeeTreeTable where [EmployeeId] = 9;
insert into EmployeeTreeTable values(@DepthNode.GetDescendant(@BreadthNode,null),10,'技術員工3');
go
select * from EmployeeTreeTable
結果集爲:
NodeId NodeLevel EmployeeId EmployeeName
0x 0 1 項目經理
0x58 1 2 技術經理
0x5AC0 2 5 技術組長1
0x5AD6 3 8 技術員工1
0x5ADA 3 9 技術員工2
0x5ADE 3 10 技術員工3
0x5B40 2 6 技術組長2
0x68 1 3 產品經理
0x78 1 4 測試經理
0x7AC0 2 7 測試員工1字符串
1.查詢技術組長1全部子節點的員工信息
select * from EmployeeTreeTable
where NodeId.IsDescendantOf(0x5AC0)=1--0x5AC0是技術組長1的NodeIdget
2.查詢技術組長1全部父節點的員工信息
with c as
(
select * from EmployeeTreeTable where EmployeeId = 5
union all
select a.* from EmployeeTreeTable as a
join c on a.NodeId = c.NodeId.GetAncestor(1)
)
select * from c博客
上面的例子中,使用了不少hierarchyid專有的函數,可能你們還不熟悉,下面我將具體介紹一下hierarchyid的10個函數,分別爲:
GetRoot,GetLevel,GetAncestor,GetDescendant,IsDescendantOf,ToString,Parse,GetReparentedValue,Read,Write。
1.GetRoot。返回層次結構樹的根節點。注意GetRoot() 是靜態方法。
關於SQL中靜態方法和實例方法的區別請參見:http://blog.csdn.net/tjvictor/archive/2009/07/29/4390673.aspx
SQL:select * from EmployeeTreeTable where NodeId = hierarchyid::GetRoot()
結果集:
NodeId NodeLevel EmployeeId EmployeeName
0x 0 1 項目經理產品
2.返回一個表示節點在樹中的深度的整數。
前面建表時咱們已經使用了這個函數,NodeLevel字段就是用這個函數自動建立的。
SQL:select EmployeeName,NodeId.GetLevel() as TreeLevel from EmployeeTreeTable
結果集爲:
EmployeeName TreeLevel
項目經理 0
技術經理 1
技術組長1 2
技術員工1 3
技術員工2 3
技術員工3 3
技術組長2 2
產品經理 1
測試經理 1
測試員工1 2
3.GetAncestor返回表示本節點爲的第 n 個父節點的 hierarchyid。
SQL:
declare @NodeId hierarchyid
select @NodeId=NodeId from EmployeeTreeTable where EmployeeId = 5
select EmployeeName,NodeLevel from EmployeeTreeTable where NodeId = @NodeId.GetAncestor(0)
select EmployeeName,NodeLevel from EmployeeTreeTable where NodeId = @NodeId.GetAncestor(1)
select EmployeeName,NodeLevel from EmployeeTreeTable where NodeId = @NodeId.GetAncestor(2)
結果集爲:
EmployeeName NodeLevel
技術組長1 2
技術經理 1
項目經理 0
@NodeId.GetAncestor(0) 取本身節點的Id,@NodeId.GetAncestor(1)取父節點的Id,@NodeId.GetAncestor(2)取爺節點的Id,以此類推。
4.GetDescendant返回父級的一個子節點
若是父級爲 NULL,則返回 NULL。
若是父級不爲 NULL,而 child1 和 child2 爲 NULL,則返回父級的子級。
若是父級和 child1 不爲 NULL,而 child2 爲 NULL,則返回一個大於 child1 的父級的子級。
若是父級和 child2 不爲 NULL,而 child1 爲 NULL,則返回一個小於 child2 的父級的子級。
若是父級、child1 和 child2 都不爲 NULL,則返回一個大於 child1 且小於 child2 的父級的子級。
若是 child1 不爲 NULL 且不是父級的子級,則引起異常。
若是 child2 不爲 NULL 且不是父級的子級,則引起異常。
若是 child1 >= child2,則引起異常。
咱們在插入的SQL語句中已經使用過了這個方法,這裏就再也不給出SQL示例,請你們參考前面的插入SQL語句。
5.IsDescendantOf若是子節點爲本節點的後代,則返回 true
SQL:select * from EmployeeTreeTable where NodeId.IsDescendantOf(0x58)=1
結果集爲:
NodeId NodeLevel EmployeeId EmployeeName
0x58 1 2 技術經理
0x5AC0 2 5 技術組長1
0x5AD6 3 8 技術員工1
0x5ADA 3 9 技術員工2
0x5ADE 3 10 技術員工3
0x5B40 2 6 技術組長2
6.ToString返回具備本節點邏輯表示形式的字符串
SQL:select *,NodeId.ToString() as Path from EmployeeTreeTable
結果集爲:
NodeId NodeLevel EmployeeId EmployeeName Path
0x 0 1 項目經理 /
0x58 1 2 技術經理 /1/
0x5AC0 2 5 技術組長1 /1/1/
0x5AD6 3 8 技術員工1 /1/1/1/
0x5ADA 3 9 技術員工2 /1/1/2/
0x5ADE 3 10 技術員工3 /1/1/3/
0x5B40 2 6 技術組長2 /1/2/
0x68 1 3 產品經理 /2/
0x78 1 4 測試經理 /3/
0x7AC0 2 7 測試員工1 /3/1/
7.Parse將hierarchyid 的規範字符串表示形式轉換爲hierarchyid值。即與ToString()函數是相反函數。Parse是靜態函數。
SQL:
declare @Path varchar(32) = '/1/2/5/6/'
select hierarchyid::Parse(@Path)
結果集爲:0x5B6394
8.GetReparentedValue把當前節點從舊路徑更新到新路徑
下面的SQL是把技術員工3,從技術組長1節點更新到技術組長2下面。
SQL:
declare @OldNode hierarchyid=0x5AC0;
declare @NewNode hierarchyid=0x5B40;
update EmployeeTreeTable set NodeId = NodeId.GetReparentedValue(@OldNode,@NewNode)
where EmployeeId = 10
結果集中技術員工3的路徑從/1/1/3/變成了/1/2/3/。
關於GetReparentedValue的用法比較複雜,我在介紹索引後,會更加詳細的說明各類替換狀況。
9.Read和Write
Read和Write是供CLS調用的,不能在T-SQL中直接使用。因此這裏就不具體介紹兩個函數的使用方法了。
hierarchyid有深度優先索引和廣度優先索引
當遞歸查詢父子節點時,會利用到深度優先索引;當平行查詢兄弟節點時,會利用到廣度優先索引。
深度優先索引圖:
廣度優先索引圖:
1.創建深度優先索引:
深度優先索引是hierarchyid默認的索引,只要在hierarchyid列上創建主鍵,那麼就會自動創建hierarchyid索引。
2.創建廣度優先索引
廣度優先索引必須是個惟一索引且包括NodeLevel和NodeId兩列:
CREATE UNIQUE INDEX IX_EmployeeBreadth ON Employee(NodeLevel, NodeId)
須要注意的是採用深度優先、廣度優先仍是結合使用這兩種索引,以及將哪種設爲彙集鍵(若是有),取決於上述兩種查詢類型的相對重要性以及 SELECT 與 DML 操做的相對重要性,本文不表明必定要如此創建hierarchyid索引。
最後咱們討論一下hierarchyid的GetReparentedValue幾種使用方法。
下面咱們先看一個有問題的節點更新:把技術組長1從技術經理更新到產品經理。
SQL:
declare @OldNode hierarchyid=0x58;
declare @NewNode hierarchyid=0x68;
update EmployeeTreeTable set NodeId = NodeId.GetReparentedValue(@OldNode,@NewNode)
where EmployeeId = 5
go
select NodeId.ToString(),* from EmployeeTreeTable
結果集爲:
路徑 NodeId NodeLevel EmployeeId EmployeeName
/ 0x 0 1 項目經理
/1/ 0x58 1 2 技術經理
/1/1/1/ 0x5AD6 3 8 技術員工1
/1/1/2/ 0x5ADA 3 9 技術員工2
/1/1/3/ 0x5ADE 3 10 技術員工3
/1/2/ 0x5B40 2 6 技術組長2
/2/ 0x68 1 3 產品經理
/2/1/ 0x6AC0 2 5 技術組長1
/3/ 0x78 1 4 測試經理
/3/1/ 0x7AC0 2 7 測試員工1
從結果裏面能夠看到技術組長已經變成了/2/1,成功更新到產品經理節點下。可是技術組長1下面的子節點技術員工1,2,3卻沒有相應的更新過來,仍是原來的/1/1/1,2,3,可是原先的技術組長1的/1/1節點已經沒有了,因此出現了所謂的「斷層」現象。
下面提出幾種經常使用更新需求,而且給出相應的SQL實現語句。
1.職位變動。例如技術經理與產品經理職位互換。
針對這種狀況,有兩種方法。一是把技術經理下面的全部節點Id都更新成產品經理節點下。這種狀況變更比較大,不推薦使用。第二種方法是把技術經理的NodeId和產品經理的NodeId互換。下面使用第二種方法:
declare @TechNode hierarchyid=0x58;
declare @ProductNode hierarchyid=0x68;
declare @TempNode hierarchyid=0x59;
update EmployeeTreeTable set NodeId = @TempNode where NodeId = @TechNode;
update EmployeeTreeTable set NodeId = @TechNode where NodeId = @ProductNode;
update EmployeeTreeTable set NodeId = @ProductNode where NodeId = @TempNode;
2.職位升降級。例如技術組長2降級成爲技術員工,被掛在技術組長1節點下:
declare @TechTeamLeadNode1 hierarchyid=0x5AC0;
declare @TechEmployeeNode3 hierarchyid=0x5ADE;
update EmployeeTreeTable set NodeId = @TechTeamLeadNode1.GetDescendant(@TechEmployeeNode3,null)
where EmployeeId = 6
部分結果集爲:
Path NodeId NodeLevel EmployeeId EmployeeName
/1/1/4/ 0x5AE1 3 6 技術組長2
可見,技術組長2從/1/2變成了/1/1/4
總結:
SQL Server 2008提供的hierarchyid類型使咱們可以靈活、方便的操做樹型結構。關於hierarchyid還有不少深刻的知識,不少靈活的用法,本文不可能一一涉及,這裏僅是介紹一些基本用法,拋磚引玉,若是你們在之後的使用中發現什麼問題或是更好的解決方案,請聯繫我。
本文來自CSDN博客:http://blog.csdn.net/tjvictor/archive/2009/07/30/4395681.aspx