以往我们在关系数据库中建立树状结构的时候,通常使用ID+ParentID来实现两条纪录间的父子关系。但这种方式只能标示其相对位置。解决这类问题在SqlServer2005出现之前通常是采用游标来操作,但熟悉数据库内部机制的人都知道使用游标带来的性能问题和其他问题是比较严重的。
到了SqlServer2005下,可以选择用CTE来做递归查询,这种方式查询比较简练,但由于数据库内部是采用递归查询的方式,其效率依旧不高;为了能够实现既简练又高效的查询,通常的做法是增加冗余字段,比如增加一个"Path"字段,查询时用模糊查询来进行左匹配。对Path建索引后,这种查询的效率还是相当高的,因此这种方式也是一种常规的设计方式;
SQL SERVER 2008引入了新的hierarchyid数据类型,可以用它来做本地存储并且在树层次结构中管理其位置.只用这个函数能简洁地表示层次结构中的位置.该函数提供的一些内置的函数方法可以操作和遍历层次结构,使得存储和查询分层数据更为容易,而不需要像那样通过CTE递归来获得.
Hierarchyid类型其实是一个CLR自定义数据类型依次打开:数据库->系统数据库->master->可编程性->类型->系统数据类型->CLR数据类型->hierarchyid,可以看到该数据类型.
于hierarchyid有关的一些函数主要有:
hierarchyid 数据类型的值表示树层次结构中的位置。hierarchyid 的值具有以下属性:
hierarchyid 数据类型具有以下局限性:
用于对分层数据进行索引的策略有两种:
例如下面的例子是一个职员表,数据有如下关系:
Scott
|
Mark <- > Ravi
| |
Ben<-> Laura Vijay <-> Frank <-> James
Use AdventureWorksLT
Go
--Scheme Creation
Create Schema HumanResources
Go
--Table Creation
CREATE TABLE HumanResources.EmployeeDemo
(
OrgNode HIERARCHYID,
EmployeeID INT,
LoginID VARCHAR(100),
Title VARCHAR(200),
HireDate DATETIME
)
Go
--Index Creation
CREATE UNIQUE CLUSTERED INDEX idxEmployeeDemo
ON HumanResources.EmployeeDemo (OrgNode,EmployeeID)
下面插入一些数据
SERT HumanResources.EmployeeDemo (OrgNode, EmployeeID, LoginID, Title, HireDate)
LUES (hierarchyid::GetRoot(), 1,'adventure-works\scott', 'CEO', '3/11/05') ;
CLARE @Manager hierarchyid
LECT @Manager = hierarchyid::GetRoot() FROM HumanResources.EmployeeDemo;
SERT HumanResources.EmployeeDemo (OrgNode, EmployeeID, LoginID, Title, HireDate)
LUES (@Manager.GetDescendant(NULL,NULL), 2, 'adventure-works\Mark', 'CTO', '4/05/07')
CLARE @Manager hierarchyid
CLARE @FirstChild hierarchyid
LECT @Manager = hierarchyid::GetRoot() FROM HumanResources.EmployeeDemo;
lect @FirstChild = @Manager.GetDescendant(NULL,NULL)
SERT HumanResources.EmployeeDemo (OrgNode, EmployeeID, LoginID, Title, HireDate)
LUES (@Manager.GetDescendant(@FirstChild,NULL), 3, 'adventure-works\ravi', 'Director Marketing', '4/08/07')
Insert the First Descendant of a Child Node
CLARE @Manager hierarchyid
LECT @Manager = CAST('/1/' AS hierarchyid)
SERT HumanResources.EmployeeDemo (OrgNode, EmployeeID, LoginID, Title, HireDate)
LUES (@Manager.GetDescendant(NULL, NULL),45, 'adventure-works\Ben','Application Developer', '6/11/07') ;
Insert the Second Descendant of a Child Node
CLARE @Manager hierarchyid
CLARE @FirstChild hierarchyid
LECT @Manager = CAST('/1/' AS hierarchyid)
LECT @FirstChild = @Manager.GetDescendant(NULL,NULL)
SERT HumanResources.EmployeeDemo (OrgNode, EmployeeID, LoginID, Title, HireDate)
LUES (@Manager.GetDescendant(@FirstChild, NULL),55, 'adventure-works\Laura','Trainee Developer', '6/11/07') ;
Insert the first node who is the Descendant of Director Marketing
CLARE @Manager hierarchyid
CLARE @FirstChild hierarchyid
LECT @Manager = CAST('/2/' AS hierarchyid)
SERT HumanResources.EmployeeDemo (OrgNode, EmployeeID, LoginID, Title, HireDate)
LUES (@Manager.GetDescendant(NULL, NULL),551, 'adventure-works\frank','Trainee Sales Exec.', '12/11/07') ;
Insert the second node who is the Descendant of Director Marketing
CLARE @Manager hierarchyid
CLARE @FirstChild hierarchyid
LECT @Manager = CAST('/2/' AS hierarchyid)
LECT @FirstChild = @Manager.GetDescendant(NULL,NULL)
SERT HumanResources.EmployeeDemo (OrgNode, EmployeeID, LoginID, Title, HireDate)
LUES (@Manager.GetDescendant(@FirstChild, NULL),531, 'adventure-works\vijay','Manager Industrial Sales', '12/09/06') ;
Insert the third node who is the Descendant of Director Marketing
in between 2 existing descendants
CLARE @Manager hierarchyid
CLARE @FirstChild hierarchyid
CLARE @SecondChild hierarchyid
LECT @Manager = CAST('/2/' AS hierarchyid)
LECT @FirstChild = @Manager.GetDescendant(NULL,NULL)
LECT @SecondChild = @Manager.GetDescendant(@FirstChild,NULL)
SERT HumanResources.EmployeeDemo (OrgNode, EmployeeID, LoginID, Title, HireDate)
LUES (@Manager.GetDescendant(@FirstChild, @SecondChild),543, 'adventure-works\james','Manager Consumer Sales', '12/04/06') ;
Hierarchyid字段类型提供了一系列相关查询函数,可以方便的查询父子关系数据。下面我们查询下数据
DECLARE @TID hierarchyid
SELECT @TID=OrgNode FROM HumanResources.EmployeeDemo WHERE title='cto'
SELECT *, OrgNode.GetLevel() as 层次,OrgNode.ToString() as 路径 FROM HumanResources.EmployeeDemo WHERE @TID.IsDescendantOf(OrgNode)=1
SELECT *, OrgNode.GetLevel() as 层次,OrgNode.ToString() as 路径 FROM HumanResources.EmployeeDemo WHERE OrgNode.IsDescendantOf(@TID)=1
下面另外附几个操作的存储过程:
SET QUOTED_IDENTIFIER ON
GO
--Use Serializable Transaction
CREATE PROCEDURE [dbo].[AddEmployee](@ManagerID hierarchyid, @EmpID int,
@LogID varchar(100), @JobTitle as varchar(200), @JoiningDate datetime)
AS
BEGIN
DECLARE @LastChild hierarchyid
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
BEGIN TRANSACTION
SELECT @LastChild = Max(OrgNode) From HumanResources.EmployeeDemo
WHERE OrgNode = @ManagerID
INSERT HumanResources.EmployeeDemo (OrgNode, EmployeeID, LoginID, Title, HireDate)
VALUES(@LastChild, @EmpID,@LogID , @JobTitle, @JoiningDate)
COMMIT
END ;
CREATE PROCEDURE MoveOrg(@oldMgr nvarchar(256), @newMgr nvarchar(256) )
AS
BEGIN
DECLARE @nold HierarchyID
DECLARE @nnew HierarchyID
SELECT @nold = OrgNode FROM HumanResources.EmployeeDemo WHERE LoginID = @oldMgr ;
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
BEGIN TRANSACTION
SELECT @nnew = OrgNode FROM HumanResources.EmployeeDemo WHERE LoginID = @newMgr ;
SELECT @nnew = @nnew.GetDescendant(max(OrgNode), NULL)
FROM HumanResources.EmployeeDemo WHERE OrgNode.GetAncestor(1)=@nnew ;
UPDATE HumanResources.EmployeeDemo
SET OrgNode = OrgNode.GetReparentedValue(@nold, @nnew)
WHERE @nold.IsDescendantOf(OrgNode) = 1
COMMIT TRANSACTION
END
Create Function GetMyMaxChild(@ManagerID as BigInt) Returns HierarchyID
BEGIN
Declare @ManagerNode HierarchyID
Declare @MaxChild HierarchyID
--Get the ManagerNode
Select @ManagerNode = OrgNode from
HumanResources.EmployeeDemo Where EmployeeID = @ManagerID
--Get the Max Child
Select @MaxChild = Max(OrgNode) from HumanResources.EmployeeDemo
Where OrgNode.GetAncestor(1) = @ManagerNode
--Return the Value
RETURN @MaxChild
END
http://msdn.microsoft.com/zh-cn/library/bb677173.aspx
http://www.cnblogs.com/downmoon/archive/2011/05/03/2035259.html