3.8 FLWOR表达式FLWOR Expressions
XQuery提供名为FLWOR表达式的特性,支持迭代及变量与中间结果的绑定。这类表达式对连接2个或多个文档的计算和重构数据往往有用。名称FLWOR发“flower”的音,暗示关键字for, let, where, order by, 和 return.
[28]
FLWORExpr
::=
(ForClause | LetClause)+ WhereClause? OrderByClause? "return" ExprSingle
[29]
ForClause
::=
"for" "$" VarName TypeDeclaration? PositionalVar? "in" ExprSingle ("," "$" VarName TypeDeclaration? PositionalVar? "in" ExprSingle)*
[31]
LetClause
::=
"let" "$" VarName TypeDeclaration? ":=" ExprSingle ("," "$" VarName TypeDeclaration? ":=" ExprSingle)*
[109]
TypeDeclaration
::=
"as" SequenceType
[30]
PositionalVar
::=
"at" "$" VarName
[32]
WhereClause
::=
"where" Expr
[33]
OrderByClause
::=
("order" "by" | "stable" "order" "by") OrderSpecList
[34]
OrderSpecList
::=
OrderSpec ("," OrderSpec)*
[35]
OrderSpec
::=
ExprSingle OrderModifier
[36]
OrderModifier
::=
("ascending" | "descending")? (("empty" "greatest") | ("empty" "least"))? ("collation" StringLiteral)?
FLWOR表达式中的for 和 let 子句生成一个绑定变量的元组的有序序列,称为元组流(tuple stream)。可选的where子句用来过滤元组流,保留某些元组并删除其他的。可选子句order by可用来记录元组流。return子句构造FLWOR表达式的结果。return子句在where子句过滤后,使用在相应元组中绑定的变量为每一个元组流中的元组计算一次。FLWOR表达式的结果是一个包含这些计算的连接结果的有序序列。
下面是一个FLWOR表达式的例子,包含了所有可能的子句。for子句对输入文档中所有部门(department)进行迭代,将变量$d依次与每个部门编号进行绑定。对每个$d的绑定,let子句将$e变量与给出部门的从另一个输入文档中选定的所有雇员(employees)绑定。For子句和let子句的结果是一个元组流,其中每个元组包含一对$d和$e的绑定($d与部门编号绑定,$e与那个部门中一组雇员绑定)。where子句过滤元组流,保留那些表示部门至少有十个雇员的绑定对(binding-pairs)。order by子句按照部门中雇员平均薪水的降序排序继续存在的元组。return子句为每个继续存在的元组构造一个新的元素big-dept包含部门编号、职员总数和平均薪水。
for $d in fn:doc("depts.xml")//deptno
let $e := fn:doc("emps.xml")//emp[deptno = $d]
where fn:count($e) >= 10
order by fn:avg($e/salary) descending
return
<big-dept>
{
$d,
<headcount>{fn:count($e)}</headcount>,
<avgsal>{fn:avg($e/salary)}</avgsal>
}
</big-dept>
下面有FLOWOR表达式中的子句更多的说明。
3.8.1 For和Let子句For and Let Clauses
FLWOR表达式中for 和 let子句的用途是产生一个元组流,其中每个元组包含一到多个绑定变量。
最简单的for子句的例子包含一个变量和一个关联的表达式。我们称表达式的值为输入序列(input sequence)。for子句迭代输入序列中的数据项,依次将变量与每个数据项绑定。如果排序模式(ordering mode)为ordered,则变量绑定的结果序列按照输入序列的值的次序排序;否则,变量绑定的次序为实现相关的(implementation-dependent)。
一个for子句也可能包含多个变量,每个变量有一个关联表达式,其值为那个变量的输入序列(input sequence)。在这种情况下,for子句迭代每个变量到其输入序列。结果元组流对每一个值与输入序列的笛卡儿乘积的组合包含一个元组。如果排序模式(ordering mode)为ordered,则元组流的次序主要由最左边的变量的输入序列的次序决定,再从左到右由其他变量的输入序列决定。否则,变量绑定的次序为实现相关的(implementation-dependent)。
一个let子句也可能包含一个或多个变量,每个变量有一个关联表达式。然而,与for子句不同的是,一个let子句将每个变量与其关联表达式的结果绑定,并不迭代。由let子句生成的变量绑定加到由for子句生成的绑定元组上。如果没有for子句,let子句生成一个包含所有变量绑定的元组。
虽然for子句和let子句都绑定变量,但变量绑定的方式完全不同,与下例说明的一样。第一个例子使用了一个let子句:
let $s := (<one/>, <two/>, <three/>)
return <out>{$s}</out>
变量$s与表达式(<one/>,<two/>,<three/>)的结果绑定。由于没有for子句,let子句生成一个包含$s的绑定的元组。return子句调用这个元组,创建下列输出:
<out>
<one/>
<two/>
<three/>
</out>
下一个例子是一个简单的查询,包含一个for子句而不是let子句:
for $s in (<one/>, <two/>, <three/>)
return <out>{$s}</out>
本例中,变量$s迭代给定的表达式。如果排序模式(ordering mode)为ordered,$s先和<one/>绑定,然后和<two/>,最后和<three/>绑定。这些绑定每个产生一个元组,return子句调用每个元组,创建下面的输出:
<out>
<one/>
</out>
<out>
<two/>
</out>
<out>
<three/>
</out>
下例说明当排序模式(ordering mode)为ordered时,绑定元组如何由一个包含多个变量的for子句生成。
for $i in (1, 2), $j in (3, 4)
由以上for子句生成的元组流如下:
($i = 1, $j = 3)
($i = 1, $j = 4)
($i = 2, $j = 3)
($i = 2, $j = 4)
如果排序模式(ordering mode)为unordered,上例中的for子句将生成同样的元组流,但是元组次序将为实现相关的(implementation-dependent)。
for子句或者let子句中变量绑定的作用域,包括所属FLWOR表达式中变量绑定后的所有子表达式。作用域不包括变量所绑定的表达式。下例说明for和let子句会怎样引用同一个FLWOR表达式里较早的子句中绑定的变量:
for $x in $w
let $y := f($x)
for $z in g($x, $y)
return h($x, $y, $z)
每个for或者let子句中绑定的变量会有一个可选的类型声明(type declaration),这是一个使用2.4.3 SequenceType Syntax中的语法声明的类型。依照序列类型匹配(SequenceType matching)规则,如果绑定到变量的值的类型与声明的类型不匹配,将引发一个类型错误(type error)。 [err:XP0004][err:XP0006] 例如,下面的表达式引发一个类型错误(type error),因为变量$salary有一个类型声明没有被与变量绑定的值满足:
let $salary as xs:decimal := "cat"
return $salary * 2
for子句中绑定的每一个变量会被同时绑定一个相关的位置变量(positional variable)。位置变量的的名字前面是关键字at。位置变量总是有一个隐含类型xs:integer。当一个变量迭代输入序列中的数据项时,其位置变量迭代输入序列中代表数据项次序位置的整数,从1开始。
下面的for子句说明位置变量:
for $car at $i in ("Ford", "Chevy"),
$pet at $j in ("Cat", "Dog")
如果排序模式(ordering mode)为ordered,由以上for子句生成的元组流如下:
($i = 1, $car = "Ford", $j = 1, $pet = "Cat")
($i = 1, $car = "Ford", $j = 2, $pet = "Dog")
($i = 2, $car = "Chevy", $j = 1, $pet = "Cat")
($i = 2, $car = "Chevy", $j = 2, $pet = "Dog")
如果排序模式(ordering mode)为unordered,结果元组流以实现相关(implementation-dependent)的次序包含同样的元组。
3.8.2 Where子句 Where Clause
可选的where子句为for和let子句生成的变量绑定的元组做过滤器服务。where子句中的表达式称为where表达式(where-expression),为其中每个元组计算一次。如果where表达式的有效布尔值(effective boolean value)为true,元组被保留并且其变量绑定被用于一个return子句的执行。如果where-expression的有效布尔值(effective boolean value)为false,则元组被删除。表达式的有效布尔值(effective boolean value)在2.3.3 Effective Boolean Value中定义。
下面的表达式说明where子句会如何被应用于位置变量(positional variable),以完成一个输入序列的采样。这个表达式通过从每一百个输入值中抽取一个样品的方法,来约计一个序列中的平均值。
fn:avg(for $x at $i in $inputvalues
where $i mod 100 = 0
return $x)
3.8.3 Order By和Return子句Order By and Return Clauses
FLWOR表达式的return 子句为元组流中的每一个元组计算一次,这些计算结果连接形成FLWOR表达式的结果。
如果没有order by子句,元组流的次序由for 和 let子句根据排序模式(ordering mode)决定。如果存在order by子句,它以一个新的,基于值的次序排序元组流中的元组。不论发生哪种情况,由此得到的次序确定return子句使用相应元组中的变量绑定每次每个元组计算的次序。注意如果有order by子句存在,则排序模式(ordering mode)在FLWOR表达式中无效,因为的order by优先级比排序模式(ordering mode)高。
正如上面语法中展示的那样,一个order by 子句包含一个或者多个排序规格说明,称为orderspecs。对于元组流中的每一个元组,使用元组中绑定的变量计算orderspec。两个元组的次序关系由他们orderspec的值比较决定,从左到右比较,直到遇到一对不等的值。如果进行比较的值是字符串,orderspec指出要使用的校对(collation)(如果没有指定校对,则使用默认的校对。)
计算和比较orderspec的过程基于下列规则:
每个orderspec中表达式的结果被原子化(Atomization)。如果原子化的结果既不是一个单一的原子值,也不是一个空序列,将引发一个类型错误(type error)[err:XP0004][err:XP0006]。
如果一个orderspec的值有一个动态类型xdt:untypedAtomic(比如一个无模式(schemaless)文档中的字符数据),它被指派为xs:string类型。
注意:
一贯地把无类型值当成字符串,会在缺少所有被排序值类型的完整知识时,开始排序过程。
所有的orderspec类型必须是可被子类型代换(subtype substitution)和/或数值类型提升(type promotion)转换为公共类型的。排序以有运算符gt的最小公共类型执行。如果两个或者多个orderspec值没有转换为有运算符gt的公共类型,将引发一个类型错误(type error) [err:XP0004][err:XP0006]。
例:orderspec值包含一个从xs:integer派生出的hatsize类型的值,和一个从xs:decimal派生出的shoesize类型的值。由子类型代换和数值类型提升可获得的最小的公共类型值为xs:decimal。
例:orderspec值包含一个xs:string类型的值和一个xs:anyURI类型的值。由于这些类型不能通过子类型代换和数值类型提升得到公共类型,所以引发一个类型错误。
当比较两个orderspec值以确定其在排序序列中的相对位置时,大于(greater-than)关系定义如下:
当orderspec规定empty least时,如果下列条件之一为真,则认为值W大于(greater-than)值V:
V为空序列,且W为非空序列。
V为 NaN,且W既不是NaN也不是空序列。
未指定校对,且W gt V为真。
具体校对C被指定,且fn:compare(V, W, C) 小于零。
当orderspec规定empty greatest时,如果下列条件之一为真,则认为值W大于(greater-than)值V:
W 为空序列,且V为非空序列。
W为 NaN,且V既不是NaN也不是空序列。
未指定校对,且W gt V为真。
具体校对C被指定,且fn:compare(V, W, C) 小于零。
当orderspec既未确定empty least也未确定empty greatest时,是否使用empty least或者empty greatest规则,是实现相关的(implementation-defined)。
如果T1和T2为元组流中的两个元组,且V1、V2是当从左到右计算它们的orderspec时,遇到的第一对一个大于(greater-than)另一个的值(如上述定义),那么:
如果V1大于(greater-than)V2: 如果orderspec指定降序(descending),则T1在元组流中的位置放在T2之前;否则,T2在元组流中的位置在T1之前。
如果V2大于(greater-than)V1: 如果orderspec指定降序(descending),则T2在元组流中的位置放在T1之前;否则,T1在元组流中的位置在T2之前。
如果对于元组T1和T2的任何一对orderspecs,V1和V2都不大于(greater-than)另一个,那么:
如果规定了 stable ,在元组流中保持T1和T2原始次序。
如果没有规定 stable ,则元组流中T1 和 T2的次序是实现相关的(implementation-dependent)。
order by 子句使得对一个FLWOR表达式的结果进行分类很方便,即使分类关键词不包括在表达式结果中。例如,下面的表达式按照薪金降序返回雇员名,不返回具体的薪金:
for $e in $employees
order by $e/salary descending
return $e/name
order by子句是XQuery提供的除了文档次序(document order)以外唯一的指定次序的功能。因此,即使并不需要迭代,每个除了文档次序以为要求排序的查询必须包含一个FLOWOR表达式。例如,一个价格少于100的书的列表可以简单的由如$books//book[price < 100]的路径表达式获得。但是如果这些书要以书名的字母次序返回,查询必须表示如下:
for $b in $books//book[price < 100]
order by $b/title
return $b
下列例子说明一个使用若干选项的order by子句。它使得一个书的集合被主要以书名排序,其次以价格降序排序。一个具体的校对被指定给书名排序,而在价格排序中,规定没有价格的书最后出现(就像它们有可能的最小价格)。只要两个具有相同书名和价格的书出现,关键字表明它们的输入顺序将被保留。
for $b in $books//book
stable order by $b/title collation "eng-us",
$b/price descending empty least
return $b
3.8.4 实例Example
下例说明FLWOR表达式是如何嵌套的,以及在多级元素层次上如何指定次序关系。示例查询颠倒一个文档的层次,把一个书刊目录变换为一个作者列表。输入书目是一个书名列表,其中每个书包含一个作者列表。例子基于如下输入:
<bib>
<book>
<title>TCP/IP Illustrated</title>
<author>Stevens</author>
<publisher>Addison-Wesley</publisher>
</book>
<book>
<title>Advanced Programming
in the Unix Environment</title>
<author>Stevens</author>
<publisher>Addison-Wesley</publisher>
</book>
<book>
<title>Data on the Web</title>
<author>Abiteboul</author>
<author>Buneman</author>
<author>Suciu</author>
</book>
</bib>
下面的查询将输入文本变换为一个列表,其中每个作者的名字只出现一次,随后是这个作者所著书名列表。函数fn:distinct-values用来从作者节点列表中删去重复(值)。作者列表和由每个作者出版的书目列表都使用默认的校对,以字母次序返回。
<authlist>
{
for $a in fn:distinct-values($books//author)
order by $a
return
<author>
<name> {$a} </name>
<books>
{
for $b in $books//book[author = $a]
order by $b/title
return $b/title
}
</books>
</author>
}
</authlist>
上面表达式的结果如下:
<authlist>
<author>
<name>Abiteboul</name>
<books>
<title>Data on the Web</title>
</books>
</author>
<author>
<name>Buneman</name>
<books>
<title>Data on the Web</title>
</books>
</author>
<author>
<name>Stevens</name>
<books>
<title>TCP/IP Illustrated</title>
<title>Advanced Programming
in the Unix Environment</title>
</books>
</author>
<author>
<name>Suciu</name>
<books>
<title>Data on the Web</title>
</books>
</author>
</authlist>
3.9 有序和无序表达式Ordered and Unordered Expressions
[81]
OrderedExpr
::=
"ordered" "{" Expr "}"
[82]
::=
"unordered" "{" Expr "}"
有序(ordered)和无序(unordered)表达式的目的是在一个查询的特定范围设置静态语境(static context)中排序模式(ordering mode)为有序的(ordered)或者无序的(unordered)。指定的排序模式应用于嵌套于花括号内的表达式。对于其结果的排序不重要的表达式,可以将排序模式设置为unordered来实现一项性能上的优越性,从而授权系统以最有效的次序灵活地返回结果。
排序模式(Ordering mode)影响路径表达式、union、 intersect和except表达式以及没有order by子句的FLWOR表达式的运行方式。如果排序模式为有序的(ordered),则由路径、union、intersect和except表达式返回的节点序列为文档次序(document order);否则这些返回序列的次序是实现相关的(implementation-dependent)。排序模式对于FLWOR表达式的作用在3.8 FLWOR Expressions中说明。排序模式对重复的删除没有作用。
注意::
在一个排序模式为unordered的查询区域中,某些依赖节点次序的函数会返回不确定的结果。这些函数包括fn:position, fn:last, fn:index-of, fn:insert-before, fn:remove, fn:reverse, 和 fn:subsequence。在一个无序的区域中,一个路径表达式中的数值谓词也是不确定的。例如,在一个有序区域中,路径表达式//a/b[5]将以文档次序(document order)返回第五个限定的b元素(b-element)。在一个无序区域中,同样的表达式将返回一个实现相关的(implementation-dependent)限定的b元素(b-element)。
一个无序的(unordered)表达式的使用通过下例说明,这个例子将两个名为parts.xml和suppliers.xml的文档联结在一起。例子返回红色零件的数目,和提供这些零件的供方编号。如果没有使用无序的(unordered)表达式,则要求结果(零件数、供方编号)对列表有个一个顺序,这个顺序主要由parts.xml的文档顺序(document order)控制,其次由suppliers.xml的文档顺序(document order)控制。但是如果结果的排序不重要,这可能不是处理查询最有效的方法。一个XQuery实现可以使用索引来查找红色零件,或者使用suppliers.xml控制结果的主要次序而不是parts.xml,来更有效的处理查询。无序(unordered)表达式给予了查询求值程序使用这类优化的自由。
unordered {
for $p in fn:doc("parts.xml")//part[color = "Red"],
$s in fn:doc("suppliers.xml")//supplier
where $p/suppno = $s/suppno
return
<ps>
{ $p/partno, $s/suppno }
</ps>
}
除了ordered表达式和unordered表达式,XQuery提供了一个名为fn:unordered的函数,它作用于数据项的任一序列并以不确定的次序返回同一个序列。对fn:unordered函数的调用被认为是允许参数表达式以任意系统认为最有效的次序来实现。fn:unordered函数与unordered表达式有以下不同:
函数fn:unordered 只为是其紧靠着的操作对象的序列排序,而unordered表达式为其操作对象表达式及其所有嵌套表达式设置排序模式(ordering mode)。
函数fn:unordered能对任一序列起作用,而unordered表达式只影响节点序列(不是原子值的序列)的次序。
3.10 条件表达式Conditional Expressions
XQuery支持基于关键字if, then, 和 else的条件表达式。
[40]
IfExpr
::=
"if" "(" Expr ")" "then" ExprSingle "else" ExprSingle
跟随在关键字if后的表达式称为测试表达式(test expression),跟随在关键字then 和 else后的表达式分别称为then表达式(then-expression)和sele表达式(else-expression)。
处理一个条件表达式的第一步,是找到测试表达式的有效布尔值(effective boolean value),如2.3.3 Effective Boolean Value中所定义。
一个条件表达式的值定义如下:如果测试表达式的有效布尔值为true,则返回then表达式的值。如果测试表达式的有效布尔值为false,则返回else表达式的值。
条件表达式有一个传播动态错误(dynamic errors)的特殊规则。如果测试表达式的有效值为true,条件表达式忽略(不是引发)任何else表达式中遇到的动态错误。在这种情况下,既然else表达式没有值得注意的作用,它不需要被求值。同样地,如果测试表达式的有效值为false,条件表达式忽略then表达式中遇到的任何动态错误(dynamic errors),而且then表达式不需要被求值。
下面是一些条件表达式的例子:
本例中,测试表达式是一个比较表达式:
· if ($widget1/unit-cost < $widget2/unit-cost)
· then $widget1
· else $widget2
本例中,测试表达式测试属性名discounted的存在,不依赖与属性值:
· if ($part/@discounted)
· then $part/wholesale
· else $part/retail