跳转到内容

XQuery/序列

来自维基教科书,开放世界中的开放书籍

您想操作一系列项目。这些项目可能彼此非常相似,也可能类型迥异。

我们从一些简单的序列示例开始。然后,我们查看最常见的序列运算符。XQuery 使用术语“序列”作为项目的有序容器的通用名称。

了解序列在 XQuery 中的工作原理对于理解该语言的工作原理至关重要。使用通用项目序列是函数式编程的核心,与其他编程语言(如 Java 或 JavaScript)形成鲜明对比,这些语言提供多种方法和函数来处理键值对、字典、数组和 XML 数据。XQuery 的妙处在于,您只需要学习一组概念和一个非常小的函数列表,即可学会如何快速操作数据。

创建字符和字符串序列

[编辑 | 编辑源代码]

您使用圆括号包含一个序列,使用逗号分隔项目,使用引号包含字符串值

   let $sequence := ('a', 'b', 'c', 'd', 'e', 'f')

请注意,您可以使用单引号或双引号,但对于大多数字符字符串,使用单引号。

   let $sequence := ("apple", 'banana', "carrot", 'dog', "egg", 'fig')

您还可以混合数据类型。例如,以下序列在同一个序列中包含三个字符串和三个整数。

   let $sequence := ('a', 'b', 'c', 1, 2, 3)

然后,您可以将序列传递给任何使用项目序列的 XQuery 函数。例如,“count()”函数接受序列作为输入并返回序列中的项目数量。

   let $count := count($sequence)

要查看这些项目的结果,您可以创建一个简单的 XQuery,使用 FLWOR 语句显示这些项目。

查看序列中的项目

[编辑 | 编辑源代码]
xquery version "1.0";
let $sequence := ('a', 'b', 'c', 'd', 'e', 'f')
let $count := count($sequence)
return
   <results>
      <count>{$count}</count>
      <items>
       {for $item in $sequence
        return
          <item>{$item}</item>
        }
      </items>
   </results>

执行

   <results>
      <count>6</count>
      <items>
         <item>a</item>
         <item>b</item>
         <item>c</item>
         <item>d</item>
         <item>e</item>
         <item>f</item>
      </items>
   </results>

查看序列中的选定项目

[编辑 | 编辑源代码]

可以使用谓词表达式选择序列中的项目。

可以通过位置(从 1 开始)选择项目

xquery version "1.0";
let $sequence := ('a', 'b', 'c', 'd', 'e', 'f')
return
   <items>{
      for $item in $sequence[1, 3, 4]
      return
           <item>{$item}</item>
      }
   </items>

执行

结果

<items>
    <item>a</item>
    <item>c</item>
    <item>d</item>
</items>

或按值

xquery version "1.0";
let $sequence := ('a', 'b', 'c', 'd', 'e', 'f')
return
    <items>{
       for $item in $sequence[1][. = ('a','e')]
       return
          <item>{$item}</item>
       }
    </items>

执行

结果

<items>
    <item>a</item>
    <item>e</item>
</items>

将 XML 元素添加到您的序列

[编辑 | 编辑源代码]

您还可以将 XML 元素存储在序列中

let $sequence := ('apple', <banana/>, <fruit type="carrot"/>, <animal type='dog'/>, <vehicle>car</vehicle>)

虽然可以使用圆括号创建 XML 项目序列,但我们也可以使用 XML 标记来开始和结束序列,并将所有项目存储为 XML 元素。

这是一个示例

let $items := 
   <items>
      <banana/>
      <fruit type="carrot"/>
      <animal type='dog'/>
      <vehicle>car</vehicle>
   </items>

一种布局约定是将所有单个项目放在自己的 item 元素标记中,如果项目列表很长,则将每个项目放在单独的一行

let $items := 
   <items>
      <item>banana</item>
      <item>
         <fruit type="carrot"/>
      </item>
      <item>
         <animal type='dog'/>
      </item>
      <item>
         <vehicle>car</vehicle>
      </item>
   </items>

然后可以使用以下 FLWOR 表达式来显示每个项目

xquery version "1.0";

let $sequence :=
    <items>
       <item>banana</item>
       <item>
          <fruit type="carrot"/>
       </item>
       <item>
          <animal type='dog'/>
       </item>
       <item>
          <vehicle>car</vehicle>
       </item>
    </items>
  

return
   <results>{
      for $item in $sequence/item
      return
         <item>{$item}</item>
   }</results>

执行

这将返回以下 XML

<results>
    <item>
        <item>banana</item>
    </item>
    <item>
        <item>
            <fruit type="carrot"/>
        </item>
    </item>
    <item>
        <item>
            <animal type="dog"/>
        </item>
    </item>
    <item>
        <item>
            <vehicle>car</vehicle>
        </item>
    </item>
</results>

请注意,当返回结果 XML 时,输出中只存在双引号。

常用序列函数

[编辑 | 编辑源代码]

您只需要使用少数几个与序列相关的函数。我们将回顾这些函数,并向您展示如何使用这些函数的组合来创建新函数。

以下是与序列一起使用的三个最常见的非数学函数。这三个函数是 XQuery 序列的真正主力。您可能花几天时间编写 XQuery,而无需使用除这三个函数之外的任何函数

  count($seq as item()*) - used to count the number of items in a sequence.  
   Returns a non-negative integer.
  distinct-values($seq as item()*) - used to remove duplicate items in a sequence.  
   Returns another sequence.
  subsequence($seq as item()*, $startingLoc as xs:double, $length as xs:double) - used to return only a subset of items in a sequence.
   Returns another sequence.  [type xs:double for $startingLoc and $length seems strange; these will be rounded to the nearest integer]

所有这些函数的数据类型都是item()*,表示零个或多个项目。请注意,“distinct-values()”函数和“subsequence()”函数都接受序列作为输入并返回序列。这在您创建递归函数时非常方便。除了“count()”之外,还有一些序列运算符可以计算总和、平均值、最小值和最大值

除了“count()”之外,还有一些序列运算符可以计算总和、平均值、最小值和最大值

  sum($seq as item()*) - used to sum the values of numbers in a sequence
  avg($seq as item()*) - used to calculate the average (arithmetic mean) of numbers in a sequence
  min($seq as item()*) - used to find the minimum value of a sequence of numbers
  max($seq as item()*) - used to find the maximum value of a sequence of numbers

这些函数旨在处理项目的数值,并且都返回数值(您可能需要在处理项目字符串时使用“number()”函数)。

您可能会发现,您只需要学习这几个 XQuery 函数就可以执行许多任务。您还可以使用这些函数创建大多数其他序列运算符。

偶尔使用的序列函数

[编辑 | 编辑源代码]

此外,还有一些函数可以返回原始序列的修改版本

  insert-before($seq as item()*, $position as xs:integer, $inserts as item()*) - for inserting
     new items anywhere in a sequence
  remove($seq as item()*, $position as xs:integer) - removes an item from a sequence
  reverse($seq as item()*) - reverses the order of items in a sequence
  index-of($seq as anyAtomicType()*, $target as anyAtomicType()) - returns a sequence of integers that
     indicate where an item is within a sequence (index counting starts at 1)

以下两个函数可以与带括号的谓词表达式“[]”一起使用,该表达式对序列中项目的“位置”信息进行操作

  last() - when used in a predicate returns the last item in a sequence so (1,2,3)[last()] returns 3
  position() - this function is used to output the position in a FLWOR statement, so 
     for $x in ('a', 'b', 'c', 'd')[position() mod 2 eq 1] return $x returns ('a', 'c')

求和函数示例

[编辑 | 编辑源代码]

假设我们有一篮子物品,我们想要计算篮子中的总物品数量

let $basket :=
   <basket>
      <item>
         <department>produce</department>
         <type>apples</type>
         <count>2</count>
      </item>
      <item>
         <department>produce</department>
         <type>banana</type>
         <count>3</count>
      </item>
      <item>
         <department>produce</department>
         <type>pears</type>
         <count>5</count>
      </item>
      <item>
         <department>hardware</department>
         <type>nuts</type>
         <count>7</count>
      </item>
      <item>
         <department>packaged-goods</department>
         <type>nuts</type>
         <count>20</count>
      </item>
   </basket>

要对每个项目的计数求和,我们需要使用 XPath 表达式来获取项目计数

  $basket/item/count

然后我们可以对该序列求和并返回结果

return
   <total>
      {sum($basket/item/count)}
   </total>

执行

结果为 37。

查找项目是否在序列中

[编辑 | 编辑源代码]

用户发现 XQuery 很容易使用,因为它会根据你提供的數據類型做出正确的操作。XQuery 会检查你是否有一个序列、一个 XML 元素或一个单一字符串,并执行最合乎逻辑的操作。这种行为使你的代码简洁易读。如果你将一个元素与一个字符串进行比较,XQuery 会查看元素内部并为你获取字符串,因此你不必明确地告诉 XQuery 使用元素的内容。当使用 “=” 运算符将一项序列与一个字符串进行比较时,XQuery 将在序列中查找该字符串,如果字符串在序列中,则返回 “true()”。它可以正常运行!

例如,给定序列

 let $sequence := ('a', 'b', 'c', 'd', 'e', 'f')

如果我们执行

 <result>{$sequence = 'd'}</result>

执行

我们将得到

   <result>true</result>

返回 “true()”,因为 'd' 在序列中。但是

  <result>{$sequence = 'x'}</result>

将返回 “false()”,因为 'x' 不在序列中。

你可以使用 “index-of()” 函数来获取序列中项的位置。如果项在序列中,则将返回一个非零整数,否则将返回空序列。

xquery version "1.0";
   let $sequence := ('a', 'b', 'c', 'd', 'e', 'f')
   let $item := 'x'
   return 
      <result>{index-of($sequence, $item)}</result>

执行

排序序列

[编辑 | 编辑源代码]

XQuery 中没有 “sort” 函数。要排序你的序列,你只需创建一个包含你的项的 FLWOR 循环的新序列,并在其中使用 order 语句。

  • 例如,如果你有一个标题作为其中一个元素的项列表,你可以使用以下语句按标题对项进行排序
  let $sorted-items :=
     for $item in $items
     order by $item/title/text()
     return $item
  • 你也可以将 “descending” 与 “order by” 一起使用来反转顺序
  for $item in $items
  order by name($item) descending
  return $item
  • 你可以通过创建一个单独的顺序序列,然后在 “order by” 子句中使用 “index-of()” 函数来指定顺序,从而根据指定顺序排列/返回$i 查询序列中的项。
  for $i in /root/*
  let $order := ("b", "a", "c")
  order by index-of($order, $i)
  return $i

集合操作:连接、并集、交集和排除

[编辑 | 编辑源代码]

XQuery 还提供函数来连接集合以及查找同时存在于两个集合中的项。

假设我们有两个包含重叠项的集合

  let $sequence-1 := ('a', 'b', 'c', 'd')
  let $sequence-2 := ('c', 'd', 'e', 'f')

你可以通过以下方式连接两个序列

  let $both := ($sequence-1, $sequence-2)

或者,也可以

  for $item in ( ($sequence-1, $sequence-2)) return $item

这将返回

 a b c d c d e f

你也可以创建一个并集集合,该集合使用 “distinct-values()” 函数删除同时存在于两个集合中的所有项的重复项

  distinct-values(($sequence-1, $sequence-2))

这将返回以下内容

  a b c d e f

请注意,'c d' 对没有重复。

现在,你可以使用此方法的变体来查找$sequence-1 中同时存在于$sequence-2 中的所有项:

  distinct-values($sequence-1[.=$sequence-2])

这将仅返回同时存在于$sequence-1$sequence-2 中的项:

  c d

此代码的含义是:“对于$sequence-1 中的每一项,如果该项 (.) 也在$sequence-2 中,则返回该项。”

你可能想要执行的最后一个集合操作是排除函数,我们将在其中查找第一个序列中不存在于第二个序列中的所有项

  distinct-values($sequence-1[not(.=$sequence-2)])

这将返回

  a b

返回重复项

[编辑 | 编辑源代码]

以下示例返回序列中所有出现次数超过一次的项列表。此过程称为“重复项检测”。

方法 1:使用 distinct-values()

[编辑 | 编辑源代码]

我们可以在序列上使用 distinct-values() 函数来查找序列中的所有唯一值。然后,我们可以检查是否存在出现两次的项。

xquery version "1.0";

let $seq := ('a', 'b', 'c', 'd', 'e', 'f', 'b', 'c')
let $distinct-value := distinct-values($seq)

(: for each distinct item if the count is greater than 1 then return it :)
let $duplicates :=
   for $item in $distinct-value
   return
      if (count($seq[.=$item]) > 1) then
         $item
      else 
         ()

return
   <results>
      <sequence>{string-join($seq, ', ')}</sequence>
      <distinct-values>{$distinct-value}</distinct-values>
      <duplicates>{$duplicates}</duplicates>
   </results>

执行

这将返回

<results>
   <sequence>a, b, c, d, e, f, b, c</sequence>
   <distinct-values>a b c d e f</distinct-values>
   <duplicates>b c</duplicates>
</results>

你也可以通过将$item 移到 “if” 语句的 “else” 部分并将 “()” 放入 “if” 语句的 “then” 部分来删除所有重复项

     if (count($seq[. = $item]) > 1) then 
        ()
     else
        $item

方法 2:使用 index-of()

[编辑 | 编辑源代码]

以下方法使用 “index-of()” 函数查找重复项

xquery version "1.0";
<duplicates>
  {
  let $values := (3, 4, 6, 6, 2, 7, 3, 1, 2)
  for $duplicate in $values[index-of($values, .)[2]]
  return
      <duplicate>{$duplicate}</duplicate>
  }
</duplicates>

执行

创建字母序列

[编辑 | 编辑源代码]

你可以使用码点 函数将字母转换为数字,并将数字转换为字母。例如,要生成从 'a' 到 'z' 的所有字母的列表,你可以编写以下 XQuery

xquery version "1.0";
<letters> 
   {
   let $number-for-a := string-to-codepoints('a')
   let $number-for-z := string-to-codepoints('z')
   for $letter in ($number-for-a to $number-for-z)
   return
      codepoints-to-string($letter)
   } 
</letters>

这将返回渲染为文本的序列

  <letters>a b c d e f g h i j k l m n o p q r s t u v w x y z</letters>

执行

创建字母集合

[编辑 | 编辑源代码]

你也可以使用此方法来创建一个子集合列表

let $data-collection := '/db/apps/terms/data'
let $number-for-a := string-to-codepoints('a')
let $number-for-z := string-to-codepoints('z')
for $letter in ($number-for-a to $number-for-z)
  return
     xmldb:create-collection($data-collection, codepoints-to-string($letter) )

此过程是在子集合中存储相关文件的非常常见的方法。

计数项

[编辑 | 编辑源代码]

在遍历项时,通常需要对项进行计数。你可以在你的 FLWOR 循环中添加 “at $count” 来完成此操作

for $item at $count in $sequence
return
  <item>
     <count>{$count}</count>
     {if ($count mod 2) then <odd/> else <even/>}
  </item>

请注意,取模运算符

 ($count mod 2)

对于奇数返回 1,这将转换为 “true()”,对于偶数返回零,这将转换为 “false()”。你可以使用此技术使表的交替行颜色不同。

删除数字

[编辑 | 编辑源代码]

你可以通过简单地在数字序列的末尾添加一个谓词来过滤掉特定类型的数字。例如,如果你想从数字序列中删除所有奇数,你将使用的表达式为

  $my-sequence-of-integers[. mod 2 = 0]

这意味着“对于序列中的所有当前数字,如果当前数字模 2 的值为 0(即“如果当前数字不是奇数”),则将其保留在结果序列中”。

这是一个完整的示例

xquery version "1.0";
declare function local:remove-odd($in as xs:integer*) as xs:integer* {
    $in[. mod 2 = 0]
};

let $thirty := 1 to 30
 
return
   <results>
       <in>{$thirty}</in>
       <out>{local:remove-odd($thirty)}</out>
   </results>

执行

这将返回

<results>
   <in>1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30</in>
   <out>2 4 6 8 10 12 14 16 18 20 22 24 26 28 30</out>
</results>

将序列转换为字符串

[编辑 | 编辑源代码]

对序列执行的最常见操作之一是将其转换为一个用于显示的单一字符串。我们经常希望在值之间放置一个分隔符字符串,但在最后一个值之后不放置。XQuery 包含一个非常方便的函数,名为 “string-join()”。其格式为

  string-join($input-sequence as item()*, $separator-string as xs:string) as xs:string

例如,

 let $sequence := ('a', 'b', 'c')
 return string-join($sequence, '--')

的输出将为

 a--b--c

请注意,最后一个字符串之后没有 "--"。分隔符仅用于序列的项之间。

组合序列操作

[编辑 | 编辑源代码]

通常需要以线性步骤序列的方式“链接”序列操作。例如,如果你想对序列列表进行排序,然后选择前 10 项,你的查询可能如下所示

xquery version "1.0";
let $input-sequence := doc('vessels.xml')//Vessel
let $sorted-items :=
  for $item in $input-sequence
  order by $item/name
  return $item
return
   <ol>
     {
     for $item at $count in subsequence($sorted-items, 1, 10)
     return
      element li  {
         attribute class {if ($count mod 2) then 'odd' else 'even'} ,  (: this puts an even or odd class attribute in the li :)
         $item/name/text()
      }
     }
   </ol>

执行

此技术可用于对搜索结果进行分页,以便用户查看搜索结果的前 10 项。然后,可以使用一个控件来获取搜索结果中的下一个 N 项。

华夏公益教科书