跳转到内容

XQuery/高级搜索

来自维基教科书,为开放世界提供开放的书籍

您有多个希望搜索的字段。您想允许用户选择性地搜索特定字段,并在使用多个字段时执行布尔“AND”。

例如,您可能有一个人员数据库。每个人都有名字、姓氏、电子邮件和电话。您想允许用户搜索任何单个字段或多个字段。如果输入两个字段,则仅返回匹配这两个字段的记录。

我们将使用一个带有多个输入和选择字段的标准 HTML 表单。我们将检查每个传入的搜索请求的每个参数,如果参数不为空,我们将使用许多谓词连接单个查询,然后使用 util:eval() 函数对其进行评估。

示例 XML 数据集

[编辑 | 编辑源代码]

在以下示例中,我们将使用一个包含人员列表的 XML 文件。格式将如下所示

<people>
   <person>
      <id>123</id>
      <firstname>John</firstname>
      <lastname>Smith</lastname>
      <phone>(123) 456-7890</phone>
      <email>[email protected]</email>
      <type>faculty</type>
   </person>
   <person>
      <id>456</id>
      <firstname>Sue</firstname>
      <lastname>Jones</lastname>
      <phone>(123) 654-0123</phone>
      <email>[email protected]</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 及其员工提供。

华夏公益教科书