更新XQuery XQuery有了一些新特性,内容包括从原子化到跟踪文件结构。
在"你所不了解的XQuery"(Oracle杂志,2003年5/6月刊)一文中,我介绍了XQuery,它是一项由万维网联盟(W3C)开发的技术,设计用来查询和操纵XML数据或任何能以XML形式出现的数据,如关系型数据库。那篇引文讨论了2002年11月发布的XQuery草案规范。2003年5月,W3C发布了新的XQuery草案规范,本文追踪报道了5月份发布的草案规范中最令人感兴趣的变化和新增加的特性,其中包括库模块、序(prolog)变量、外部函数以及用于调试、错误处理和格式化的新函数。
变化 5月草案增加了大量新特性,但是我首先讨论对现有特性所做的更改。有些更改是表面上的。 例如,document()输入函数(该函数使用给定的统一资源标识符[URI]返回一个文档)被改为一个新的更短的名字doc()。另外,曾经被写成{- comment -}的注释现在改用新的笑脸符号(: comment :)。是的,现在每个注释都成了一个笑话。
有些更改则是更根本的。也许最重大的改动就是distinct-values()函数不再返回节点(节点是XML结构,如元素、文档、注释以及文本节点),它只返回原子值(如整数或字符串)。尽管该函数仍然接受节点和原子值,但只返回原子值。任何进入该函数的节点都会被"原子化"并被当作原子值,然后以原子形式返回。
原子化的规则很复杂,这里给出一些基本的:由模式定义为布尔型的元素将被原子化为true/false布尔值。定义为整型的元素将被原子化为一个整数。没有被模式定义的元素将被原子化为节点的XPath字符串值(文本节点递归地连接在一起)。
为了说明: distinct-values(<item>apple</item>,
<item>banana</item>, "grape")
返回值("apple","banana","grape"),假设在模式中没有声明 。在下面的例子中,如果我们假设 被模式定义为布尔型,那么下面的语句:
distinct-values(<status>0</status>,
<status>false</status>)
返回false(),因为它是两个元素的原子值。记住false()是XQuery常量,表示"假"。
现在你也许会想,"当我想返回节点时,可以使用distinct-nodes()函数。" 是的,但该函数只能根据节点标识删除重复节点(那些完全相同的节点,类似于Java中引用的等效节点)。没有能删除等效节点的函数。这会使查询变得复杂,因为没有办法能轻松地删除等效节点。
回过头来看我以前的那篇文章"你所不了解的XQuery",你将发现有些示例会受到这一改动的影响。在那篇文章中,下面的查询返回了艺术家名字的惟一列表,其中每一个名字前后都带有 标记:
distinct-values(document("itunes.xml")
/itunes/Tracks/Track/Artist)
示例输出类似于:
<Artist>Marc Cohn</Artist>
<Artist>Pink Floyd</Artist>
现在,执行同样的查询则返回原子值:
Marc Cohn
Pink Floyd
由于distinct-values()去掉了 标记(这是原子化过程的一部分),所以你必须在完成distinct-values()调用后添加标记,如下所示:
let $artists :=
distinct-values(doc("itunes.xml")
/itunes/Tracks/Track/Artist)
for $a in $artists
return <Artist>{ $a }</Artist>
不是每种情况都是这么轻松地得到处理。看一下W3C 使用案例文档中的示例1.1.9.4在2002年11月版与2003年5月版中是如何变化的。该示例返回每位作者的著作列表。它使用了distinct-values(),根据2002年11月的规范,它的代码如下:
<results>
{
for $a in distinct-values(
document("http://www.bn.com/bib.xml")
//author)
return
<result>
{ $a }
{
for $b in document(
"http://www.bn.com/bib.xml")
/bib/book
where some $ba in $b/author
satisfies deep-equal($ba,$a)
; return $b/title
}
</result>
}
</results>
根据查询结果你不能直接分辨出姓和名,但每个 元素都由一个 和 名组成。distinct-values()调用返回具有惟一名字的 元素列表。对于2003年5月的规范,现在查询必须在姓和名上单独运行distinct-values(),而且在嵌套的FLWOR表达式中也没有将$a指定为惟一的作者:
<results>
{
let $a :=
doc("http://www.bn.com/bib/bib.xml")
//author
for $last in distinct-values($a/last),
$first in distinct-values(
$a[last=$last]/first)
return
<result>
{ $last, $first }
{
for $b in
doc("http://www.bn.com/bib.xml")
/bib/book
where some $ba in $b/author
satisfies ($ba/last = $last and
$ba/first=$first)
return $b/title
}
</result>
}
</results>
除了编写用户定义的distinct-deep-equal()外,没有更好的方法来完成这件事了,而该方法在纯XQuery中不能执行。(注:FLWOR(发音为"flower")表达式是XQuery的构建模块。这个名字来源于组成表达式的关键词For、Let、Where、Order by和Return}。
新函数 2003年5月的XQuery规范草案增加了三个新函数,它们肯定会非常有用。第一个是:
trace($value as item()*,
$label as xs:string) as item()*
trace()函数允许在查询的中间进行printf风格的调试。该函数有两个参数:要显示的值(可以是任意多个项的序列)以及要显示的这个值的字符串标签。为方便起见,函数返回$value传递的值。trace()输出的位置由你的引擎来决定。
该函数使你能够详细查看查询的内部过程。例如,下面的查询根据文档名返回在XQuery引擎中存储的所有文档的URI。通过增加一个trace()调用,我能够在排序前查看返回的每个URI:
define function uris() as xs:string* {
for $n in input()
return trace(
xs:string(document-uri($n)), "base:")
}
for $u in uris() order by $u return $u
输出结果可能如下:
2003-08-01 14:40:46 base: census.xml
2003-08-01 14:40:46 base: ipo.xml
当使用trace()和其他类似的函数时,记住在XQuery中每项内容都是一个表达式。没有语句!为了能够完成类似语句的操作,你可以采用在表达式之间加逗号的方法来创建一个序列。然后,单独计算每个表达式的值。在最终的结果序列中忽略所有返回空值的表达式。例如,下面是两个不影响结果的trace()调用:
trace((), "starting query"),
let $time := current-dateTime()
let $ignored := trace($time, "Got time")
return
<html>
<head></head>
<body>Current time is { $time }</body>
</html>
请注意第一个trace()调用后面的逗号。它使查询返回一个具有两个项的序列,第一个trace()调用的结果为空,因而被忽略。一般人不了解这一情况,但高级查询实际上就是返回一个序列,因此在这种特殊情况下两端的括号不是必需的。查询"5, "是完全正确的。此外,在这个示例中你会看到,当编写一个FLWOR表达式时,你可以执行let子句右侧的任意代码,并且忽略该值。
5月份的草案还增加了error()函数:
error($srcval as item()?)
这个函数使用户能够报告一个错误,类似于抛出一个异常。$srcval被定义为item()?,意味着它可以是一个XML结构或原子,并且加上问号标记表示这是可选的。下面是一些示例应用:
error()
error("Missing source document")
error(<span>A <i>beautifully</i>
formatted error</span>)
error()调用就像发生异常时那样展开堆栈。遗憾的是,XQuery仍没有try/catch功能。因此,尽管你能抛出错误,但却不能从中恢复。
在5月份的草案中最后一个引人注目的新增函数有一个奇怪的名字:round-half-to-even()。它有两种形式:
round-half-to-even($srcval as numeric?)
as numeric?
round-half-to-even($srcval as numeric?,
$precision as xs:integer) as numeric?
在有一个参数的情况下,它的行为类似于round()函数,只是当一个数恰好落在其他两个数的中间时,它将参数取整为最接近的偶数值。数字理论家们会告诉你从统计学上讲这是一个更精确的取整算法。举例说明:
round-half-to-even(1.5) = 2.0
round-half-to-even(2.5) = 2.0
round-half-to-even(2.51) = 3.0
有第二个参数的情况使函数变得很有趣。第二个参数表示精确级,并允许将函数用于格式化小数值。例如:
round-half-to-even(3.567, 2) = 3.57
round-half-to-even(1113.567, -2) = 1100.0
round-half-to-even(1 div 9, 3) = 0.111
在声明中使用的数字数据类型是xs:decimal、xs:integer、xs:float、xs:double以及任何根据限制由它们导出的类型的一种简单表示。它用于XQuery规范中,但你不能在自己的查询中使用它。
<