利用数据库表构造无限级分类,通常我们会采用下面这种方式
这种主子关系结构清晰,但查询时会有一些小问题;查询当前类别的所有子类及子类下的子孙类,会出现递归查询,而实际应用中这种查询是很常见的,比如我们要查询所有技术类文章信息;
解决这类问题在SqlServer2005出现之前通常是采用游标来操作,但熟悉数据库内部机制的人都知道使用游标带来的性能问题和其他问题是比较严重的
到了SqlServer2005下,可以选择用CTE来做递归查询,例如要查询所有技术类文章,就可使用这种方式,向上递归和向下递归基本实现方式是基本一致的
/*查询所有"技术类文章"类别及其子孙类*/
WITH T_Category(id,name,parentid) as
(
SELECT CategoryID,CategoryName,ParentId FROM Category WHERE CategoryID=3
UNION ALL
SELECT A.CategoryID,A.CategoryName, A.ParentID FROM Category A ,T_Category B where A.ParentId=B.id
)
SELECT * FROM T_Category
这种方式查询比较简练,但由于数据库内部是采用递归查询的方式,其效率依旧不高;
为了能够实现既简练又高效的查询,通常的做法是增加冗余字段,比如增加一个"Path"字段:
查询时用模糊查询来进行左匹配,如查询所有文章类别时这样写, Select * from SiteCategory where path like ‘文章%'
对Path建索引后,这种查询的效率还是相当高的,因此这种方式也是一种常规的设计方式;
下面介绍另一种设计方式,利用SqlServer2008中的Hierarchyid字段类型来构造这种结构;
创建一客户地区信息表“Customer”,结构如下
hierarchyid类型为CLR类型,以二进制形式保存数据,插入部分测试数据
insert into CustomerLocation(ID,location) values('/','全世界')
insert into CustomerLocation(ID,location) values('/0/','亚洲')
insert into CustomerLocation(ID,location) values('/1/','欧洲')
insert into CustomerLocation(ID,location) values('/2/','非洲')
insert into CustomerLocation(ID,location) values('/3/','南北美洲')
insert into CustomerLocation(ID,location) values('/4/','大洋洲')
insert into CustomerLocation(ID,location) values('/0/1/','韩国')
insert into CustomerLocation(ID,location) values('/0/0/','中国')
insert into CustomerLocation(ID,location) values('/0/0/1/','华北大区')
insert into CustomerLocation(ID,location) values('/0/0/2/','华南大区')
insert into CustomerLocation(ID,location) values('/0/0/3/','西南大区')
insert into CustomerLocation(ID,location) values('/0/0/4/','西北大区')
insert into CustomerLocation(ID,location) values('/0/0/1/0/','山东省')
insert into CustomerLocation(ID,location) values('/0/0/1/1/','河南省')
insert into CustomerLocation(ID,location) values('/0/0/1/0/0/','青岛市')
insert into CustomerLocation(ID,location) values('/0/0/1/0/1/','济南市')
insert into CustomerLocation(ID,location) values('/0/0/1/0/2/','烟台市')
insert into CustomerLocation(ID,location) values('/0/0/1/1/0/','郑州市')
查看表中数据
select *,ID.GetLevel() as 层次 from CustomerLocation
ID.GetLevel() 是Hierarchyid字段类型内置函数,用于获取当前层次级别,另外提供了一系列相关查询函数,可以方便的查询父子关系数据
- 向上查询“华北大区”的所有父级直到根级
DECLARE @TID hierarchyid
SELECT @TID=ID FROM CustomerLocation WHERE location='华北大区'SELECT *, ID.GetLevel() as 层次,ID.ToString() as 路径 FROM CustomerLocation WHERE @TID.IsDescendantOf(ID)=1
- 向下查询“华北大区”的所有子孙级
DECLARE @TID hierarchyid
SELECT @TID=ID FROM CustomerLocation WHERE location='华北大区'
SELECT *, ID.GetLevel() as 层次,ID.ToString() as 路径 FROM CustomerLocation WHERE ID.IsDescendantOf(@TID)=1
该类型的一些主要的函数(下面是引用网上的部分内容):
◆GetAncestor :取得某一个级别的祖先
◆GetDescendant :取得某一个级别的子代
◆GetLevel :取得级别
◆GetRoot :取得根
◆IsDescendantOf :判断某个节点是否为某个节点的子代
◆Parse :将字符串转换为hierarchyid。该字符串的格式通常都是/1/这样的
◆Read :Read 从传入的 BinaryReader 读取 SqlHierarchyId 的二进制表示形式,并将 SqlHierarchyId 对象设置为该值。不能使用 Transact-SQL 调用 Read。请改为使用 CAST 或 CONVERT。
◆GetReparentedValue :可以用来移动节点(或者子树)
◆ToString :将hierarchyid转换为字符串,与parse正好相反
◆Write Write 将 SqlHierarchyId 的二进制表示形式写出到传入的 BinaryWriter 中。无法通过使用 Transact-SQL 来调用 Write。请改为使用 CAST 或 CONVERT。