树形结构是一类重要的非线性结构,在关系型数据库中如何对具有树形结构的表进行查询,从而得到所需的数据是一个常见的问题。本文笔者以 SQL Server 2000 为例,就一些常用的查询给出了相应的算法与代码,颇值得读者借鉴。

关系型数据库将数据按表结构形式进行组织。它对表格的处理方便灵活,且易学易用,因而得到广泛的应用。关系型数据库所处理的表格是线性结构的,表的每一行对应着一个数据元素,称做一条记录。记录与记录之间呈线性排列,彼此间没有联系, 然而,在解决实际问题时,常常会遇到非线性结构的数据。如下表所示,每一条纪录中的上级代码,就和其他纪录有着联系,这样就形成了一棵具有层次结构的树,它可以用下面的图来形象地表示:

树形结构是一种结点之间有分支,并具有层次关系的结构,它非常类似于自然界中的树。 树结构在客观世界中大量存在,例如家谱、行政组织机构都可用树形象地表示。树在计算机领域中也有着广泛的应用,例如在编译程序中,用树来表示源程序的语法结构;在数据库系统中,用树来组织信息;在分析算法的行为时,用树来描述其执行过程。

在关系型数据库中如何对具有树形结构的表进行查询,从而得到所需的数据是一种常见的需求。下面以SQL Server 2000 为例,就三种常用的查询给出相应的算法与代码:

1. 节点A的位于第n层的父亲节点的信息。

如:员工黄菁菁的两级上司的信息。

要实现这样的查询,常使用递归的方法。我们可以用SQL Server 2000 增加的用户定义函数 (UDF, User Defined Function)这个新特性来实现递归函数调用。下面是函数的定义:

CREATE FUNCTION dbo.GetManager
(
  @employee_id AS char(5),
@level AS int = 1 --缺省值为1
)
RETURNS char(5)

其中,employee_id表示要查询的员工号码,level表示高于该员工的级别数,返回的结果是上司的员工号码。

该函数的递归定义为: 如果 level = 0,则返回当前的员工号码;如果 level > 0,则返回直接上司的 level-1 级的上司号码。

根据这样的递归定义,我们可以写出完整的递归函数:

CREATE FUNCTION dbo.GetManager
(
@employee_id AS char(5),
@level AS int = 1
)
RETURNS char(5) AS
BEGIN
IF @level = 0
RETURN @employee_id
--如果 level 为0,表示已经找到其上司号码
RETURN dbo.GetManager(
(SELECT [上级号码] FROM [员工信息] WHERE [员工号码] = @employee_id),
@level - 1)
--如果 level 大于 0,则返回直接上司的 level-1 级的上司号码
END

执行下面的语句可以得到需要的结果:

SELECT * FROM [员工信息] WHERE [员工号码] =dbo.GetManager(‘E9907’, 2)

当然,如果要让该递归函数更为健壮,我们还需要在函数中加入容错检查,这里不再赘述。

2. 某棵子树的统计信息。

如:员工余顺景及其所有下属员工的工资总额。

这个查询同样使用递归的方法来实现。先看一下函数定义:

CREATE FUNCTION dbo.GetTotalSalary
(
@manager_id AS char(5)
)
RETURNS int

其中,@manager_id 是要统计的某位上司的员工号码,返回其所有下属的工资总额。

该函数的递归定义为:如果没有下属,则返回当前的工资额; 如果有下属,则返回所有下属的工资总额。

根据这样的递归定义,我们可以写出完整的递归函数:

CREATE FUNCTION dbo.GetTotalSalary
(
@manager_id AS char(5)
)
RETURNS int AS
BEGIN
RETURN (
SELECT [工资] FROM [员工信息] WHERE [员工号码] = @manager_id) +
CASE
WHEN EXISTS(SELECT * FROM [员工信息] WHERE [上级号码] = @manager_id) THEN
(
SELECT SUM(dbo.GetTotalSalary([员工号码])) FROM [员工信息]
WHERE [上级号码] = @manager_id
)
ELSE 0
END
END

上面的自定义用户函数中使用了CASE 搜索函数,它按指定顺序为每个 WHEN 子句的 Boolean_expression 求值,返回第一个取值为 TRUE 的 Boolean_expression 的 result_expression,如果没有取值为 TRUE 的 Boolean_expression,则当有ELSE子句时SQL Server将返回 else_result_expression; 若没有ELSE子句,则返回 NULL 值。

在自定义用户函数中,如果员工信息表中发现该员工有下属(EXISTS子查询),则为每个下属调用GetTotalSalary函数返回下属的工资总额,并用SUM函数求和;反之,则直接返回其工资额。

执行下面的语句可以得到所需的结果:

SELECT dbo.GetTotalSalary(‘E9902’) AS ‘工资总额’

实际工作还可能有这样的查询要求,即某名员工一共有多少个下属级别(包括其自身),如张建平一共有四个下属级别。用树的术语来描述,即求出某棵子树的深度。可以通过这样的递归函数来实现:

CREATE FUNCTION dbo.GetUnderlyingLevel
(
@manager_id AS char(5)
)
RETURNS int AS
BEGIN
RETURN
CASE
WHEN EXISTS(SELECT * FROM [员工信息] WHERE [上级号码] = @manager_id)
THEN 1 + (SELECT MAX(dbo.GetUnderlyingLevel([员工号码])) FROM [员工信息] WHERE [上级号码] = @manager_id)
ELSE 1
END
END

执行下面的语句可以得到所需的结果:

SELECT dbo.GetUnderlyingLevel('E9901') AS ‘下属级别’

3. 某棵子树的结点信息。

如:员工郑可可及其所有下属员工的信息

前面的两种查询返回的都是标量值,这里的查询需返回某棵子树的所有子节点的信息,这是一个结果集,需要用 table 数据类型来存储。函数定义如下:

CREATE FUNCTION dbo.GetSubtreeInfo
(
@manager_id AS int
)
RETURNS @treeinfo table
(
[员工号码] [char] (5) NOT NULL,
[姓名] [char] (10) NOT NULL,
[年龄] [int] NOT NULL,
[工资] [money] NOT NULL,
[上级号码] [char] (5) NULL,
[级别] [int] NOT NULL
)

其中,@manager_id 代表要查询的上司的员工号码,返回的是其所有下属的信息,这些信息存放在 table 型变量 @treeinfo 中。

由于该查询返回的是一个结果集,因此已经不能使用递归的方法来实现,我们使用循环的方法来实现,循环的过程为:将参数 @manager_id 所代表的上司的信息插入到表中,赋予级别0;级别增加为1,将所有上级号码为以上 @manager_id 的员工信息插入到表中;级别增加为2,将所有上级号码与第2步插入的记录中的员工号码一致的员工信息插入到表中;依次增加级别,直到找不到上级号码与前一步插入的纪录中的员工号码一致的员工信息为止。

为了实现这个循环,我们要用系统函数 @@ROWCOUNT 来判断前一步中是否有新的记录被插入到表中。如果有,则循环继续;如果无,则循环结束。另外,我们在表中增加了一个名为“级别”的字段,既可以显示出所在的级别关系,还可以用来代表每一次新插入的记录,可谓一举两得。完整的函数定义如下:

CREATE FUNCTION dbo.GetSubtreeInfo
(
@manager_id AS char(5)
)
RETURNS @treeinfo table
(
[员工号码] [char] (5) NOT NULL,
[姓名] [char] (10) NOT NULL,
[年龄] [int] NOT NULL,
[工资] [money] NOT NULL,
[上级号码] [char] (5) NULL,
[级别] [int] NOT NULL
) AS
BEGIN
DECLARE @level AS int
SELECT @level = 0
INSERT INTO @treeinfo
SELECT [员工号码], [姓名], [年龄], [工资], [上级号码], @level
FROM [员工信息]
WHERE [员工号码] = @manager_id
WHILE @@ROWCOUNT > 0
BEGIN
SET @level = @level + 1
INSERT INTO @treeinfo
SELECT E.[员工号码], E.[姓名], E.[年龄], E.[工资], E.[上级号码], @level
FROM [员工信息] AS E JOIN @treeinfo AS T
ON E.[上级号码] = T.[员工号码] AND T.[级别] = @level - 1
END
RETURN
END

下面是测试的结果:

SELECT * FROM dbo.GetSubtreeInfo(‘E9903’)

员工号码 姓名 年龄 工资 上级号码 级别

-------- --------- ------- --

E9903 郑可可 38 5000.0000 E9901 0

E9906 肖遥 26 3350.0000 E9903 1

E9907 黄菁菁 22 2800.0000 E9906 2

最后我们来看一个有趣的例子。将上面的函数稍做修改后,可以将该树型结构以图形化的方式打印出来,结果如下所示:

完整的函数如下所示:

CREATE FUNCTION dbo.GetSubtreeInfo2
( @manager_id AS char(5) )
RETURNS @treeinfo table
( [员工号码] [char] (5) NOT NULL,
[姓名] [char] (10) NOT NULL,
[年龄] [int] NOT NULL,
[工资] [money] NOT NULL,
[上级号码] [char] (5) NULL,
[级别] [int] NOT NULL,
[标记] [varchar] (200) NOT NULL
) AS
BEGIN
DECLARE @level AS int, @path AS varchar(200)
SELECT @level = 0, @path = 'NULL'
INSERT INTO @treeinfo
SELECT [员工号码], [姓名], [年龄], [工资], [上级号码], @level, ‘NULL->’+ [员工号码]
FROM [员工信息]
WHERE [员工号码] = @manager_id
WHILE @@ROWCOUNT > 0
BEGIN
SET @level = @level + 1
INSERT INTO @treeinfo
SELECT E.[员工号码], E.[姓名], E.[年龄], E.[工资], E.[上级号码], @level, T.[标记] + '->'+ E.[员工号码]
FROM [员工信息] AS E JOIN @treeinfo AS T
ON E.[上级号码] = T.[员工号码] AND T.[级别] = @level - 1
END
RETURN
END

使用以下语句,即可返回如上所示的树型结构示意图:

SELECT REPLICATE (‘ | ’, [级别]) + [姓名] AS 组织结构 FROM dbo.GetSubtreeInfo2(‘E9901’) order by [标记]

=================================

  求树的深度:

  CREATE FUNCTION dbo.GetUnderlyingLevel ( @manager_id AS char(5) )
RETURNS int AS
BEGIN
RETURN
CASE
WHEN EXISTS(SELECT * FROM [员工信息] WHERE [上级号码] = @manager_id)
THEN 1 + (SELECT MAX(dbo.GetUnderlyingLevel([员工号码])) FROM [员工信息] WHERE [上级号码] = @manager_id)
ELSE 1
END
END
--如:
SELECT dbo.GetUnderlyingLevel('E9901') AS '下属级别'
--返回:4

=================================

以上内容摘自网络,没能找到原文章地址,如有需要请自行搜索。

【转】用SQL实现树的查询的更多相关文章

  1. sql无限级树型查询

    表结构如下: 表数据如下: 一提到无限级,很容易想到递归,使用sql 的CET语法如下 with menu(Id,Name,ParentId,Level) as ( select Id,Name,Pa ...

  2. oracle 树状查询

    做树状查询的时候,oracle有自己的优势,一条sql语句就可以搞定,而mysql这种数据库就只能用递归了... 递归的项目实例: //递归取到栏目路径 public List getTreeList ...

  3. SQL SERVER中XML查询:FOR XML指定PATH

    SQL SERVER中XML查询:FOR XML指定PATH 前言 在SQL SERVER中,XML查询能够指定RAW,AUTO,EXPLICIT,PATH.本文用一些实例介绍SQL SERVER中指 ...

  4. oracle中的树状查询

    oracle中的树状查询 工作中经常会遇到将数据库中的数据以树的形式展现的需求.以下我们来看一下该需求在Oracle中如何实现. 首先我们需要有一个树形的表结构(当然有时候会出现表结构不是典型的树形结 ...

  5. SQL多表连接查询

    SQL多表连接查询 本文主要列举两张和三张表来讲述多表连接查询. 新建两张表: 表1:student  截图如下: 表2:course  截图如下: (此时这样建表只是为了演示连接SQL语句,当然实际 ...

  6. 【SQL】Oracle分页查询的三种方法

    [SQL]Oracle分页查询的三种方法 采用伪列 rownum 查询前10条记录 ? 1 2 3 4 5 6 7 8 9 10 11 [sql] select * from t_user t whe ...

  7. C# ADO.NET (sql语句连接方式)(查询)

    using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.We ...

  8. [.NET] SQL数据总笔数查询

    [.NET] SQL数据总笔数查询 程序下载 范例下载:点此下载 原始码下载:点此下载 NuGet封装:点此下载 数据查询 开发系统时,使用C#执行SQL查询指令,就可以从SQL数据库里查询所需数据. ...

  9. 图解SQL多表关联查询

      图解SQL多表关联查询     网上看了篇文章关于多表连接的,感觉很好,记录下来,以便日后自己学习  内连接     左连接     右连接       全外连接   1. 查两表关联列相等的数据 ...

随机推荐

  1. HTML代码 CSS

    第一步.编写横向菜单的HTML代码架构 请将以下代码添加到HTML文档的导航栏区域中. <ul id="menu"> <li><a href=&quo ...

  2. (字符串)最长公共字串(Longest-Common-SubString,LCS)

    题目: 给定两个字符串X,Y,求二者最长的公共子串,例如X=[aaaba],Y=[abaa].二者的最长公共子串为[aba],长度为3. 子序列是不要求连续的,字串必须是连续的. 思路与代码: 1.简 ...

  3. C++生成十字绣图案(二) 面向对象

    基本的十字绣线性生成中提供了判断下一步可以画的位置并且逐步生成的函数.以这些基本函数为基础,可以进行更多变化的图案设计. 为了方便的扩展,可以把线性生成写成一个类,以后的修改继承这个类. 头文件Bas ...

  4. JDBC五数据源和数据池(web基础学习笔记十一)

    一.为什么使用数据源和连接池 现在开发的应用程序,基本上都是基于数据的,而且是需要频繁的连接数据库的.如果每次操作都连接数据库,然后关闭,这样做性能一定会受限.所以,我们一定要想办法复用数据库的连接. ...

  5. JavaScript match()方法使用

    1.JavaScript match() 方法说明http://www.w3school.com.cn/jsref/jsref_match.asp 写法: stringObject.match(sea ...

  6. 发现linux shell中$0,$?,$!等的特殊用法

    记录下linux shell下的特殊用法及参数的说明 变量说明: $$ Shell本身的PID(ProcessID) $! Shell最后运行的后台Process的PID $? 最后运行的命令的结束代 ...

  7. MVC3循环添加数据

    foreach (var item in orderdetails) { var billdetails = new BillDetail(){BillCode = billoflading.Bill ...

  8. 【Oracle】迁移含有BLOG类型的字段的表

    日常数据库迁移过程中,发现dmp文件中的表,如果表含有blog类型的字段,无法进行迁移. 以下是我迁移含有blog类型字段表的经验分享' 使用PLSQL Developer进行导出 接着选择需要导出的 ...

  9. 【Linux】在Linux上查看并替换特殊字符

    现有windows上新建的一个txt文件file01.txt,内容如下: 我们通过ftp上传到Linux,在Linux下使用命令cat –A file01.txt查看文件内容发现该文件的结尾全是^M$ ...

  10. System.out.print实现原理猜解

    我们往往在main中直接调用System.out.print方法来打印,但是其实就这简单的一步里面有很多的玄机,因为main是static的,所以只能调用static的函数,那么print是stati ...