-2010-05-27(東升)
SQL SERVER 2008 新資料庫類型hierarchyid
SQL SERVER 2008引入了新的hierarchyid資料類型,可以用它來做本地存儲并且在樹層次
結構中管理其位置.隻用這個函數能簡潔地表示層次結構中的位置.該函數提供的一些内置的函
數方法可以操作和周遊層次結構,使得存儲和查詢分層資料更為容易,而不需要像那樣通過
CTE遞歸來獲得.
該類型其實是一個CLR自定義資料類型依次打開:資料庫->系統資料庫->master->可程式設計性
->類型->系統資料類型->CLR資料類型->hierarchyid,可以看到該資料類型.
于hierarchyid有關的一些函數主要有:
GetAncestor :取得某一個級别的祖先
GetDescendant :取得某一個級别的子代
GetLevel :取得級别
GetRoot :取得根
IsDescendantOf :判斷某個節點是否為某個節點的子代
Parse :将字元串轉換為hierarchyid。該字元串的格式通常都是/1/這樣的
Read :Read 從傳入的BinaryReader 讀取SqlHierarchyId 的二進制表示形式,并将
SqlHierarchyId 對象設定為該值。不能使用Transact-SQL 調用Read。請改
為使用CAST 或CONVERT。
GetReparentedValue :可以用來移動節點(或者子樹)
ToString :将hierarchyid轉換為字元串,與parse正好相反
Write : 将SqlHierarchyId 的二進制表示形式寫出到傳入的BinaryWriter 中。無法通
過使用Transact-SQL 來調用Write。請改為使用CAST 或CONVERT。
以下就該新類型做一些示範
*/
USE TESTDB
GO
CREATE TABLE TEST(
[PHASE] hierarchyid NOT NULL,
LVL AS [PHASE].GetLevel(),
USERID INT NOT NULL,
USERNAME VARCHAR(50) NOT NULL
)
--接着插入一個根
INSERT TEST([PHASE],USERID,USERNAME)
VALUES('/',1001,'東升')--'/'被用來表示層次的根,會自動轉換成二進制格式
SELECT * FROM TEST
/* 結果
PHASE LVL USERID USERNAME
------ ------ --------- ----------
0x 0 1001 東升
(1 行受影響)
查詢後應該發現'/'被重新定義成x十六進制值.
使用斜杠字元來表示層次路徑,一個表示的是根,用斜杠分隔的整數值來組成連續的層次.
插入小弟
VALUES('/1/',1002,'洋芋')
VALUES('/2/',1003,'紅太狼')
--以上條資料時同級别的
/*
PHASE LVL USERID USERNAME
------- ------ --------- -----------
0x 0 1001 東升
0x58 1 1002 洋芋
0x68 1 1003 紅太狼
(3 行受影響)
--同樣可以使用GetDescendant方法來填充[PHASE]
DECLARE @PARENTPHASE hierarchyid, @Child1 hierarchyid
SELECT @PARENTPHASE= CONVERT(hierarchyid,'/1/')
VALUES(@PARENTPHASE.GetDescendant(NULL,NULL),1004,'洋芋小弟1')
SELECT @Child1 = CAST('/1/1/' AS hierarchyid)
VALUES(@PARENTPHASE.GetDescendant(@Child1,NULL),1005,'洋芋小弟2')
SELECT [PHASE].ToString() AS [PHASE] ,LVL,USERID,USERNAME FROM TEST
ORDER BY [PHASE]
PHASE LVL USERID USERNAME
---------- ------ --------- ---------
/ 0 1001 東升
/1/ 1 1002 洋芋
/1/1/ 2 1004 洋芋小弟1
/1/2/ 2 1005 洋芋小弟2
/2/ 1 1003 紅太狼
(5 行受影響)
查詢中的中ToString方法可以顯示hierarchyid類型的字元串表示,而不是十六進制值.
該方法下面會經常用到.
下面示範一下上面提到的函數方法(注意方法的調用要注意大小寫):
1 GetAncestor: child.GetAncestor ( n ) 傳回指定的祖先.
用于測試輸出中的每個節點是否将目前節點作為指定級别的祖先。
如果傳遞的數字大于GetLevel(),則傳回NULL。
如果傳遞的是負數,則引發異常
SELECT PHASE.GetAncestor(2).ToString()AS Ancestor
--把對應的二級祖先的層次顯示為字元串
,PHASE.ToString() as CURPHASE
,USERNAME
FROM TEST
WHERE LVL>=2 --去除級别小于的行
Ancestor CURPHASE USERNAME
----------- --------- ----------
/ /1/1/ 洋芋小弟1
/ /1/2/ 洋芋小弟2
(2 行受影響)
注意GetAncestor(0)傳回的是節點本身
2 GetDescendant: parent.GetDescendant ( child1 , child2 ) 傳回子節點
child1:NULL 或目前節點的子節點的hierarchyid。
child2:NULL 或目前節點的子節點的hierarchyid。
如果父級為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,則引發異常。
--插入一行作為最低級别的後代節點,上面插入的時候就運用的這個特點
--不含參數的GetDescendant 方法插入新行,以将新行節點指定為/1/1/
--上面執行過,此處不執行
DECLARE @PARENTPHASE hierarchyid
--插入一行作為較進階别的後代節點
--使用帶有Child1 參數的GetDescendant 方法插入新行
--指定新行的節點将緊跟'/1/1/'節點,成為/1/2/
--在兩個現有節點之間插入一行
--同時使用child1 參數和child2 參數指定新行的節點将成為節點/1/1.1/
DECLARE @PARENTPHASE hierarchyid, @Child1 hierarchyid,@Child2hierarchyid
SELECT @Child2 = CAST('/1/2/' AS hierarchyid)
VALUES(@PARENTPHASE.GetDescendant(@Child1,@Child2),1006,'洋芋小弟')
SELECT [PHASE].ToString() AS [PHASE] ,lvl,userid,username
FROM test ORDER BY [PHASE]
/*
PHASE lvl userid username
------ ------ ---------- ------------
/ 0 1001 東升
/1/ 1 1002 洋芋
/1/1.1/ 2 1006 洋芋小弟3
/1/1/ 2 1004 洋芋小弟1
/1/2/ 2 1005 洋芋小弟2
/2/ 1 1003 紅太狼
(6 行受影響)
節點/1/1.1/高于節點/1/1/低于節點/1/2/,但是屬于同一級别
3 GetLevel (): node.GetLevel () 傳回一個表示節點在樹中的深度的整數。
--将層次結構級别傳回為列,本例的測試表建立的計算列用的就是這個特性.
SELECT PHASE.GetLevel() AS lvl1,LVL FROM TEST
lvl1 LVL
------ ------
0 0
1 1
2 2
*/
--傳回層次結構級别的所有成員
SELECT * FROM TEST WHERE PHASE.GetLevel()=1
PHASE LVL USERID USERNAME
------ ------ ---------- -----------
0x58 1 1002 洋芋
0x68 1 1003 紅太狼
同樣可以用PHASE.GetLevel()=0傳回根節點
4 IsDescendantOf :child.IsDescendantOf(parent)判斷某個節點是否為某個節點的子代
DECLARE @parent hierarchyid
SELECT @parent=PHASE FROM TEST WHERE USERNAME='洋芋'
WHERE PHASE.IsDescendantOf ( @parent )=1
------ ------ ----------- -----------
0x5AC0 2 1004 洋芋小弟1
0x5B40 2 1005 洋芋小弟2
0x5B16 2 1006 洋芋小弟3
(4 行受影響)
父級被視為其本身的後代,是以洋芋的子代中包含洋芋
5 GetRoot: 傳回層次結構樹的根節點,是一個靜态方法。
--獲得根節點
SELECT PHASE.ToString() AS ROOT, *
WHERE PHASE = hierarchyid::GetRoot()
ROOT PHASE LVL USERID USERNAME
------- ------- ----- --------- ----------
/ 0x 0 1001 東升
6 Parse: 将hierarchyid 的規範字元串表示形式轉換為hierarchyid 值。當發
生從字元串類型到hierarchyid 的轉換時,将隐式調用Parse。作用
與ToString 相反。Parse() 是靜态方法。
ToString: 将hierarchyid轉換為字元串,與parse正好相反
DECLARE @String AS nvarchar(100), @hierarchyid AS hierarchyid
SET @String = '/1/1/'
SET @hierarchyid = 0x5AC0
SELECT hierarchyid::Parse(@String) AS hierarchyidvalue,
@hierarchyid.ToString() AS Stringvalue
SELECT CONVERT(hierarchyid,@String)
,CONVERT(nvarchar(100),@hierarchyid)
hierarchyidvalue Stringvalue
---------------- -------------
0x5AC0 /1/1/
可以看到結果二者的作用剛好是相反的.其實測試後他們可以發現CONVERT也
可以實作他們的效果.如果Parse 收到的值不是hierarchyid 的有效字元
串表示形式,則會引發異常,例如尾部有空格
SELECT hierarchyid::Parse('/1/1 ')
----------------------------------------------------------------------------------
消息6522,級别16,狀态2,第1 行
在執行使用者定義例程或聚合"hierarchyid" 期間出現.NET Framework 錯誤:
Microsoft.SqlServer.Types.HierarchyIdException: 24001: SqlHierarchyId.Parse 失敗
,因為輸入字元串'/1/1 ' 不是SqlHierarchyId 節點的有效字元串表示形式。
Microsoft.SqlServer.Types.HierarchyIdException:
在Microsoft.SqlServer.Types.SqlHierarchyId.Parse(SqlString input)
7 GetReparentedValue :節點或者是子樹的移動
1)節點的移動
--查詢一下前後的變化
DECLARE @ActionUser hierarchyid , @OldParent hierarchyid, @NewParenthierarchyid
SELECT @ActionUser = PHASE FROM TEST
WHERE USERNAME = '洋芋小弟3' --/1/1.1/
SELECT @OldParent = PHASE FROM TEST
WHERE USERNAME = '洋芋' -- /1/1/
SELECT @NewParent = PHASE FROM TEST
WHERE USERNAME = '東升' -- /
SELECT PHASE.ToString() AS Current_PHASE_AS_Text,
(@ActionUser. GetReparentedValue(@OldParent, @NewParent) ).ToString() ASProposed_PHASE_AS_Text,
PHASE AS Current_PHASE,
@ActionUser. GetReparentedValue(@OldParent, @NewParent) AS Proposed_PHASE,
LVL,
USERID,
USERNAME
WHERE PHASE = @ActionUser ;
Current_PHASE_AS_Text Proposed_PHASE_AS_Text Current_PHASE Proposed_PHASE LVL USERID USERNAME
---------------------- ----------------------- -------------- --------------- ------- ----------- ----------
/1/1.1/ /1.1/ 0x5B16 0x62C0 2 1006 洋芋小弟3
接着上面的代碼進行更新
UPDATE TEST
SET PHASE = @ActionUser.GetReparentedValue(@OldParent, @NewParent)
WHERE PHASE = @ActionUser
SELECT PHASE.ToString() AS PHASE,
LVL,USERID,USERNAME
WHERE USERNAME = '洋芋小弟3' ; -- /1.1/
------ ------ ---------- ----------
/1.1/ 1 1006 洋芋小弟3
2)子樹的移動
--把洋芋這棵樹移動到紅太郎下面
DECLARE @nold hierarchyid, @nnew hierarchyid
,@oldP nvarchar(256)='洋芋', @newP nvarchar(256)='紅太狼'
SELECT @nold = PHASE FROM TEST WHERE USERNAME = @oldP ;
SELECT @nnew = PHASE FROM TEST WHERE USERNAME = @newP ;
SELECT @nnew = @nnew.GetDescendant(max(PHASE), NULL)
FROM TEST WHERE PHASE.GetAncestor(1)=@nnew ;
UPDATE TEST
SET PHASE = PHASE.GetReparentedValue(@nold, @nnew)
WHERE PHASE.IsDescendantOf(@nold) = 1 ;
SELECT PHASE.ToString() AS PHASE,LVL,USERID,USERNAME FROM TEST
ORDER BY PHASE
/*更新後的結果
PHASE LVL USERID USERNAME
------------ ------ ----------- ----------
/ 0 1001 東升
/1.1/ 1 1006 洋芋小弟3
/2/ 1 1003 紅太狼
/2/1/ 2 1002 洋芋
/2/1/1/ 3 1004 洋芋小弟1
/2/1/2/ 3 1005 洋芋小弟2
8 Read,Write 這2個方法不能使用 Transact-SQL 調用,可以使用 CAST 或 CONVERT代替。
本文轉自溫景良(Jason)部落格園部落格,原文連結:http://www.cnblogs.com/wenjl520/archive/2010/06/01/1748811.html,如需轉載請自行聯系原作者