SQL Story(十一)--树状表游戏

树状结构的存储与管理,是每一个在关系型数据库平台上工作的程序员早晚都要遇到的问题。说大不大,怎么都能解决,说小不小,处理不好,有的是麻烦等着你。仁者见仁,智者见智,公说公有理,婆说婆有理(谁用机箱砸我?机箱是个好东西,乱丢会摔坏硬盘的,你看我话没说完你又把显示器丢了……),咳咳,好吧,闲话少说,我们从最大路的处理风格谈一谈吧。这里面的大部分内容并非我的独创,从很久很久以前,数据库程序员们就这样做啦。

树状表的结构化表达

    在一切开始前,我们先就树状表的表示方式达成一个共识。在关系型数据库中,我们当然没有办法这样直接表示一个树:

a

b  c

d e f g

    相应的,我们会把它变形为平面表,这种变形让我想起拓扑几何:

r      n1    n2

a      b     d

a      b     e

a      c     f

a      c     g

存储结构

稍有经验的朋友,大概都不会试着把树状结构一层一列的存进去了吧。这样做的问题是显而易见的:与表中存储的信息结构不同,表的结构应该是相对固定的,不能随便改动。而对于层数不能固定的普通树状结构,这是不可思议的。没有必要讨论过多的错误,我们选择一个相对正确的方式――把树状结构抽象成适合关系数据库的形式。

只考虑某一个节点的话,和这个节点相关的信息是:它的唯一标识、父节点、子节点、数据信息。这其中只有子节点的数目不定。不过如果每一个节点都确定了自己的父节点,显然可以省略子节点。这样一来,一个节点需要存储三部分信息――它的唯一标识、父节点、数据信息。一个理想的TreeView表只需要三个节点就可以了,用SQL语句来表达就是

CREATE TABLE [dbo].[TreeView] (

       [ID] [char] (10) PRIMARY KEY,

       [PID] [char] (10) FOREIGN KEY REFERENCES [dbo].[TreeView],

       [DATA] [char] (10) 

)

       ID是当前节点的唯一标识号,显然它应当是主键;我们建立的是一个自闭的存储结构,每一个节点的父节点也应当出自表中存储的节点,所以PID列引用ID作为外键;至于DATA,它是节点中的信息,通常和树的结构没有绝对关系。我把这三列全设成Char(10),是为了后面做演示更方便,当然也有人喜欢用自动标识列来做主键,在这种场合,也自有其优点。为了维护数据的完整性或存储、检索等方面的考虑,实用中我们可能会采用更复杂的结构,不过骨干就这样子了。这个结构从数学上讲很简洁,而且是自洽的。如果一个节点没有父节点,那么它的PID就等于它自己的ID。这并不违反我们关于主外键的定义。

信息的管理与使用

       树表的结构确定后,问题就集中在如何读写其中的数据。

       增加节点:增加一个叶节点很简单,只要指定这个节点的父节点,把它“挂接”到TreeView中。从递归的角度来看,我们可以重复这一步骤,真到插入一个完整的子树。相对而言,比较麻烦的是,如何把一个子节点插入到现有的树中间,而不是最末端。比如存在一个节点N,它的根为R,现在要在R和N之间插入一个新节点N’,我们可以这样做:把N’挂在R下面,作为它的子节点,然后把N的父节点指定为N’即可。

       修改节点:这里指修改树结构,改变某一节点的父节点,在这种结构中,修改是很方便的,只要调用标准的update就可以。

       删除节点:删除节点时要注意这个节点下面还有没有子节点,如果有,我们通常以两种方式处理,一是把相关子节点全删掉,如果是MS SQL Server 2000这样的系统,你可以很简单的在建表时将外键约束指定为支持级联删除,自己写一个级联删除比较麻烦,不过也不是不可能,重点在于,为这个过程建立递归。简要示例如下:

--定义存储过程

CREATE PROCEDURE DeleteNode

       @NodeID Char(10)

AS

BEGIN

       --以当前节点的子节点作为记录集建立游标

       DECLARE ChildNodes CURSOR

       READ_ONLY

              FOR SELECT ID FROM TreeView WHERE PID = @NodeID

       DECLARE @ChildNode VARCHAR(40)

       OPEN ChildNodes

       FETCH NEXT FROM ChildNodes INTO @ChildNode

       WHILE (@@fetch_status <> -1)--判断记录集是否成功打开

       BEGIN

              IF (@@fetch_status <> -2)

              BEGIN

                     --递归调用

                     EXEC DeleteNode @ChildNode

              END

              FETCH NEXT FROM ChildNodes INTO @ChildNode

       END

       CLOSE ChildNodes

       DEALLOCATE ChildNodes

       --代码执行到这里,可以确定@NodeID不再有子节点,现在,我们删除它

       DELETE FROM TreeView WHERE ID = @NodeID

END;

当然,这是一种比较低效的设计,每一个将要删除的节点都要执行一次Delete,比较有效率的方法是多深入一层,操作当前节点的子节点。有兴趣的读者可以一试。

       查询:树状表的查询是最有趣的内容之一。当然仅仅查出某一个节点的信息没有太大的意思,我们希望的是得到从当前节点一直向下(通常是到最底层)的完整子树。这同样需要一个递归。让我们从一个归纳法游戏开始,事先,我们先在TreeView中插入以下数据:

ID

PID

DATA

----------

----------

----------

ROOT

ROOT

ROOT

N11

ROOT

Node1-1

N12

ROOT

Node1-2

N13

ROOT

Node1-3

N21

N11

Node2-1

N22

N11

Node2-2

N23

N12

Node2-3

N24

N13

Node2-4

N31

N21

Node3-1

N32

N21

Node3-2

N33

N21

Node3-3

N34

N22

Node3-4

 

       归纳法第一步,当然是构造初值,查询子树的根节点就是标准的SQL,SELECT ID, DATA FORM TreeView WHERE ID = …,在这里有一个细节,查找表中所有的根节点(细心的读者可能早就发现,我们设计的这个TreeView可以存储任意多个树)可以通过SELECT R.ID, R.DATA FORM TreeView R WHERE R.ID = R.PID来实现。简单起见,我们就从这里起步吧。

       第二步,选择出前两层也不难,

SELECT R.ID, N1.ID, N1.DATA

FROM TreeView R

LEFT JOIN TreeView N1

ON R.ID = N1.PID

AND R.ID <> N1.ID

WHERE R.ID = R.PID

       我们要注意的是加蓝的部分,这是增加一层后做改动的部分。

       很容易,我们得到前三层,

SELECT R.ID, N1.ID, N2.ID, N2.DATA

FROM TreeView R

LEFT JOIN TreeView N1

ON R.ID = N1.PID

AND R.ID <> N1.ID

LEFT JOIN TreeView N2

ON N1.ID = N2.PID

AND N1.ID <> N2.ID

WHERE R.ID = R.PID

       同样的,我们重点观察加蓝的部分。相信经过这两步,大家会发现其中的规律。现在我们可以把这个过程自动化了……

       ……

       不知道大家是否还记得前几集里那个关于四色问题的笑话,我又一次犯了这样的错误。一个多月以来,我就陷在这里不能自拔。显然,我低估了问题的难度。这个问题似乎不能转化为一个简单表达式,只能通过一个递归的流程来实现。而流程化的程序又非SQL所长。这里面有着各种各样的麻烦。相信试过的朋友都有体会。现在我使用的架构问题是显然的,它需要明确知道到底从子树的顶点向下到底有多少层。所以,计算树的层数就首先被提了出来。

       但是,但是,但是……(我简直没脸见人啦)我一直找不到一个简洁优雅的方法。一个多月的时间就这么过去了。我不得不一点点放弃我的原则(此乃人生堕落之始啊)。最后,我得承认,要想写出一个优雅的树状表选择来,对我还是比较困难的。所以,在这一篇文章里,我们先讨论到这里,树状表的选择――其实应该说树状视图构造,还是留到下次再讨论吧。

时间: 2024-09-28 08:27:51

SQL Story(十一)--树状表游戏的相关文章

MySQL递归查询树状表的子节点、父节点具体实现_Mysql

简介:mysql5.0.94版本,该版本以及较高级的版本(5.5.6等等)尚未支持循环递归查询,和sqlserver.oracle相比,mysql难于在树状表中层层遍历的子节点.本程序重点参考了下面的资料,写了两个sql存储过程,子节点查询算是照搬了,父节点查询是逆思维弄的. 表结构和表数据就不公示了,查询的表user_role,主键是id,每条记录有parentid字段(对应该记录的父节点,当然,一个父节点自然会有一个以上的子节点嘛) 复制代码 代码如下: CREATE FUNCTION `g

MySQL递归查询树状表的子节点、父节点

简介:mysql5.0.94版本,该版本以及较高级的版本(5.5.6等等)尚未支持循环递归查询,和sqlserver.oracle相比,mysql难于在树状表中层层遍历的子节点.本程序重点参考了下面的资料,写了两个sql存储过程,子节点查询算是照搬了,父节点查询是逆思维弄的. 资料参考:http://blog.csdn.net/ACMAIN_CHM/article/details/4142971#comments 表结构和表数据就不公示了,查询的表user_role,主键是id,每条记录有par

论坛树状记录表的堆栈展开

由于工作原因,涉及到一个树状存放记录的表,要求程序中把树状表全部展开,并输出相应的数据內容.由于涉及到此种操作的地方很多,比如网络上的\\\"论坛"就是典型的采用树状存放记录的表,特此整理出来与大家分享.在很多资料都有介绍展开树状记录的程序,但是很多是采用递归的方法.我們知道,递归的方法逻辑比较简单,实际操作起来比较容易.但是递归有一个最大的缺点就是占用资源太多,速度太慢.如果在互联网的"论坛"上采用此种方法,在表记录很多的情况下将是一个非常严重的问题.下面的程序在

论坛关键技术,树状记录表的堆栈展开

    由于工作原因,涉及到一个树状存放记录的表,要求程序中把树状表全部展开,并输出相应的数据內容.由于涉及到此种操作的地方很多,比如网络上的\\\"论坛"就是典型的采用树状存放记录的表,特此整理出来与大家分享.     在很多资料都有介绍展开树状记录的程序,但是很多是采用递归的方法.我們知道,递归的方法逻辑比较简单,实际操作起来比较容易.但是递归有一个最大的缺点就是占用资源太多,速度太慢.如果在互联网的"论坛"上采用此种方法,在表记录很多的情况下将是一个非常严重的

树状结构 求和-sql 按树状结构 分组求和 父节点包括所有字节点的值

问题描述 sql 按树状结构 分组求和 父节点包括所有字节点的值 有一个表存树状结构: 值 上级节点 所属级数 001 * 1 00101 001 2 00102 001 2 0010203 00102 3 ... 00103 001 2 ... 在其他表中存储对应的数据,有[金额]和[数状表的字节点值]. 如: 100.00 / 0010203 现在想做出求和的效果如下 码值 合计 001 161 00101 61 0010101 50 0010101 11 00102 100 0010203

sql 树 汇总 子汇总父-sql 树状结构 向下汇总

问题描述 sql 树状结构 向下汇总 有结构:p_id m_id |num0 1 |81 2 |22 3 |1 3 4 |1需用语句快速实现:p_id m_id |num sum |0 1 |81 2 |2 82 3 |1 103 4 |1 11

使用“使用中值排序基数法”实现树状结构(一)

排序|排序 在BBS的编写中,经常有人问怎样实现树状结构?一个比较不负责任的回答是:使用递归算法.当然,递归是一个可行的办法(二叉树的历遍也好象只能使用递归算法),但对于BBS来说,这样做势必要进行大量的Sql查询(虽然可以使用存储过程来做,但要从根本上加快速度,则应该考虑更快的算法). 下面给出一个可行的彻底屏弃递的实现树状结构的算法. 下面给出另一种使用"使用中值排序基数法"实现树状结构: 一.主要思想:增加一个排序基数字段ordernum,回复同一根贴的贴子中插入贴子时,排序

用中值排序基数法实现树状结构——让递归滚一边去

递归|排序 用中值排序基数法实现树状结构     在BBS的编写中,经常有人问怎样实现树状结构?一个比较不负责任的回答是:使用递归算法.当然,递归是一个可行的办法(二叉树的历遍也好象只能使用递归算法),但对于BBS来说,这样做势必要进行大量的Sql查询(虽然可以使用存储过程来做,但要从根本上加快速度,则应该考虑更快的算法).下面给出一个可行的彻底屏弃递的实现树状结构的算法.     下面给出另一种使用"使用中值排序基数法"实现树状结构:一.主要思想:增加一个排序基数字段ordernum

树状数组专题【完结】

1 国外的论文点击打开链接 2 我的总结点击打开链接 任何能够造成树状数组下表为0的操作都会使得程序TLE,这是个很重要的地方! 第一题 poj 2299 Ultra-QuickSort 点击打开poj 2299 思路: 离散化+树状数组 分析: 1 题目的意思就是要求逆序数对 2 题目的输入个数有500000的规模但是每个数的最大值为999999999,因此我们需要离散化这些数据 3 对于数据9 1 0 5 4我们离散化成5 2 1 4 3 那么对于输入一个树a[i]我们去求一下它的离散化后的