从数据库检索分层数据






4.69/5 (9投票s)
高效地从数据库检索分层数据!
引言
分层数据的表示相对简单,但其管理和检索却相当复杂。本文介绍存储和检索分层数据的各种技术。
背景
分层数据模型是实现分层业务实体(如(总账层级、组织层级、菜单层级等))的成熟建模技术。分层数据也可用于从数据库检索基于角色的菜单。管理数据和遍历分层数据本身是一项艰巨的任务。
使用代码
存储和管理分层数据
理想情况下,分层数据通过级别(深度)表示,并应考虑使用树数据结构算法。现在,重要的是使用分层数据建模设计这样的表,并创建能够以优化方式返回分层数据的查询。
创建一个如下的分层表(地理位置层级)。
在此表中插入一些记录。(请在附加的 SQL 文件中查找插入脚本)
现在,重要的是以分层格式检索此表中的数据。这意味着,如果我们请求某个特定的地理位置(例如“India”),则查询应返回 India 的所有子孙记录。为了实现这一点,我们将使用 CTE(公用表表达式)。有关 CTE 的其他用法,请参阅 http://msdn.microsoft.com/en-us/library/ms190766.aspx。
上面表的创建表脚本。
CREATE TABLE [GLHierarchy](
[GUID] [int] IDENTITY(1,1) NOT NULL,
[GUCD] [varchar](10) NOT NULL,
[GUDesc] [varchar](50) NOT NULL,
[ParentGUCD] [varchar](10) NULL
) ON [PRIMARY] Create Table script for above table.
上面表的插入脚本。
/* Insert queries */
INSERT INTO [GLHierarchy] Values ('AP0','Asia-Pacific',null)
-- Level 2 Records --
INSERT INTO [GLHierarchy] Values ('IND','India','AP0')
INSERT INTO [GLHierarchy] Values ('PHL','Phillipines','AP0')
INSERT INTO [GLHierarchy] Values ('CHN','China','AP0')
-- Level 3 Records --
INSERT INTO [GLHierarchy] Values ('MAH','Maharashtra','IND')
INSERT INTO [GLHierarchy] Values ('KAR','Karnataka','IND')
INSERT INTO [GLHierarchy] Values ('GUJ','Gujarat','IND')
INSERT INTO [GLHierarchy] Values ('MNL','Manila','PHL')
-- Level 4 Records --
INSERT INTO [GLHierarchy] Values ('BOM','Mumbai','MAH')
INSERT INTO [GLHierarchy] Values ('PUN','Pune','MAH')
INSERT INTO [GLHierarchy] Values ('BAN','Bangalore','KAR')
GLSubordinates
假设您需要通过传递 GLCd 来返回地理位置树。为了实现这一点,您需要编写 CTE 查询。CTE 查询有些复杂,难以理解,因此我们将创建一些函数来简化此查询。我们将创建 GLSubordinates 函数,该函数接受 GLCd(GL 代码)。
----- create GLSubordinates function using CTE
if object_id( 'GLSubordinates', 'IF' ) is not null
drop function GLSubordinates
GO
create function GLSubordinates( @GLCD varchar(10) )
returns table as return with GLSubnodes( distance, GLID, GLCD, GLDesc, ParentGLCD, NodeSequence )
AS ( select 0, h.GLID, h.GLCD, h.GLDesc, h.ParentGLCD, convert( varchar(80), ltrim(str(h.GLID))) as NodeSequence
from [GLHierarchy] h
where h.GLCD = @GLCD
union all
select distance+1, h.GLID, h.GLCD, h.GLDesc, h.ParentGLCD, convert( varchar(80), sn.NodeSequence+'.'+ltrim(str(h.GLID)))
from [GLHierarchy] h inner join GLSubnodes sn on h.ParentGLCD = sn.GLCD)
select distance, GLID, GLCD, GLDesc, ParentGLCD, NodeSequence from GLSubnodes
GO
从下属中选择“IND”的记录。
select * from GLSubordinates('IND')
上述语句应返回以下结果。
上面的示例返回 GLCD = ‘IND’ 的所有子孙地理位置。NodeSequence 是可选字段,它只是显示树的深度,您可以看到 2, 2.5, 2.5.9, 2.5.10 等…
CTE 查询的性能相当高。在具有 12 个层级、24000 条记录的数据的 GLID 和 GLCD 上进行索引后,此查询的返回数据时间不到一秒。
默认情况下,CTE 查询的递归限制为 100 个级别。我认为这对于普通的分层数据来说已经足够了,但您也可以更改它。
有关更多信息,请参阅 http://msdn2.microsoft.com/en-us/library/ms181714.aspx。
您可以直接或在查询中使用此函数。通过添加级别检查,您可以自定义此函数以返回指定级别的树。
检索分层数据
我们可以从最后一个节点到根节点检索分层数据。
您可能需要了解子元素的祖父母信息。通过实现分层数据的 Superior 函数,您可以实现相同的功能。
假设有一个需求,即某些条件适用于一个城市,则该条件也应适用于该层级的所有父节点。
为了实现此功能,我们需要创建 GLSuperiors 函数。此函数也使用 CTE(公用表表达式)查询。GLSuperiors 函数遍历分层信息,并以相反方向返回遍历后的层级。
----- create the Superiors function using CTE
if object_id( 'GLSuperiors', 'IF' ) is not null
drop function GLSuperiors
GO
create function GLSuperiors( @GUCD varchar(10))
returns table as return with GLSupnodes
( distance, GUID, GUCD, GUDesc, ParentGLCD ) as
(select 0, h.GUID, h.GUCD, h.GUDesc, h.ParentGUCD
from [GLHierarchy] h where h.GUCD = @GUCD )
union all
(select distance-1, h.GUID, h.GUCD, h.GUDesc, h.ParentGUCD
from [GLHierarchy] h inner join GLSupnodes sn on h.GUCD = sn.ParentGLCD )
select distance, GUID, GUCD, GUDesc, ParentGLCD from GLSupnodes
GO
现在选择“BOM”城市代码的分层数据。
SELECT * FROM GLSuperiors('BOM')
以上 SELECT 语句返回 GLCd = 'BOM' 的所有父级祖父母地理位置。
我们已经完成了分层数据的管理和检索。我们可以使用相同的功能来填充分层菜单、公司层级或家谱等。
提高 C# 应用程序中分层数据的检索效率。
通常,我们将非事务性的分层数据存储在数据库中,这些数据会定期更新,并且不参与事务。我们可以使用 Enterprise Library Application Block 的缓存机制。我们需要创建父子数据结构来在应用程序中存储分层数据,我们只需要将每个分层数据的根数据存储在某个地方(缓存)。
实现 HierarchicalDataType
枚举。
public enum HierarchicalDataType
{
GULocation = 1,
OrganizationHierarchy = 2,
ProductHierarchy = 3,
ServiceCenterHierarchy = 4
}
HierarchicalData
类。 public class HierarchicalData
{
HierarchicalDataType dataType;
HierarchicalData parent;
string hierardhicalDataCD;
string hierardhicalDataDesc;
int order;
IList<HierarchicalData> child;
}
现在,您只需要使用分层数据填充树。这应该是一次性活动,您可以从 Application_Start 调用,并且必须将根节点存储到缓存中。为了更好地访问,您可以将节点信息存储在键值对(HashTable)中,其中键是分层数据的 CD(必须是唯一的),值是主要的 HierarchicalData 对象。
假设我们有一个网页,其中包含一些依赖于国家的下拉列表,并且用户选择了“India”(CD: 'IND'),它应该直接返回所有以“India”作为父级的地理位置(州)。
这减少了数据库调用,提高了非事务性分层数据的性能。
您还可以维护顺序,这对于按顺序显示非常有用。这对于创建菜单很有用。通过操作这些函数,您可以在 AJAX、Web 服务等中使用它们。
关注点
这是我一些有趣的探索活动的一部分。我可能会扩展本文档以供进一步使用。