使用 DB2 pureXML 模拟社会网络
据 Morgan Stanley 于 2008 年 3 月的一份报告《互联网趋势》(Internet Trends)中指出,从 2007 年 1 月至 2008 年 1 月期间,互联网应用明显从企业应用( Business )转向个人应用( Consumer ),在 2008 年个人应用的访问量已经赶上企业应用,而且预计到 2011 年个人应用的访问量将可能达到企业应用的 2 倍左右。这其中增长最快速的是社会关系网(Social Networks),其增长速度接近 60%。人们花在社会关系网的时间约占 16%,而这一领域在 3 年前是不存在的。报告还引用了 Alex 流量排名,2007 年名不见经传的 facebook 网站(http://www.facebook.com)在 2008 年一跃挺进到第六名。Facebook 的用户数目比上年增长率达到了 305%。
社会关系网络是一种新兴的网络应用,其中典型的有上文提到的 facebook 网站以及国内的“海内”网(www.hainei.com),它为我们提供了一个和知己保持联系及密切沟通的平台,我们可以和所有的朋友、同事、同学共享生活的点滴,也可以结识新朋友,或许还可以找到多年未谋面的老朋友。像 facebook 这样的大型社会关系网站,通常提供一些应用程序,如查找朋友功能以及共享相册、视频、事件簿等。本文中仅专注于解决人与人之间的关系,即如何组织和管理社会关系网络的数据,开发相关的应用程序。
由于社会关系网络的复杂性,其数据建模方式如果采用关系模型则相当复杂,而 XML 是以层次化的树状结构、通过元素(element)与属性(attribute)来存储数据,这就使得 XML 模型比关系型模型更加便捷,而且更易于扩展。同时,DB2 V9.5 的 pureXML 为以 XML 模型支撑的应用系统提供了强大的支持。
这里,先设定一些社会关系中的常见问题,并在下文中一一解决。
- 在这个圈子里,谁最受欢迎?
- 如何找到我的同学?
- 如何找到我的同事?
- 谁和我有共同的兴趣爱好?
- 谁是我们共同的朋友?
总体上来看,社会关系主要需要考虑两个要素,一个是人,另一个是人与人之间的关系。
如果采用传统的关系型建模方式,对于人这个实体就需要有若干表来描述,例如人员基本信息,人员住址,人员爱好等等。而随着不同类型的人参与进来,例如已经参加工作的人,那么将要增加一张数据表来描述从业情况以及以往工作经历。人与人之间的关系也需要多张数据表来描述,一张数据表描述谁和谁之间有关系,另一张数据表描述这两个人之间有哪些关系。如果在初期没有仔细考虑的话,那么在后期应对变化时则相当被动。
而采用 DB2 pureXML 则可以从更宏观的角度来确立数据模型。
人是一个对象,那么创建一张包含 XML 字段 PERSON 表,建表命令如清单 1 所示。
create table person (person_info xml); |
人员关系是一个对象,创建一张包含 XML 字段的 FRIENDSHIP 表,建表命令如清单 2 所示。
create table friendship (friendship_info xml); |
至于每个人的特性如何多样,人与人之间的关系多么复杂,则由 XML 字段来描述,从而达到快速完成总体建模的目的。
完成了总体的建模,现在来看看 person 内部结构应该是怎样的。
首先,每个人应该有一个唯一标识。由于唯一标识不可能重复出现在一个 XML 文档中,因此可以放在 XML 文档的属性上。
其次,每个人的信息可以用多个段落标识,比如基本信息 (basic_info),学习经历 (campus_experience),工作经历 (work_experience) 等。
人员的基本信息中可以存放人员姓名、性别、是否已婚、生日、联系地址、电话等,可以有多份学习经历和工作经历的信息,如图 1 的 schema 所示。完整的 schema 文件请参考附件内容。
当然,随着系统的日渐完善,可以陆续加入一些新的信息或模块,例如对学习经历,可以把课程都加进去,某课程由某位教师授课等等;增加参加某俱乐部的信息等等。不管如何变动,都不会对外层的模型产生较大影响。这里仅为说明,不做深入讨论。
清单 3 给出了一个人员信息的示例 XML 文档。
图 1. 人员信息的 Schema 描述
清单 3. 人员信息的示例 XML 文档
<?xml version="1.0" encoding="UTF-8"?> <!-- 基本信息 --> ……… birthday>… … <!-- 教育背景 --> … campus_experience> <!--工作经历 --> … <!--工作经历 --> … |
在真实的场景,一些辅助表也是必不可少的,例如人与人之间关系类型(是朋友,同学还是同事等)、工作单位、学校,本文仅添加关系类型辅助表如清单 4,而对工作单位信息和学校信息直接以名称表示。感兴趣的读者可以自行创建这些表,并在人员的 XML 信息中以 id 替换名称作为关联。
create table friendType (type_id int NOT NULL,type_name varchar(255)); |
综合以上建模过程,我们得到了以下数据表:PERSON 表、FRIENDSHIP 表和 FRIENDTYPE 表,如图 2 所示。
图 2. 表结构概览
为了演示如何解决本文开头提出的问题,我们构造如下数据:
6 个人员 p1、p2、p3、p4、p5 和 p6,其中各个人员差异性较大,有些人员有 work_experienc 元素 , 且可能包含多个子节点。而有些有包含多个子元素 campus_experience 元素,另一些则可能仅有 basic_info 元素。在表 1 中,每个元素列下面的数字表示是否有该元素,0 表示没有,1 和 2 分别表示有 1 个和 2 个该元素。
表 1. 人员数据
Person ID | basic_info | campus_experience | work_experience |
p1 | 1 | 1 | 2 |
p2 | 1 | 1 | 1 |
p3 | 1 | 0 | 1 |
p4 | 1 | 2 | 0 |
p5 | 1 | 0 | 1 |
p6 | 1 | 0 | 0 |
同时,还为这 6 个人设置了互相之间的关系,如表 2 所示(用√表示两个人之间有关系)。以人员 p1 为例,他与 p2、p3 和 p4 有关系,用 XML 描述如清单 5 所示。
表 2. 人员相互关系设定
P1 | P2 | P3 | P4 | P5 | P6 | |
P1 | √ | √ | √ | |||
P2 | √ | √ | ||||
P3 | √ | √ | ||||
P4 | √ | √ | √ | √ | ||
P5 | √ | √ | ||||
P6 | √ | √ |
清单 5. 用 XML 描述 p1 和其他人的关系
<?xml version="1.0" encoding="UTF-8"?> |
其中 friendtype 属性标识关系的类别,具体的定义在 friendtype 表中描述,如普通朋友,同事,同学等。完整的数据请参考附件。
现在回到文章前面提到的若干问题,并讨论如何解决。
这个问题在技术上等同于:“谁的朋友最多?”,也就是要查找 friendship 表,并将朋友个数以逆序排序,并打印出来其名称。
将该问题分解为 2 个步骤: 1) 对朋友个数进行排序 2)找出来对应的人的名称。
在 XPath 规范中有 fn:count() 函数可以完成个数统计工作,对应的 XPath 表达式为 /friendship/fn:count(friend),由于 xmlquery() 选择出来的是一个 XML 片段,因此需要使用 xmlcast() 做一个转换,使之能够被 SQL 语句的 order by 使用,如清单 6 所示。
ORDER BY XMLCAST(XMLQUERY('$d/friendship/fn:count(friend)' PASSING T.FRIENDSHIP_INFO AS "d" ) AS INT ) DESC |
第二个步骤,在查找的时候,则需要和 person 表做关联,如清单 7 所示。
WHERE XMLEXISTS('$p/person[fn:data(@pid)=$t/friendship/fn:data(@my_id)]' PASSING P.PERSON_INFO AS "p", T.FRIENDSHIP_INFO AS "t") |
综合起来查询语句如清单 8 所示。
SELECT XMLQUERY('$d/person/basic_info/person_name/text()' PASSING P.PERSON_INFO AS "d") AS NAME, XMLCAST(XMLQUERY('$d/friendship/fn:count(friend)' PASSING T.FRIENDSHIP_INFO AS "d") AS INT ) FRIENDCOUNT FROM PERSON P, FRIENDSHIP T WHERE XMLEXISTS('$p/person[fn:data(@pid)=$t/friendship/fn:data(@my_id)]' PASSING P.PERSON_INFO AS "p", T.FRIENDSHIP_INFO AS "t") ORDER BY XMLCAST(XMLQUERY('$d/friendship/fn:count(friend)' PASSING T.FRIENDSHIP_INFO AS "d" ) AS INT ) DESC |
执行后的结果清单 9 所示,可以看出王五的朋友最多,即王五在这个圈子里最受欢迎。
NAME FRIENDCOUNT -------- ----------- 王五 4 黄耀华 3 张三 3 李四 2 马六 2 5 条记录已选择。 |
现在要为 p2( 张三 ) 找到他的同学,逻辑上应该针对他的每一个教育经历找出来同一个学校且入学时间相同的人(此处不考虑专业,读者可以自行添加此条件)。
将问题分解为三部分:
1) 根据指定的 id (“p2”) 取得该人员的 XML 数据,并取出其教育经历;
2) 从人员表中查询,找出和该人员的学校以及入学时间相同的人员;
3) 返回这些人员列表。
xquery let $p :=db2-fn:xmlcolumn("PERSON.PERSON_INFO")/person[data(@pid)="p2"] for $ep in $p/campus_experience/cexp for $f in db2-fn:xmlcolumn("PERSON.PERSON_INFO")/person where $f/campus_experience/cexp[data(@school)=$ep/data(@school) and ./period/data(@from)=$ep/period/data(@from)] and $f/data(@pid)!="p2" return $f |
从而可以取得 p2 的同学列表如清单 11 所示。
王五北京市海淀区上地东路一号 游泳 |
这个场景和找同学是类似的,我们在这里采用了不同于问题 2 中的 XQuery 的实现方式,用如清单 12 所示的 SQL/XML 语句。具体语法不在此赘述,请参考相关教程或信息中心。
SELECT XMLQUERY('$d/person/data(@pid)' PASSING P.PERSON_INFO AS "d") PID, XMLQUERY('$d/person/basic_info/person_name/text()' PASSING P.PERSON_INFO AS "d") NAME FROM PERSON P WHERE XMLEXISTS( 'for $wexp in $p/person/work_experience/wexp for $myjob in db2-fn:xmlcolumn("PERSON.PERSON_INFO") /person[data(@pid)=$myid] /work_experience/wexp where $wexp/data(@company)=$myjob/data(@company) return $wexp/data(@company)' PASSING P.PERSON_INFO AS "p", 'p2' AS "myid") |
结果如清单 13 所示。
PID NAME -------------------------------------------------------- p1 黄耀华 p2 张三 2 条记录已选择。 |
这个问题和以上问题的解决是类似的,留给读者自己作为练习。
找出来共同的朋友是比较有意思的一件事。在 pureXML 中查找 p1 和 p2 的共同朋友,只需要针对 p1 的每一个朋友,和 p2 的每一个朋友逐一进行比较,如果其 id 相同,则返回。查询语句如清单 14 所示。
xquery for $i in db2-fn:xmlcolumn("FRIENDSHIP.FRIENDSHIP_INFO") /friendship[data(@my_id)="p1"]/friend for $j in db2-fn:xmlcolumn("FRIENDSHIP.FRIENDSHIP_INFO") /friendship[data(@my_id)="p2"]/friend where $i/data(@id)=$j/data(@id) return $i; |
输出结果清单 15 所示,p4 是 p1 和 p2 的共同朋友。
1 条记录已选择。 |
至此,我们已经完成了建模、建数据表以及设计演示数据,并解决了文章开始时所提出来的所有问题。在实际的社会关系网络应用程序开发过程中,我们将会面对更加复杂的数据和更多的问题,但是我们基本上都可以上述的方法进行处理。
来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/15082138/viewspace-591947/,如需转载,请注明出处,否则将追究法律责任。
转载于:http://blog.itpub.net/15082138/viewspace-591947/