跳轉到內容

XQuery/高階搜尋

來自華夏公益教科書,為開放世界提供開放的書籍

您有多個希望搜尋的欄位。您想允許使用者選擇性地搜尋特定欄位,並在使用多個欄位時執行布林“AND”。

例如,您可能有一個人員資料庫。每個人都有名字、姓氏、電子郵件和電話。您想允許使用者搜尋任何單個欄位或多個欄位。如果輸入兩個欄位,則僅返回匹配這兩個欄位的記錄。

我們將使用一個帶有多個輸入和選擇欄位的標準 HTML 表單。我們將檢查每個傳入的搜尋請求的每個引數,如果引數不為空,我們將使用許多謂詞連線單個查詢,然後使用 util:eval() 函式對其進行評估。

示例 XML 資料集

[編輯 | 編輯原始碼]

在以下示例中,我們將使用一個包含人員列表的 XML 檔案。格式將如下所示

<people>
   <person>
      <id>123</id>
      <firstname>John</firstname>
      <lastname>Smith</lastname>
      <phone>(123) 456-7890</phone>
      <email>john.smith@example.com</email>
      <type>faculty</type>
   </person>
   <person>
      <id>456</id>
      <firstname>Sue</firstname>
      <lastname>Jones</lastname>
      <phone>(123) 654-0123</phone>
      <email>sue.jones@example.com</email>
      <type>staff</type>
   </person>
</people>

謂詞背景

[編輯 | 編輯原始碼]

如果您只有一個“where 子句”(稱為謂詞),則始終可以將此謂詞放在 XPath 表示式的末尾。例如,以下 FLWOR 表示式將返回系統中的所有人員記錄

  for $person in collection('/db/apps/directory')//person
  return
     $person

您現在可以將其限制為僅包含教師,方法是新增一個謂詞

  for $person in collection('/db/apps/directory')//person[type='faculty']
  return
     $person

您現在可以透過僅新增另一個謂詞來搜尋所有姓氏為“mark”的教師

  for $person in collection('/db/apps/directory')//person[type='faculty'][firstName='mark']
  return
     $person

示例搜尋表單

[編輯 | 編輯原始碼]

高階搜尋表單的示例 HTML 程式碼

[編輯 | 編輯原始碼]

以下是此表單的 HTML 表單部分。

<form method="get" action="advanced-search.xq">            
    <label>First Name: </label>
    <input type="text" name="firstname"/>
    <br/>
    
    <label>Last Name: </label>
    <input type="text" name="lastname"/>
    <br/>
    
    <label>E-Mail: </label>
    <input type="text" name="email" size="40"/>
    <br/>
    
    <label>Phone: </label>
    <input type="text" name="phone"/>
    <br/>
    
    <label>Primary Type: </label>
    <select name="type">
        <option value="">- Select -</option>
        <option value="staff">Staff</option>
        <option value="faculty">Faculty</option>
        <option value="student">Students</option>
    </select>
    <br/>
    
    <input type="submit" name="Submit"/>
</form>

當用戶在名字欄位中新增“John”的名字,並選擇“staff”的型別,然後按下提交查詢按鈕時,以下是一個由該表單建立的 URL 的示例

  advanced-search.xq?firstname=John&lastname=&email=&phone=&type=staff&Submit=Submit+Query

請注意,大多數字段為空。只有 firstname 和 type 在等號右側有值。

示例搜尋服務

[編輯 | 編輯原始碼]

搜尋服務將包含以下程式碼部分。

獲取 URL 引數

[編輯 | 編輯原始碼]

以下程式碼片段將從傳入的 URL 請求中獲取 URL 引數,並將它們分配給 XQuery 變數。

let $firstname := lower-case(request:get-parameter('firstname', ''))
let $lastname := lower-case(request:get-parameter('lastname', ''))
let $email := lower-case(request:get-parameter('email', ''))
let $phone := lower-case(request:get-parameter('phone', ''))
let $type := lower-case(request:get-parameter('type', ''))

請注意,每個傳入的引數在進行任何比較之前都會先轉換為小寫。

構建謂詞字串

[編輯 | 編輯原始碼]

我們現在準備開始構建我們的謂詞。由於許多欄位將為空,因此我們只有在變數存在時才構建謂詞。

let $firstname-predicate := if ($firstname) then concat('[lower-case(firstname)', " = '", $firstname, "']") else ()
let $lastname-predicate := if ($lastname) then concat('[lower-case(lastname)', " = '", $lastname, "']") else ()
let $email-predicate := if ($email) then concat('[lower-case(email)', " = '", $email, "']") else ()
let $phone-predicate := if ($phone) then concat("[contains(phone, '", $phone, "')]") else ()
let $type-predicate := if ($type) then concat('[type', " = '", $type, "']") else ()

對於 firstname、lastname 和 email,我們正在將傳入的引數與 XML 檔案中的小寫字串進行比較。對於電話號碼,我們使用 contains() 函式返回電話號碼中包含某個字串的所有記錄。型別使用精確匹配,因為資料和關鍵字的大小寫都是已知的。

該程式最具挑戰性的方面是學習如何正確獲取引號的順序。一般來說,我使用單引號來括起靜態字串,除非該字串本身必須包含單引號。然後我們使用雙引號。最困難的部分是組裝一個像 [type = 'staff'] 這樣的字串,並記住在詞語 staff 周圍加上單引號。如果您能弄清楚這一點,剩下的部分就很容易了。

如果您遇到問題,也可以將 concat 分成多行

  concat(
     '[type',
     " = '",
     $type,
     "']"
  )

其中每一行都必須以相同的引號型別開頭和結尾。

連線查詢

[編輯 | 編輯原始碼]

要建立 eval 字串,我們只需要從集合開始建立一個單個長字串,並新增每個謂詞。如果沒有引數,則謂詞字串將為空。

let $eval-string := concat
   ("collection('/db/apps/directory/data')//person", 
    $firstname-predicate,
    $lastname-predicate,
    $email-predicate,
    $phone-predicate,
    $type-predicate
   )

只有姓氏和型別時,查詢將如下所示

collection('/db/apps/directory/data')//person[lower-case(firstname) = 'John'][lower-case(type) = 'faculty']

請注意,一些高階系統會根據最有可能縮小搜尋範圍的謂詞來修改謂詞的順序。由於姓氏為 John 的記錄少於教師記錄,因此始終將姓氏放在型別之前效率更高。這意味著需要從硬碟轉移到 RAM 的節點更少,查詢將執行得更快。

執行查詢

[編輯 | 編輯原始碼]

查詢的執行是透過將 eval 字串傳遞給 util:eval 函式來完成的。

  let $persons := util:eval($eval-string) 

顯示結果

[編輯 | 編輯原始碼]

我們現在準備顯示所有結果。我們透過為每個人建立一個 FLWOR 語句,併為每個命中返回一個 <div> 元素來做到這一點。每個 <div> 元素都有一個帶有姓氏、名字和型別的連結。當用戶單擊每個連結時,將使用一個專案檢視器,並將該人的 ID 傳遞給專案檢視器。

for $person in $persons
   let $id := $person/id
   let $lastname := $person/lastname
   let $firstname := $person/firstname
   order by $lastname, $firstname
   return
      <div class="hit">
         <a href="../views/view-item.xq?id={$id}">
           {$lastname},
           {$person//firstname/string()} {' '}
           {$person/type/string()}
         </a>
      </div>

NGram 搜尋

[編輯 | 編輯原始碼]

在您的 conf.xml 模組中,確保取消以下行的註釋

<module uri="http://exist-db.org/xquery/ngram"  class="org.exist.xquery.modules.ngram.NGramModule" />

以下是您 collection.xconf 檔案中 NGram 元素的頁面

NGram 配置檔案

編輯後重新索引。

您現在可以使用以下任何函式

NGram 函式

此示例由美國里士滿大學的 Eric Palmer 及其員工提供。

華夏公益教科書