关于XPath的用法
[原作者:guoyan1981]
节点匹配路径Xpath
在利用XSL进行转换的过程中,匹配的概念非常重要。在模板声明语句xsl:template match = ""和模板应用语句xsl:apply-templates select = ""中,用引号括起来的部分必须能够精确地定位节点。具体的定位方法则在XPath中给出。
另外,也可以使用Xpath对XML文档进行搜索、定位。
之所以要引入XPath的概念,目的就是为了在匹配XML文档结构树时能够准确地找到某一个节点元素。可以把XPath比作文件管理路径:通过文件管理路径,可以按照一定的规则查找到所需要的文件;同样,依据XPath所制定的规则,也可以很方便地找到XML结构文档树中的任何一个节点。
在介绍XPath的匹配规则之前,我们先来看一些有关XPath的基本概念。首先要说的是XPath数据类型。XPath可分为四种数据类型:
节点集(node-set)
节点集是通过路径匹配返回的符合条件的一组节点的集合。其它类型的数据不能转换为节点集。
布尔值(boolean)
由函数或布尔表达式返回的条件匹配值,与一般语言中的布尔值相同,有true和false两个值。布尔值可以和数值类型、字符串类型相互转换。
字符串(string)
字符串即包含一系列字符的集合,XPath中提供了一系列的字符串函数。字符串可与数值类型、布尔值类型的数据相互转换。
数值(number)
在XPath中数值为浮点数,可以是双精度64位浮点数。另外包括一些数值的特殊描述,如非数值NaN(Not-a-Number)、正无穷大infinity、负无穷大-infinity、正负0等等。number的整数值可以通过函数取得,另外,数值也可以和布尔类型、字符串类型相互转换。
其中后三种数据类型与其它编程语言中相应的数据类型差不多,只是第一种数据类型是XML文档树的特有产物。另外,由于XPath包含的是对文档结构树的一系列操作,因此搞清楚XPath节点类型也是很必要的。由于XML文档的逻辑结构,一个XML文件可以包含元素、CDATA、注释、处理指令等逻辑要素,其中元素还可以包含属性,并可以利用属性来定义命名空间。相应地,在XPath中,将节点划分为七种节点类型:
根节点(Root Node)
根节点是一棵树的最上层,根节点是唯一的。树上其它所有元素节点都是它的子节点或后代节点。对根节点的处理机制与其它节点相同。在XSLT中对树的匹配总是先从根节点开始。
元素节点(Element Nodes)
元素节点对应于文档中的每一个元素,一个元素节点的子节点可以是元素节点、注释节点、处理指令节点和文本节点。可以为元素节点定义一个唯一的标识id。
元素节点都可以有扩展名,它是由两部分组成的:一部分是命名空间URI,另一部分是本地的命名。
文本节点(Text Nodes)
文本节点包含了一组字符数据,即CDATA中包含的字符。任何一个文本节点都不会有紧邻的兄弟文本节点,而且文本节点没有扩展名。
属性节点(Attribute Nodes)
每一个元素节点有一个相关联的属性节点集合,元素是每个属性节点的父节点,但属性节点却不是其父元素的子节点。这就是说,通过查找元素的子节点可以匹配出元素的属性节点,但反过来不成立,只是单向的。再有,元素的属性节点没有共享性,也就是说不同的元素节点不共有同一个属性节点。
对缺省属性的处理等同于定义了的属性。如果一个属性是在DTD声明的,但声明为#IMPLIED,而该属性没有在元素中定义,则该元素的属性节点集中不包含该属性。
此外,与属性相对应的属性节点都没有命名空间的声明。命名空间属性对应着另一种类型的节点。
命名空间节点(Namespace Nodes)
每一个元素节点都有一个相关的命名空间节点集。在XML文档中,命名空间是通过保留属性声明的,因此,在XPath中,该类节点与属性节点极为相似,它们与父元素之间的关系是单向的,并且不具有共享性。
处理指令节点(Processing Instruction Nodes)
处理指令节点对应于XML文档中的每一条处理指令。它也有扩展名,扩展名的本地命名指向处理对象,而命名空间部分为空。
注释节点(Comment Nodes)
注释节点对应于文档中的注释。下面,我们来构造一棵XML文档树:
<A id="a1">
<B id="b1">
<C id="c1">
<B name="b"/>
<D id="d1"/>
<E id="e1"/>
<E id="e2"/>
</C>
</B>
<B id="b2"/>
<C id="c2">
<B/>
<D id="d2"/>
<F/>
</C>
<E/>
</A>
现在,来实现一些利用Xpath使XML中节点匹配的基本方法。
路径匹配
路径匹配与文件路径的表示相仿,比较好理解。有以下几个符号:
符 号
含 义
举 例
匹配结果
/
指示节点路径
/A/C/D
节点"A"的子节点"C"的子节点"D",即id值为d2的D节点
/
根节点
//
所有路径以"//"后指定的子路径结尾的元素
//E
所有E元素,结果是所有三个E元素
//C/E
所有父节点为C的E元素,结果是id值为e1和e2的两个E元素
*
路径的通配符
/A/B/C/*
A元素→B元素→C元素下的所有子元素,即name值为b的B元素、id值为d1的D元素和id值为e1和e2的两个E元素
/*/*/D
上面有两级节点的D元素,匹配结果是id值为d2的D元素
//*
所有的元素
|
逻辑或
//B | //C
所有B元素和C元素
位置匹配
对于每一个元素,它的各个子元素是有序的。如:
举 例
含 义
匹配结果
/A/B/C[1]
A元素→B元素→C元素的第一个子元素
name值为b的B元素
/A/B/C[last()]
A元素→B元素→C元素的最后一个子元素
id值为e2的E元素
/A/B/C[position()>1]
A元素→B元素→C元素之下的位置号大于1的元素
id值为d1的D元素和两个具有id值的E元素
属性及属性值
在XPath中可以利用属性及属性值来匹配元素,要注意的是,元素的属性名前要有"@"前缀。例如:
举 例
含 义
匹配结果
//B[@id]
所有具有属性id的B元素
id值为b1和b2的两个B元素
//B[@*]
所有具有属性的B元素
两个具有id属性的B元素和一个具有name属性B元素
//B[not(@*)]
所有不具有属性的B元素
A元素→C元素下的B元素
//B[@id="b1"]
id值为b1的B元素
A元素下的B元素
亲属关系匹配
XML文档可归结为树型结构,因此任何一个节点都不是孤立的。通常我们把节点之间的归属关系归结为一种亲属关系,如父亲、孩子、祖先、后代、兄弟等等。在对元素进行匹配时,同样可以用到这些概念。例如:
举 例
含 义
匹配结果
//E/parent::*
所有E节点的父节点元素
id值为a1的A元素和id值为c1的C元素
//F/ancestor::*
所有F元素的祖先节点元素
id值为a1的A元素和id值为c2的C元素
/A/child::*
A的子元素
id值为b1、b2的B元素,id值为c2的C元素,以及没有任何属性的E元素
/A/descendant::*
A的所有后代元素
除A元素以外的所有其它元素
//F/self::*
所有F的自身元素
F元素本身
//F/ancestor-or-self::*
所有F元素及它的祖先节点元素
F元素、F元素的父节点C元素和A元素
/A/C/descendant-or-self::*
所有A元素→C元素及它们的后代元素
id值为c2的C元素、该元素的子元素B、D、F元素
/A/C/following-sibling::*
A元素→C元素的紧邻的后序所有兄弟节点元素
没有任何属性的E元素
/A/C/preceding-sibling::*
A元素→C元素的紧邻的前面所有兄弟节点元素
id值为b1和b2的两个B元素
/A/B/C/following::*
A元素→B元素→C元素的后序的所有元素
id为b2的B元素、无属性的C元素、无属性的B元素、id为d2的D元素、无属性的F元素、无属性的E元素。
/A/C/preceding::*
A元素→C元素的前面的所有元素
id为b2的B元素、id为e2的E元素、id为e1的E元素、id为d1的D元素、name为b的B元素、id为c1的C元素、id为b1的B元素
条件匹配
条件匹配就是利用一些函数的运算结果的布尔值来匹配符合条件的节点。常用于条件匹配的函数有四大类:节点函数、字符串函数、数值函数、布尔函数。例如前面提到的last()、position()等等。这些功能函数可以帮助我们精确寻找需要的节点。
函数及功能
作用
count()功能
统计计数,返回符合条件的节点的个数
number()功能
将属性的值中的文本转换为数值
substring() 功能
语法:substring(value, start, length)
截取字符串
sum()功能
求和
这些功能只是XPath语法中的一部分,还有大量的功能函数没有介绍,而且目前XPath的语法仍然在不断发展中。通过这些函数我们可以实现更加复杂的查询和操作。
以上这些匹配方法中,用得最多的还要数路径匹配。依靠给出相对于当前路径的子路径来定位节点的。
XQuery----XML时代的革命性数据查询语言(曾毅)
在以文档和数据为中心的环境里,XML自从诞生之后,迅速得到来自各方技术联盟以及研究机构的支持与关注。现在,许多厂商开发的应用程序都使用XML来传送消息(如SOAP或者XML-RPC消息)或者作为数据的永久性存储(如XML数据库)。在关系型数据库中的数据查询语言SQL,XQuery将成为XML时代的主流数据查询语言。
XQuery概览
自从计算机网络普及后,互联网便成为了人们获取信息的重要手段之一,随着技术的日新月异,人们在互联网上查询数据的需求也愈加强烈,鉴于非结构性数据的查询工作以及关系数据库自身的缺陷,人们需要一种更优化的数据查询语言,XQuery以其良好的设计和强大的功能扮演了这个角色。
XQuery是一个从XML格式的文档中获取数据的查询语言,起源于XML数据查询语言 Quilt。并将Xpath版本2.0作为其子集。Quilt有很多非常优秀的特性,集 SQL, ODMG, XPath1.0, XQL,以及XML-QL的诸多特性于一身,然而随着存储在XML文档中的信息量的增长,对于能高效的存取和查询XML的信息,Quilt显示出了它的不足。于是全新的XQuery数据查询语言诞生了。
XQuery 规范启动于 1998 年由 W3C 发起的查询语言波士顿专题讨论会,与会的成员对XML的使用应当划分为两类,第一类是将 XML 主要作为文档使用的人,第二类是将 XML作为数据使用的人。来自业界、学术界和研究团体的受邀代表利用这个机会发表了各自的看法,阐述了他们认为重要的 XML 查询语言的特性和需求。讨论会之后成立了Query Language Working Group(查询语言工作组)。这个工作组很庞大,由 30 多个成员公司构成,XML 查询语言标准很好地代表了两类使用者的需求和观点。
虽然1998年XQuery规范化工作就已经启动,但是由于XML应用领域的差异,这项工作花费的时间是相当漫长的。直到2001年2月,工作组的发布工作开始大踏步地进行,大量的文档开始推出。在2001年6月和12月、2002年8月和11月、和2003年5月进行了重要更新。在2003年5月加入了一个XQuery序列化和两个全文相关的工作草案后,12个文档基本完成,工作草案正在接近收尾工作。这12份重要的文档包括:
XML Query Requirements(最新版本发布于2003年11月12日)
此文档施工作组的规划文档。XQuery 需求列表。
XML Query Use Cases(最新版本发布于2003年11月12日)
此文档提供了解决特定问题的几个实际方案和 XQuery 代码片段。
XQuery 1.0: An XML Query Language(最新版本发布于2003年11月12日)
此文档是工作草案中的核心文档,介绍语言本身,以及对大多数其他内容的概述。
XQuery 1.0 and XPath 2.0 Data Model(最新版本发布于2003年11月12日)
此文档是XML 信息集的扩展。描述查询实现必须理解的数据项和形式语义的基础。
XQuery 1.0 and XPath 2.0 Formal Semantics(最新版本发布于2003年11月12日)
此文档从形式上定义语言的底层代数。
XML Syntax for XQuery 1.0 (XQueryX) (最新版本发布于2003年12月19日)
此文档为喜欢使用 XML 的人提供的另一种语法。任何地方的机器都可以使用。
XQuery 1.0 and XPath 2.0 Functions and Operators Version 1.0(最新版本发布于2003年11月12日)
此文档描述了Schema 数据类型、 XQuery 节点和节点序列的基本函数和操作符。
XML Path Language (XPath) 2.0 (最新版本发布于2003年11月12日)
此文档是一个单独分离出来的XPath 文档。
XPath Requirements Version 2.0 (最新版本发布于2003年8月22日)
此文档是XPath 的需求文档。
XSLT 2.0 and XQuery 1.0 Serialization(最新版本发布于2003年11月12日)
此文档介绍了XSLT 2.0和XQuery 1.0的详细情况。
XML Query and XPath Full-Text Requirements (最新版本发布于2003年5月2日)
此文档描述了Full-Text Recommendation 需要达到的功能需求。
XML Query and XPath Full-Text Use Cases (最新版本发布于2003年2月14日)
此文档提供了Full-Text 规范预期能够处理的实际情况。
XQuery规范工作草案,至笔者截稿之日最新的版本是:2003年11月12日的版本,可以通过下面的地址获得最新的工作草案 http://www.w3.org/TR/XQuery/。
XQuery的模块结构
XQuery模块包括三个部分:名字空间和模式声明,函数定义,查询表达式。其中模式声明和函数定义不是必需的。
(1)名字空间的定义类似于:namespace xs = “http://www.w3.org/2001/XMLSchema”
(2)函数定义的概念对于程序设计人员并不陌生,下面就是一个函数定义的实例(后面我们还会用到这个例子):
define function depth(element $e) returns xs:integer
{
{-- An empty element has depth 1 --}
{-- Otherwise, add 1 to max_u100 ?epth of children --}
if (empty($e/*)) then 1
else max(for $c in $e/* return depth($c)) + 1
}
头两个部分合在一起叫做query prolog。
(3)查询表达式是模块结构中最重要的部分,例如:
<Everyone>
for $e in document ("Emp.xml")//Name
return <Who> $e </Who>
</Everyone>
XQuery语法与实例
每一个XQuery查询包括一个或多个查询表达式。常用的XQuery语法有:路径表达式(Path Expression),算术表达式与布尔表达式,FLWR表达式,条件表达式(Conditional Expression),元素构造器(Element Constructor),以及函数调用(Function Call)。
路径表达式(Path Expression)
XQuery中, 的路径表达式沿袭了XPath 2.0的语法。它们使用path标记从XML文档中选择感兴趣的节点。在这里其实是把XML文档看作是带有结点的树。路径表达式提供了从结点树中选择结点的方法。下面举一些路径表达式的例子:
? para 对上下文结点的段落(para)元素进行选择
? * 对上下文结点的所有元素进行选择
? text() 对上下文结点的所有文本结点进行选择
? @name 对上下文结点的name属性进行选择
? @* 对上下文结点的所有属性进行选择
? para[1] 对上下文结点的第一个para元素结点进行选择
? para[last()]对上下文结点的最后一个para元素结点进行选择
? */para对上下文结点作为孙子结点的para元素进行选择
? /doc/chapter[5]/section[2]选择doc的第五章节的第二部分
? chapter//para 对上下文结点的孩子结点chapter的孙子结点para元素进行选择
? //para 对所有的文档根结点的孙子结点para以及对做为上下文结点的同一个文档中的para元素进行选择
? //olist/:项在同样的文档中选择所有的项元素作为具有olist双亲结点的上下文结点
? . 选择上下文结点
? .//para 选择作为上下文结点的孙子结点的para元素
? .. 对上下文结点的双亲结点进行选择
? ../@lang 对上下文结点的双亲结点的lang属性 进行选择
? para[@type="warning"] 对所有的具有一个type属性值为warning的作为上下文结点孩子结点的para元素
? chapter[title="Introduction"]选择具有title名为Introduction的chapter.
? chapter[title] 选择出具有一个或多个title的chapter
? employee[@secretary and @assistant] 选择作为上下文结点的孩子结点的所有同时具有secretary属性和assistant属性的 employee。
? book/(chapter|appendix)/section选择book中双亲结点是chapter或者appendix的部分。
? book/xf:ID(publisher)/name 与xf:ID(book/publisher)/name返回同样的结果。
算术表达式与布尔表达式(Arithmetic and boolean expressions):
这可以算是最简单的一部分了,规则与语法几乎同很多程序设计语言都类似,我们不妨通过几个例子来体会一下:
表达式:10 * 5 返回结果50.0
表达式:if (10 * 5 = 50) then 55 else 56 返回结果:55
表达式:
<result>
NumFormat("####", if (10 * 5 = 50) then 55 else 56)
</result>
返回结果:55
表达式:
<result>
NumFormat("###.##", if (10 * 5 = 50) then "55.5555" else "56.4444")
</result>
请注意需要双引号。计算结果将返回:55.56
FLWR 表达式
FLWR(读作“flower”)是XQuery最为有特色且是最为重要的的语法类型之一。它们看上去和SQL的select 语句类似,并且具有相似的功能。FLWR "flower", 代表 for, let, where, 以及 return表达式。 其中:
(1) For语句将一个或多个变量同表达式结合在一起,每一个变量都会被赋予一系列值,这些值正是相应的表达式求得的 ,整个的内容即是这些序列值得笛卡儿乘积 。
(2) Let语句将变量直接与一个完整的表达式绑定在一起。如果for语句存在,由let创建的变量绑定就会加入到由for语句产生的tuple当中。 如果没有for语句,let语句将会产生一个带有所有变量绑定的 tuple。
(3) Where语句适用于由for语句和let语句产生的tuple。
(4) Return语句包含一个被用于生成FLWR表达式结果的表达式。
尽管一个完整的查询语句可以在一行内完成,XQuery仍然采用缩进式程序设计风格来增加可读性。在我们的数据查询中,我们使用缩进式程序设计主要与输出的嵌套遥相呼应。例如输入中的 "for" 语句与输出中的 "for" 语句是在竖排对齐的。当然这是我们的编程风格,你大可保留自己的程序设计风格。
例:for $e in document ("Emp.xml")//Name
return <Who> $e </Who>
上面的例子为每一个<Name>元素返回一个 <Who> 元素。输出的形式如下所示。实际的输出将取决于数据库的具体情况。不同的数据库输出格式将不同。
<Who> <Name> Hari </Name> </Who>
<Who> <Name> John </Name> </Who>
...
<Who> <Name> Leu </Name> </Who>
例:let $e := document ("Emp.xml")//Name
return <Who> $e </Who>
例子返回为由<Name>元素组成的森林,并被包围在一个 <Who>元素中。
<Who>
<Name> Hari </Name>
<Name> John </Name>
...
<Name> Leu </Name>
</Who>
需要注意的是上面的let语句都返回一个正确的 xml 文档。但是 for 语句不然。如果这是用户所希望的,那就很好。但是如果用户希望要得到一个格式整齐的xml文档,那么for查询将可以修改为。
<Everyone>
for $e in document ("Emp.xml")//Name
return <Who> $e </Who>
</Everyone>
上面的表达式将会返回一个正确的xml文档。请注意在这个例子中的标签 <Everyone> 和 <Who> ,是原样返回的。同时需要注意的是为了使return语句正常执行,必须包含一个单一的实体。
<Everyone>
<Who> <Name> Hari </Name> </Who>
<Who> <Name> John </Name> </Who>
...
<Who> <Name> Leu </Name> </Who>
</Everyone>
请注意上面的查询返回的是<Name>元素。如果我们想仅仅显示名字的值而不显示出任何的标签该如何做呢? 我们需要使用text()函数。在上面的查询中,将"//Name" 改为"//Name/text()" ,将"$e"改为"$e/text()"。下面的查询就使用了这种方法。
for $e in document ("Emp.xml")//Name
return $e/text()
这个查询返回了一系列没有标签的名字。这说明XQuery查询是可以根据用户或者应用程序所预期的情况定制的。XQuery是从一种叫做Quilt的语言中发展起来。
Hari
John
...
Leu
这里有另外一个例子。
for $d in distinct(document("Emp.xml")//DName)
let $e := document("Emp.xml")//entry[DName = $d]
return
concat($d/text(), " department has ", numformat("##", count($e)),
" employee(s).")
将会输出下面的结果: Credit department has 1 employee(s).
Toys department has 2 employee(s).
Shoes department has 1 employee(s).
Audit department has 1 employee(s).
条件表达式(Conditional Expression)
在前面的例子中我们已经初步接触了一些if then else语句的用法,下面我们来具体看看他们的语法格式:
if <条件表达式> then <表达式一> else <表达式二>
在条件表达中,条件表达式应当赋值为布尔值,或者是一个能被转换为布尔值的类型。如果值是为真,整个条件表达的结果的值和表达式一的值一样。否则和表达式二一致。下面提供给读者一个简单的例子:
<Report>
for $e in document("Emp.xml")//entry
return
<Entry>
$e/Name,
<ProposedSal> if ($e/Salary .>=. 60000) then 100000 else 200000</ProposedSal>
</Entry>
</Report>
元素构造器(Element Constructor)
在写查询语句的时候,你可能需要创建新的元素作为输出的一部分。元素构造器便提供这样的功能。一个简单的元素构造的例子如下:
<newElement>XQuery Language</newElement>
新元素的内容可以是指定的,也可以是计算产生的。
函数调用(Function Call)
这里函数的概念和其他程序设计语言中的函数概念是完全相同的。我们来看一个例子:
下面的函数返回XML文档的最大深度。
随后是一个计算partslist.xml 文档深度的调用。
define function depth(element $e) returns xs:integer
{
{-- An empty element has depth 1 --}
{-- Otherwise, add 1 to max depth of children --}
if (empty($e/*)) then 1
else max(for $c in $e/* return depth($c)) + 1
}
depth(document("partlist.xml"))
高级话题与应用
求和
在XQuery当中,求和通过let和sum可以很简便的完成,在SQL中,要使用"group by"与"having"语句。但是在SQL当中,"group by"和"having" 只能和一个from语句对应,而在XQuery当中可以使用多个let语句。下面的例子是按照部门返回工资总额。
<DeptPayrolls>
for $d in distinct(document("Emp.xml")//DName)
let $s := document("Emp.xml")//entry[DName = $d]/Salary
return
<DeptPayroll>
$d,
<SumSal> sum($s) </SumSal>
</DeptPayroll>
</DeptPayrolls>
如果想得到大于等于70000的工资,我们应当做下面的查询。
<SpecialDepartments>
for $d in distinct(document("Emp.xml")//DName)
where every $s in document("Emp.xml")//entry[DName = $d]
satisfies ($s/Salary >= 70000)
return
<DeptAvg>
$d,
<AvgSal> avg(document("Emp.xml")//entry[DName = $d]/Salary) </AvgSal>
</DeptAvg>
</SpecialDepartments>
值得注意的最重要一点是。在Kweelt当中我们使用".>=." 而不是使用 ">="e uses ".>=."。最大的障碍是Kweelt不支持数词。因此 "every x satisfies c(x)" 在Kweelt不起作用。
设想一个等价表达式"not(some x satisfies not(c(x))" 尽管这样还是含有数词,但是它提供给我们一种间接的方式:首先我们在$s中收集满足not(c(x))的元素x,在where从句中使用"not exists ($s)" 来过滤掉已经通过for语句但是不符合这个要求的部分。剩余的部分就是所需的,并且将在return语句执行后被释放出来。因此我们得出了下面的查询。
<SpecialDepartments>
for $d in distinct(document("Emp.xml")//DName)
let $s := document("Emp.xml")//entry[DName = $d and Salary .<. 70000]/Salary
where not(exists($s))
return
<DeptAvg>
$d,
<AvgSal> avg(document("Emp.xml")//entry[DName = $d]/Salary) </AvgSal>
</DeptPayroll>
</SpecialDepartments>
这同时也说明了SQL语法的不完善性,计算机科学技术系的学生必须十分重视对这个的认识。当一个事情被标准化后会引来多大的麻烦。而XQuery在最开始的时候就被设计的非常好。Kweelt 的不完善性可能源于预标准化版本的XQuery。
排序
排序是被默认执行的。如下例所示:
for $i in (1, 2),
$j in (3, 4)
return
<tuple>
<i>{ $i }</i>
<j>{ $j }</j>
</tuple>
结果输出的格式如下,顺序是非常重要的。
<tuple> <i>1</i> <j>3</j> </tuple>
<tuple> <i>1</i> <j>4</j> </tuple>
<tuple> <i>2</i> <j>3</j> </tuple>
<tuple> <i>2</i> <j>4</j> </tuple>
当然,我们为了效率也可以改变顺序,如下例所示:输出的结果与上面类似,但是顺序可能是不一样的,系统将进行优化。
for $i in unordered(1, 2),
$j in unordered(3, 4)
return
<tuple>
<i>{ $i }</i>
<j>{ $j }</j>
</tuple>
重构
考虑下面的一个例子,这个例子是关于书的,每一本书包含作者和出版商的信息。
<bib>
<book>
<title>TCP/IP Illustrated</title>
<author>W. Stevens</author>
<publisher>Addison-Wesley</publisher>
</book>
<book>
<title>Advanced Programming in the Unix environment</title>
<author>W. Stevens</author>
<publisher>Addison-Wesley</publisher>
</book>
</bib>
下面的表达式用来将输入转换成一个作者列表:
<authlist>
{
let $input := document("bib.xml")
for $a in distinct-values($in//author)
return
<author>
{
<name>
{ $a/text() }
</name>,
<books>
{
for $b in $input//book
where $b/author = $a
return $b/title
}
</books>
}
</author>
}
</authlist>
可以发现:作者表中列出的是作者的姓名,每一个作者元素都包括了作者的姓名以及作者的著作名称,其中使用了distinct-values来防止输出顺序遭破坏。
下面是表达式输出的结果:
<authlist>
<author>
<name>W. Stevens</name>
<books>
<title>TCP/IP Illustrated</title>
<title>Advanced Programming in the Unix environment</title>
</books>
</author>
</authlist>
进行数据查询程序编写的程序员应当谨慎将FLWR表达式同路径表达式组合使用。最为重要的是你必须清除路径表达式的返回结果是按照文档顺序的,而FLWR 表达式的返回结果是由for语句决定的。
查找
下面的例子列出了价格大于100的书,按照第一作者的名字来查找,每个作者又按照标题来查找。
//book[price > 100] sortby (author[1], title)
排序可以产生多级查找结果,下面的查询返回了一个按照字母顺序排出的出版商列表,在每一个出版商元素中包含了若干的书元素,每一个数元素都包含书的名称和价格,而且是按照降序排列的。
<publisher_list>
{for $p in distinct-values(document("bib.xml")//publisher)
return
<publisher>
<name> {$p/text()} </name>
{for $b in document("bib.xml")//book[publisher = $p]
return
<book>
{$b/title}
{$b/price}
</book>
sortby(price descending)
}
</publisher>
sortby(name)
}
</publisher_list>
需要注意的是:sortby如果在路径表达式中使用sortby将会造成不可预期的结果。
下面我们再来分析下面的例子:
(employees sortby (salary))/name
你可能会觉得返回的查询结果一定是按照雇员的收入排列的雇员的名字。但是,路径表达式总是返回按照文档顺序的结点序列。因此,这个查询的结果是按照文档序列排列的雇员名列表。如果希望得到的名字序列是按照工资顺序输出的,查询的方法就应当如下所示:
for $e in (employees sortby (salary)) return $e/name
过滤
函数filter($doc//(A | B))返回一个森林。
? 从$doc中过滤掉不是A和B类的结点。
? 保护A类结点和B类结点的关系。
执行过滤前的:$doc 执行过滤后的:filter($doc//(A | B))
下面的例子就用了过滤来返回cookbook.xml文件的目录。
<toc>
{
filter(document("cookbook.xml") //
(section | section/title | section/title/text()))
}
</toc>
在序列中的查询
XQuery使用precedes,follows,<< 和 >>操作符来表示序列关系。下面的这部分查询包含了一个外科手术报告,包含了流程(procedure),开刀(incision),以及麻醉元素(anesthesia)。
下面的查询返回在第一个流程的第一个手术和第二个手术的元素和结点序列。
<critical-sequence>
{
let $proc := //procedure[1]
for $n in $proc//node()
where $n follows ($proc//incision)[1]
and $n precedes ($proc//incision)[2]
return $n
}
</critical-sequence>
SPJ查询: 雇员与管理人员的名字
这里有一个例子:
<EmpAndManagers>
for $e in document ("Emp.xml")//entry
for $m in document("Dept.xml")//entry
where $e/DName = $m/DName
return
<EmpManager>
$e/Name,
$m/MName
</EmpManager>
sortby(Name descending)
</EmpAndManagers>
查询将会返回下面的xml文档 document。
<EmpAndManagers>
<EmpManager>
<Name> Mary </Name>
<MName> Mary </MName>
</EmpManager>
<EmpManager>
<Name> Leu </Name>
<MName> Leu </MName>
</EmpManager>
<EmpManager>
<Name> John </Name>
<MName> Mary </MName>
</EmpManager>
</EmpAndManagers>
下面是进行查询的另外一种书写方法。
<EmpAndManagers>
for $e in document ("Emp.xml")//entry
for $m in document("Dept.xml")//entry[DName = $e/DName]/MName
return
<EmpManager>
$e/Name,
$m
</EmpManager>
sortby(Name descending)
</EmpAndManagers>
如果我们考虑用let语句来代替第二个for语句应当如何做呢?我们来看下面的查询。
<EmpAndManagers>
for $e in document ("Emp.xml")//entry
let $m := document("Dept.xml")//entry[DName = $e/DName]/MName
return
<EmpManager>
$e/Name,
$m
</EmpManager>
sortby(Name descending)
</EmpAndManagers>
在前两个带有for语句嵌套的查询当中,里面的那个for语句没有发现任何的匹配的部门,对于这个雇员,什么也没有报告出。现在在这种原来的第二个地方使用let语句的情况下,变量将会与空的manager榜定。这不是一个失败操作,空的manager将被报告。结果如下显示。
请注意没有manager的employee也被列出来了,但是由于这样的employee在部门关系中没有匹配的元素。因此,<Manager>元素将被简单的省略掉。当部门关系中没有manager的时候会是什么样呢?在这种情况下,将会是这种结构"<Manager> </Manager>" 这个元素也将出现在查询结果当中。
<EmpAndManagers>
...
<EmpManager>
<Name> John </Name>
<Manager> Mary </Manager>
</EmpManager>
<EmpManager>
<Name> Hari </Name>
</EmpManager>
</EmpAndManagers>
没有manager的信息可以通过下面的方法过滤掉:
<EmpAndManagers>
for $e in document ("Emp.xml")//entry
let $m := document("Dept.xml")//entry[DName = $e/DName]/MName
return
if (exists($m)) then
<EmpManager>
$e/Name,
$m
</EmpManager>
else ""
sortby(Name descending)
</EmpAndManagers>
用类似的方法我们可以将具有manager 的employee信息过滤出来,可以使用下面的语句:"not(exists($m))"。
需要注意的是当与SQL语言比较时,XQuery提供了更多的选择。当用户对于如何进行一个查询有了初步的想法时,基本就会成功了。还需要注意否定似乎变得更为对称。在SQL中进行的子查询只要运用let表达式就可以完成了。
按照部门重组Emp.xml 文档
Employee记录是按照<Name> 元素组织的。即使在部门中可能有多个雇员,在xml 当中这样做并不复杂。下面的例子展示了如何用查询的方法完成上面的功能。
<DeptsAndEmps>
for $d in distinct(document("Emp.xml")//DName)
return
<DeptList>
$d,
<EmpsList>
for $e in document("Emp.xml")//entry[DName = $d]
return
<NameSal> $e/Name, $e/Salary </NameSal>
</EmpsList>
</DeptList>
</DeptsAndEmps>
查询的结果如下所示:
<DeptsAndEmps>
<DeptList>
<DName> Credit </DName>
<EmpsList>
<NameSal> <Name> Hari </Name> <Salary> 55000 </Salary> </NameSal>
</EmpsList>
</DeptList>
<DeptList>
<DName> Toys </DName>
<EmpsList>
<NameSal> <Name> John </Name> <Salary> 80000 </Salary> </NameSal>
<NameSal> <Name> Mary </Name> <Salary> 90000 </Salary> </NameSal>
</EmpsList>
</DeptList>
...
</DeptsAndEmps>
XQuery与SQL 的比较
首先我们来看两个例子:
SQL
SELECT pno
FROM p
WHERE descrip LIKE ’Gear%’
ORDER BY pno;
SELECT pno, avg(price) AS avgprice
FROM sp
GROUP BY pno
HAVING count(*) >= 3
ORDER BY pno;
XQuery
for $p in document(”p.xml”)//p_tuple
where starts-with($p/descrip, ”Gear”)
order by $p/pno
return $p/pno
for $pn in distinct-values(
document(”sp.xml”)//pno)
let $sp:=document(”sp.xml”)//sp_tuple[pno=$pn]
where count($sp) >= 3
order by $pn
return
<well_supplied_item> {
$pn,
<avgprice> {avg($sp/price)} </avgprice>
} <well_supplied_item>
通过上面两个例子我们可以看出,二者既有类似之处,又有很多不同,作为两种主流的查询语言,我们可以对两者进行一个比较:
SQL
是对关系型数据库的操作
模式(Schema)是已知的,并且已经有相应的策略。
使用select, from, where, group by以及having语句命令
关系属性值
Tuple
完整的XML 文档
用tuple变量代替tuple
在每个select语句中只能搭配一个group变量
在where语句过滤出tuple后,group by 和having 语句方可处理group。
仅仅是单一的属性子查询能够被用于where语句
语法形式纷繁复杂
仅仅返回一个关系
Select语句返回简单的值
XQuery
是对XML文档的操作
模式不是必须的。在不久的未来模式可以被用于验证和进行XQuery查询。
使用for-let, where和return表达式对应于SQL中的from, where和select语句。在XQuery中是不需要group by以及having语句的。
单一的XML元素
For变量代表的是一个元素(一个子树)let变量代表的是一系列元素(一个森林)可以有多个for和let变量
where语句可以被用于过滤由for和let变量定义的一个元素或者一系元素。
Let变量可以被用于创建任意数目的类似分组机制。
在XQuery中没有这样的要求。在XQuery中,可以在你需要使用子查询的任何时候使用它
简洁清晰的语法结构。合乎普通语法的结构都是可用的。还包括了,if then else,聚集和数词
可以返回xml文档甚至是简单的文本文档
Return语句允许元素被调整成任意的结构作为结果输出。
XQuery的实现与应用
XQuery作为一种全新的数据查询语言,已经被众多的科研机构与企业所接受和采纳。目前微软、IBM和Oracle公司数据库核心都是采用的XQuery标准。
IBM的Xperanto-based Information Integrator集成了XML、XQuery、文件搜索技术及Web Services技术。可让人们在关系数据库、XML文件、一般文件、电子表格及其他数据源中查询数据,有如在单一数据库中。
BEA推出的Liquid Data是以XML和XQuery为基础的引擎及可视化工具,可以将不同地点的数据源连接起来,包括表格、文件、影音资料。可以加速企业內部、企业与合作夥伴间的信息整合与集成。
Oracle在2002年3月发表Java XQuery的原型标准,现在我们可以通过下面的地址获得一个最新下载:到本文完稿时最新版本为 RELEASE_0.2_030217,这是一个技术预览版。http://otn.oracle.com/sample_code/tech/xml/xmldb/xmldb_XQuerydownload.html其中包含一个带有 Oracle XQuery 原型(在一个 jar 文件中)和 java 文档的 jar 文件。一旦安装完成,就可以使用 Java API (OJXQI) 或命令行实用工具来测试该原型。
运行这个原型还需具备一些最基本的环境,如XDK,最低版本需要XDK 9i production release - XDK 9.2.0.1.0。另外就是JDK,最低版本为JDK 1.3,如果要访问数据库还要用到Oracle XSU以及JDBC驱动。
解压缩XQuery文件后,需要设置环境变量,我们假设XQuery文件夹背解压缩到C盘根目录,那么对环境变量的操作如下:
CLASSPATH=C:\XQuery\XQuery.jar;C:\XDK\lib\xmlparserv2.jar;
如果设置完全没有问题,你现在就应当可以进行一个XQuery查询了。
由于Oracle官方下载包XQuery原型中的样例exmpl1.xql看起来似乎有些问题,我们在此基础之上作一些修改,得到了下面的一个测试程序:
<bib>
{
FOR $b IN document("bib.xml")/bib/book
WHERE $b/publisher = "Addison-Wesley" AND $b/@year > 1991
RETURN
<book>
{$b/@year}
{$b/title}
</book>
}
</bib>
切换到命令行状态下,进入解压缩XQuery.jar的文件夹,在命令行下我们输入:java oracle.XQuery.XQLPluse exmpl2.xql便会从命令行中返回下面的查询结果:
另外还有一种交互式的方法,笔者感觉设计的还不完善,所以不作为介绍内容。如果想了解可以参考Oracle的在线文档。
该技术的特色是实现了W3C的XQuery标准再加上Oracle的扩展功能,且着重支持关系数据库及XQuery用例(XQuery use cases),另一特色是包含一個实验性质的JDBC式的Java API供XQuery使用,並像SQL的函数类似,可以在SQL的查询结果上使用XQuery。Oracle的目标是提供兼具SQL风格及XQuery-based的查询语法,以便查询Oracle数据库中的XML数据。Oracle 9i也支持XML schema,可用XML格式存取原本关系数据库中的数据,成为Native XML数据库。最新的Oracle 10g也进一步加强了对XQuery的支持。
Microsoft早在2001年5月就推出的XML查询语言演示 (XML Query Language Demo)网,你可以通过下面的地址访问到:(http://131.107.228.20/)。你可以使用上面的例子来进行查询演示,也可以自己来编写XQuery查询程序。我们下面来看一下一个具体的示范查询:
在SQL Server 2000 中Microsoft提供了对XML支持。而在即将推出的代号为Yukon的新版SQL Server中不但增强了对XML的支持,用于管理和存储结构和非结构化的数据。而且还加入和对XQuery查询能力的深度支持,允许XML对象的数据层计算。同时还提供了创建XQuery查询程序的一套新的工具集。
参考文献:
下面提供出来的不但有本文成文时参考的一些材料,还有一些是我希望推荐给大家的极其珍贵的学习材料,希望能够对读者学习XQuery有所帮助:
? XQuery from the Experts: A Guide to the W3C XML Query Language, Addison Wesley
? XML Query Use Cases, W3C Working Draft, (http://www.w3.org/TR/xmlquery-use-cases)
? XML Query Requirements, W3C Working Draft, (http://www.w3.org/XML/Group/xmlquery/xmlquery-req)
? XQuery 1.0 and XPath 2.0 Data Model, W3C Working Draft(http://www.w3.org/TR/query-datamodel/)
? XQuery 1.0 and XPath 2.0 Formal Semantics, W3C Working Draft (http://www.w3.org/TR/query-semantics/)
? XQuery 1.0 and XPath 2.0 Functions and Operators Version 1.0, W3C Working Draft (http://www.w3.org/TR/XQuery-operators/)
? XQuery 1.0: An XML Query Language, W3C Working Draft(http://www.w3.org/TR/XQuery/)
? XML Syntax for XQuery 1.0 (XQueryX), W3C Working Draft(http://www.w3.org/TR/XQueryx)
? Howard Katz: An introduction to XQuery:
(http://www-106.ibm.com/developerworks/xml/library/x-XQuery.html)
? Howard Kaze, Don Chamberlin, Denise Draper, Mary Fernandez, Michael Kay, Jonathan Robie, Michael Rys, Jerome Simeon, Jim Tivy, and Philip Wadler : XQuery from the Experts: Influences on the design of XQuery(http://www-106.ibm.com/developerworks/xml/library/x-xqbook.html)
? Don Chamberlin: XQuery: An XML query language