跳轉到內容

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 項。

華夏公益教科書