以往我们在关系数据库中建立树状结构的时候,通常使用ID+ParentID来实现两条 纪录间的父子关系。但这种方式只能标示其相对位置。解决这类问题在SqlServer2005出现之前通常是采用游标来操作,但熟悉数据库内部机制的人都 知道使用游标带来的性能问题和其他问题是比较严重的。

到了SqlServer2005下,可以选择用CTE来做递归查询,这种方式查询比 较简练,但由于数据库内部是采用递归查询的方式,其效率依旧不高;为了能够实现既简练又高效的查询,通常的做法是增加冗余字段,比如增加一个"Path" 字段,查询时用模糊查询来进行左匹配。对Path建索引后,这种查询的效率还是相当高的,因此这种方式也是一种常规的设计方式;

SQL SERVER 2008引入了新的hierarchyid数据类型,可以用它来做本地存储并且在树层次结构中管理其位置.只用这个函数能简洁地表示层次结构中的位置.该 函数提供的一些内置的函数方法可以操作和遍历层次结构,使得存储和查询分层数据更为容易,而不需要像那样通过CTE递归来获得.

Hierarchyid类型其实是一个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。

hierarchyid 数据类型的值表示树层次结构中的位置。hierarchyid 的值具有以下属性:

  • 非常紧凑

    在具有 n 个节点的树中,表示一个节点所需的平均位数取决于平均端数(节点的平均子级数)。端数较小时 (0-7),大小约为 6*logAn 位,其中 A 是平均端数。对于平均端数为 6 级、包含 100,000 个人的组织层次结构,一个节点大约占 38 位。存储时,此值向上舍入为 40 位,即 5 字节。

  • 按深度优先顺序进行比较

    给定两个 hierarchyid 值 aba<b 表示在对树进行深度优先遍历时,先找到 a,后找到 b。hierarchyid 数据类型的索引按深度优先顺序排序,在深度优先遍历中相邻的节点的存储位置也相邻。例如,一条记录的子级的存储位置与该记录的存储位置是相邻的。

  • 支持任意插入和删除

    通过使用 GetDescendant 方法,始终可以在任意给定节点的右侧、左侧或任意两个同级节点之间生成同级节点。在层次结构中插入或删除任意数目的节点时,该比较属性保持不变。大多数插 入和删除操作都保留了紧凑性属性。但是,对于在两个节点之间执行的插入操作,所产生的 hierarchyid 值的表示形式在紧凑性方面将稍微降低。

hierarchyid 数据类型具有以下局限性:

  • 类 型为 hierarchyid 的列不会自动表示树。由应用程序来生成和分配 hierarchyid 值,使行与行之间的所需关系反映在这些值中。一些应用程序甚至可能不需要用类型为 hierarchyid 的列来表示树。可能这些值为对其他表中定义的层次结构中位置的引用。

  • 由应用程序来管理生成和分配 hierarchyid 值时的并发情况。不能保证列中的 hierarchyid 值是唯一的,除非应用程序使用唯一键约束或应用程序自身通过自己的逻辑来强制实现唯一性。

  • 由 hierarchyid 值表示的层次结构关系不是像外键关系那样强制实现的。可能会出现下面这种层次结构关系而且有时这种关系是合理的:A 具有子级 B,然后删除了 A,导致 B 与一条不存在的记录之间存在关系。如果这种行为不可接受,应用程序在删除父级之前必须先查询其是否有后代。

用于对分层数据进行索引的策略有两种:

  • 深度优先

    深度优先索引,子树中各行的存储位置相邻。例如,一位经理管理的所有雇员都存储在其经理的记录附近。

  • 广度优先

    广度优先将层次结构中每个级别的各行存储在一起。例如,同一经理直属的各雇员的记录存储在相邻位置。

例如下面的例子是一个职员表,数据有如下关系:

CodeUse 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') ;

3.

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 

4.向表里插入记录
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 ;

5.移动层级关系
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

6.获取最大的子节点,传递给GetDescendant() 函数获得新的子节点
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 

性能

我们来比较新类型和CTE的性能。为了比较,我们来举个例子,它的需求是重新获取经理'adventure-works \ james1'的所有分支。表就用本文提到的表:Employee(传统模型)和Organization(HierarchyID)。

CTE脚本:

WITH UpperHierarchy(EmployeeId, LastName, Manager, HierarchyOrder)
AS
(
    SELECT emp.EmployeeId, emp.LoginId, emp.LoginId, 1 AS HierarchyOrder
    FROM HumanResources.Employee AS emp
      WHERE emp.LoginId ='adventure-works\james1'
    UNION ALL
    SELECT emp.EmployeeId, emp.LoginId, Parent.LastName, HierarchyOrder + 1
    FROM HumanResources.Employee AS emp
          INNER JOIN UpperHierarchy AS Parent
                ON emp.ManagerId = parent.EmployeeId
)
SELECT EmployeeId, LastName
From UpperHierarchy

使用HierarchyID的脚本如下所示。你会注意到有2步:第一步是获取父节点,第二步是找出分支:

Declare @BossNode As HierarchyId  
Select @BossNode = EmployeeID From dbo.Organization Where EmployeeName = 'adventure-works\james1'
Select *
From dbo.Organization
Where @BossNode.IsDescendant(EmployeeId)= 1

从SSMS里可以看到,执行计划给了我们一些性能方面的信息。

图8-性能基准

我们可以看到CTE占了批处理的63%。这意味着HierarchyID要好50%。

我们可以看到返回父节点(james1)这一步占了查询的大部分(使用了扫描),因为列没有索引。但是,由于方法2里采占用了相同的比率,我们可以忽略这一点。

我们也可以看到CTE的执行计划比HierarchyID类型要复杂得多。这是因为主键允许表的唯一扫描。

如果我们来看这些需求使用的系统资源,那么对于CTE而言,结论是灾难性的。事件探查器跟踪显示了多个执行:


图9-系统资源使用情况

我们可以看到duration列里1/3-2/3的比例。然而IO使用情况却上升到了300。CTE过多使用临时表(Table Spool),这意味着很多的读操作。CPU使用也多用了9倍。

 

HierarchyID取得了压倒性的胜利。

局限

唯一性

HierarchyId类型天生就不支持唯一性。例如,同一个表里可能有两个根节点。显然,在你的程序里可能会陷入完整性的问题,但是,它也不可能唯一索引树而使整个树变得能聚集。

为了解决这个局限,我们可以在HierarchyId列上添加主键(或唯一索引):

ALTER TABLE dbo.Organization ADD CONSTRAINT
      PK_Organization PRIMARY KEY
      (
      EmployeeID
      )
HierarchyId上的主键或唯一索引允许表的深度优先索引。

对于前面的数据填充,该DDL将报错。事实上,每个节点的子节点都有相同的索引,这不允许唯一。为了纠正这个问题,我们需要在每一级别上排序子节点来重新组织树。为了实现它,有必要给GetDescendant()函数传递参数。该操作将在稍后进行说明。

外键

与上面描述的传统建模方式相反,外键引用父记录天生也不支持。事实上,HierarchyId类型存储树里节点的路径,而不是父节点。

不过,使用GetAncestor()函数来轻易获取父节点的标识符倒是可行的,如下面的语句:

Select EmployeeId.GetAncestor(1), EmployeeName
From dbo.Organization

GetAncestor()返回HierarchyId。如果HierarchyId列是表的主键(如我们的例子),添加一个外键引用到自身就是可行的了。

Alter Tabledbo.Organization
Add ParentId AS EmployeeId.GetAncestor(1)PERSISTED
      REFERENCES dbo.Organization(EmployeeId)

现在,我们的表有了和最初模型相同的完整性规则了。

HierarchyID函数

HierarchyID数据类型通过一系列的函数来操作:

· GetAncestor
· GetDescendant
· GetLevel
· GetRoot
· ToString
· IsDescendant
· Parse
· Read
· Reparent
· Write

在前面的例子里我们看到了前5个函数,接下来的5个在下表作出了解释:

函数 描述
IsDescendant 允许知道一条记录是不是层次结构里另一个的子节点
Parse 它和ToString()正好相反,它使从字符串里得到HierarchyID值成为可能
Read 类似Parse,但针对varbinary值
Write 类似ToString,但针对varbinary值
Reparent 允许通过更改父节点来移动层次结构里的节点

警告:所有的函数都是区分大小写的。

SQL Server2008 Hierarchyid数据类型的更多相关文章

  1. SQL SERVER 2008 Hierarchyid数据类型

    以往我们在关系数据库中建立树状结构的时候,通常使用ID+ParentID来实现两条纪录间的父子关系.但这种方式只能标示其相对位置.解决这类问题在SqlServer2005出现之前通常是采用游标来操作, ...

  2. SQL Server2008 TIME类型

    SQL Server2008现在有了一个TIME数据类型,它允许你只存储一个时间值而没有时间.如果想要存储一个特定的时间信息而不涉及具体的日期时,这将非常的有用.TIME数据类型存储使用24小时制,它 ...

  3. 说说SQL Server的数据类型

    以SQL Server为例,SQL Server的数据类型总共有33种,归纳为一下类别: 精确数字 Unicode字符串 近似数字 二进制字符串 日期和时间 其他数据类型 字符串   1.数字数据类型 ...

  4. SQL Server2008宝典 全书代码

    -- ============================================= -- Create database template -- ==================== ...

  5. sql server2008 R2 各个版本的区别与选择

    目前已知的SQL Server 2008 R2的版本有: 企业版.标准版.工作组版.Web版.开发者版.Express版.Compact 3.5版. 这个次序也是各个版本功能的强大程度从高到低的一个排 ...

  6. SQL Server常见数据类型介绍

    数据表是由多个列组成,创建表时必须明确每个列的数据类型,以下列举SQL Server常见数据类型的使用规则,方便查阅. 1.整数类型 int 存储范围是-2,147,483,648到2,147,483 ...

  7. hibernate中java类的成员变量类型如何映射到SQL中的数据类型变化

    hibernate映射文件??.hbm.xml配置映射元素详解--Hibernate映射类型 在从Hibernate的java的成员类型映射到SQL中的数据类型,其内映射方式它满足,SQL可以自己调制 ...

  8. 记录SQL Server2008日志文件损坏的恢复过程

    记录SQL Server2008日志文件损坏的恢复过程: 环境: 系 统:Windows Server2003 数据库:SQL Server2008 故障原因: 通过mstsc链接同一服务器时,用户界 ...

  9. SQL Server2008附加数据库之后显示为只读时解决方法

    啰嗦的话就不多说了,直入主题吧! 方案一: 碰到这中情况一般是使用的sa账户登录的,只要改为Windows身份验证,再附加数据库即可搞定. 方案二: 使用sa登录SQL Server2008附加数据库 ...

随机推荐

  1. LINQ to SQL活学活用(1):这要打破旧观念

    程序架构 如今比較经典的架构,看看以下图片. 怎样实现 在一个N层应用程序中我们怎样使用LINQ to SQL呢?这给刚刚入门的朋友的确是个难题,使用LINQ to SQL就是ORM技术,能够非常轻松 ...

  2. 菜鸟nginx源代码剖析数据结构篇(七) 哈希表 ngx_hash_t(下)

      菜鸟nginx源代码剖析数据结构篇(七) 哈希表 ngx_hash_t(下)   Author:Echo Chen(陈斌) Email:chenb19870707@gmail.com Blog:B ...

  3. Reuse Is About People and Education, Not Just Architecture

     Reuse Is About People and Education, Not Just Architecture Jeremy Meyer you MigHT AdopT THE AppRoA ...

  4. php 内置的 html 格式化/美化tidy函数 -- 让你的HTML更美观

    php 内置的 html 格式化/美化tidy函数 https://github.com/htacg/tidy-html5 # HTML 格式化 function beautify_html($htm ...

  5. Kinect 开发 —— 录音

    不涉及语音识别~~ <Window x:Class="KinectRecordAudio.MainWindow" xmlns="http://schemas.mic ...

  6. POJ 1061 青蛙的约会 数论水题

    http://poj.org/problem?id=1061 傻逼题不多说 (x+km) - (y+kn) = dL 求k 令b = n-m ; a = x - y ; 化成模线性方程一般式 : Lx ...

  7. [Angular] Create a custom validator for reactive forms in Angular

    Also check: directive for form validation User input validation is a core part of creating proper HT ...

  8. poj3244(公式题)

    Difference between Triplets Time Limit: 3000MS   Memory Limit: 65536K Total Submissions: 2476   Acce ...

  9. funuiTitle-居中问题修改

    今天遇到了一个问题,在一个actionbar上,title居中了,现在想要的方式是,让actionbar上显示返回按钮,后面紧跟着title.当时自己一直尝试要找到activity,然后在theme中 ...

  10. gridview-selector的设置

    其实它是跟listview相似的,如果你看下它跟listview的继承关系,就很容易理解了 public class GridView extends AbsListView { /** * Disa ...