XQuery/Lucene 搜索
您希望对一个或多个 XML 文档执行全文关键词搜索。这可以通过使用 Lucene 索引扩展到 eXist 来完成。
Apache Lucene 全文搜索框架作为全文索引添加到 eXist 1.4 中,取代了以前的原生全文索引。新的 Lucene 全文搜索框架比 eXist 的传统全文索引更快、更可配置且功能更丰富。它也将成为 W3C 的 XQuery 全文扩展实现的基础。
eXist 将一个独特的节点 ID 与 XML 文档中的每个节点关联。此节点 ID 用作 Lucene 索引文件中的 Lucene 文档 ID,即每个 XML 节点都成为一个 Lucene 文档。这意味着您可以高度自定义关键词匹配到文档中每个节点的搜索权重。例如,标题中关键词的匹配得分可能高于文档正文中的匹配得分。这意味着在大量文档中检索文档标题的搜索命中更有可能在搜索结果中排名第一。这意味着您的搜索将具有比不保留文档结构的搜索系统更高的精确率和召回率。
以下是关于如何使用 Lucene 的 eXist 文档
eXist 支持完整的 Lucene 查询解析器语法(除了“字段搜索”)。
<test>
<p n="1">this paragraph tests the things made up by
<name>ron</name>.</p>
<p n="2">this paragraph tests the other issues made up by
<name>edward</name>.</p>
</test>
为了对该文档执行 Lucene 索引的全文搜索,我们需要创建一个索引配置文件,collection.xconf,描述应索引哪些元素和属性,以及索引的各种详细信息。
<collection xmlns="http://exist-db.org/collection-config/1.0">
<index>
<!-- Enable the legacy full text index for comparison with Lucene -->
<fulltext default="all" attributes="no"/>
<!-- Lucene index is configured below -->
<lucene>
<analyzer class="org.apache.lucene.analysis.standard.StandardAnalyzer"/>
<analyzer id="ws" class="org.apache.lucene.analysis.WhitespaceAnalyzer"/>
<text match="//test"/>
</lucene>
</index>
</collection>
注释
- 如果您的测试数据保存在 db/test 中,则应将 collection.xconf 保存到 db/system/config/db/test 中。索引配置文件始终保存在 system/config/db 内部的目录结构中,该目录结构与 db 的目录结构同构。
- 创建或更新此索引配置文件后,您需要重新索引数据。您可以使用基于 Java 的 eXist 管理客户端来执行此操作,选择测试集合并选择“重新索引集合”,或者使用xmldb:reindex()函数,在 eXide 或 XQuery 沙箱中提供 xmldb:reindex('/db/test')。
- 虽然传统全文索引对于基于 Lucene 的搜索不是必需的,但在此示例配置中已明确启用它,以便指出 Lucene 和传统搜索函数/运算符之间的表达相似性(即 Lucene 的 ft:query() 与传统全文索引的 &=、|=、near()、text:match-all()、text:match-any())。
您可以为单个元素或属性名称 (qname="...") 或节点路径 (match="...") 定义 Lucene 索引。
如果在 qname 上定义索引,例如 <text qname="test"/>,则仅在 <test> 上创建索引。传递给 Lucene 的是 <test> 的字符串值,其中包括其所有后代文本节点的文本。使用此类索引,无法搜索 <test> 下的节点,例如 <p> 或 <name>,因为此类节点都已折叠。如果要能够查询后代节点,则应为其设置其他索引,例如 <text qname="p"/> 或 <text qname="name"/>。
如果在节点路径上定义索引,如上所示使用 <text match="//test"/>,则 <test> 下的节点结构将保留在索引中,并且您仍然可以查询后代节点,例如 <p> 或 <name>。这可以看作是在 <test> 下的所有元素上建立索引的快捷方式。请注意,根据文档,此功能“可能会发生变化”[1]。
在决定使用哪种方法时,您应该考虑文档的哪些部分将作为全文查询的上下文。在考虑具体搜索场景时,最好决定将其范围缩小或扩大。
eXist 可以处理以两种查询语法表达的 Lucene 搜索,即 Lucene 的标准查询语法和特定于 eXist 的 XML 语法。在本节中,介绍了标准查询语法。这是用户可以在搜索字段中输入的语法。
在当前上下文中搜索“Ron”将表示为 [ft:query(., 'ron')]。第一个参数保存要搜索的节点,此处为“.”,当前上下文节点。第二个参数提供查询字符串,此处仅为单词“ron”。
ft:query() 函数允许使用 Lucene 通配符。
“?”可用于单个字符,“*”可用于零个、一个或多个字符:“edward”可以通过“ed?ard”和“e*d”找到。Lucene 标准查询语法不允许“*”和“?”出现在单词的开头。但是,在 eXist 中,可以向查询添加一个选项以允许在搜索中使用前导通配符;请参阅eXist Lucene 文档。
模糊搜索,在单词末尾使用“~”可以检索“ron”通过“don~”。可以通过附加 0.0f 到 1.0f 之间的一个数字来量化模糊度,从而可以通过 [ft:query(., 'don~0.6')] 检索“ron”,但不能通过 [ft:query(., 'don~0.7')] 检索。模糊度是基于 Levenshtein 距离或编辑距离算法。[2]。默认值为 0.5。
可以使用布尔运算符“AND”和“OR”,具有预期的语义。这有一个变体表示法:[ft:query(., 'edward AND ron')] 也可以写成 [ft:query(., '+edward +ron')]。[ft:query(., '+edward ron')] 将要求“edward”存在,但不要求“ron”存在。“NOT”也可以使用:[ft:query(., 'edward NOT ron')] 查找不带“ron”的“edward”。“NOT”也可以用“-”表示:[ft:query(., '+edward -ron')]。运算符可以用括号分组,如 [ft:query(., '(edward OR ron) NOT things')]。
可以通过将短语放在引号中来搜索短语:[ft:query(., '"other issues"')]。
eXist 中不支持使用 Lucene 标准查询语法的查询中的字段、邻近搜索、范围搜索、提升和转义保留字符。可以在索引期间进行提升:eXist Lucene 文档。
由于我们已将<test>元素索引为路径,因此索引包含后代节点,对嵌套元素的查询也会返回命中结果。
collection('/db/test')/test/p/name[ft:query(., 'edward')] collection('/db/test')/test/p[ft:query(name, 'edward')]
如果我们使用<text qname="test"/>将qname test索引,我们将无法做到这一点。
上面collection.conf文件中激活的标准Lucene分析器(使用<analyzer class="org.apache.lucene.analysis.standard.StandardAnalyzer"/>
),应用了Lucene默认的英语停用词列表,并从索引中删除了以下单词:a、an、and、are、as、at、be、but、by、for、if、in、into、is、it、no、not、of、on、or、such、that、the、their、then、there、these、they、this、to、was、will、with。
如果要自定义停用词列表,请指定一个分析器,该分析器包含您希望应用的停用词列表(停用词之间用换行符分隔)的文件系统上的绝对位置,然后重新索引集合。
<analyzer class="org.apache.lucene.analysis.standard.StandardAnalyzer">
<param name="stopwords" type="java.io.File" value="/tmp/stop.txt"/>
</analyzer>
如果希望所有单词都可搜索,可以将stop.txt留空或省略对stop.txt的引用。
<analyzer class="org.apache.lucene.analysis.standard.StandardAnalyzer">
<param name="stopwords" type="java.io.File"/>
</analyzer>
进行这些更改后,重新启动eXist并重新索引。
Lucene为每个匹配项分配一个相关性分数或排名。某个单词在文档中出现的频率越高,分数就越高。eXist会保留此分数,并且可以通过score函数访问,该函数返回一个小数。
for $m in collection('/db/test')//p[ft:query(., 'tests ron')]
let $score := ft:score($m)
order by $score descending
return
<hit score="{$score}">{$m}</hit>
分数越高,命中结果的相关性就越高。
可以设置配置文件,以便对文档中特定元素应用更高的搜索权重。例如,在书籍标题中匹配关键字的排名将高于在书籍正文中匹配关键字的排名。
以下查询是等效的(使用的索引除外)。
要使用新的Lucene查询函数表达“匹配任何”(|=)传统风格的全文查询
collection('/db/test')//p[. |= 'tests edward']
可以使用以下方法
collection('/db/test')//p[ft:query(.,
<query>
<bool>
<term occur="should">tests</term>
<term occur="should">edward</term>
</bool>
</query>)]
要使用新的Lucene查询函数表达“匹配所有”(&=)传统风格的全文查询
collection('/db/test')//p[. &= 'tests edward']
可以使用以下方法
collection('/db/test')//p[ft:query(.,
<query>
<bool>
<term occur="must">tests</term>
<term occur="must">edward</term>
</bool>
</query>)]
要使用新的Lucene查询函数表达“匹配无”(not + |=)传统风格的全文查询
collection('/db/test')//p[not(. |= 'issues edward')]
可以使用以下方法
collection('/db/test')//p[not(ft:query(.,
<query>
<bool>
<term occur="should">issues</term>
<term occur="should">edward</term>
</bool>
</query>))]
请注意,最后一个不能表示为
collection('/db/test')//p[ft:query(.,
<query>
<bool>
<term occur="not">issues</term>
<term occur="not">edward</term>
</bool>
</query>)]
因为Lucene的NOT运算符不能单独使用,必须存在“正”搜索词。
以下查询是等效的,可以通过将它们作为此XQuery代码片段中$query的值来测试莎士比亚示例(随eXist一起提供)。
declare option exist:serialize "highlight-matches=both";
let $query := 'query'
return //SPEECH[ft:query(., $query)]
搜索类型 | Lucene语法 | XML语法 |
---|---|---|
'原子',匹配任何术语 | fillet snake |
<query> <bool> <term>fillet</term> <term>snake</term> </bool> </query> |
'原子',匹配所有术语 | +fillet +snake |
<query> <bool> <term occur="must">fillet</term> <term occur="must">snake</term> </bool> </query> |
'原子',仅匹配某些术语 | -fillet +snake |
<query> <bool> <term occur="not">fillet</term> <term occur="must">snake</term> </bool> </query> |
'原子',带通配符 | +fillet +sn*e |
<query> <bool> <term occur="must">fillet</term> <wildcard occur="must">sn*e</wildcard> </bool> </query> |
'原子',带正则表达式 | <query> <bool> <term occur="must">fillet</term> <regex occur="must">sn.*e</regex> </bool> </query> |
|
短语搜索 | "fillet snake" |
<query> <phrase>fillet snake</phrase> </query> <query> <near>fillet snake</near> </query> |
邻近搜索 | "fillet snake"~1 |
<query> <near slop="3"> <term>fillet</term> <term>snake</term> </near> </query> |
邻近搜索,无序 | <query> <near slop="1" ordered="no"> <term>snake</term> <term>fillet</term> </near> </query> |
|
模糊搜索,无相似度参数 | snake~ |
<query> <fuzzy>snake</fuzzy> </query> |
模糊搜索,带相似度参数 | snake~0.3 |
<query> <fuzzy min-similarity="0.3">snake</fuzzy> </query> |
请注意上表中的空白!在标准Lucene语法中,您无法表达
- 正则表达式:这是eXist的XML查询语法的独特功能,通过<regex>元素实现。
- 邻近搜索词的顺序:这是eXist的XML查询语法的独特功能,通过<near>上的@ordered属性实现。
最后,一个更复杂的情况,其中布尔运算符被分组以覆盖默认的优先级规则。
搜索类型 | Lucene语法 | XML语法 |
---|---|---|
布尔搜索运算符组 | (fillet OR malice) AND snake |
<query> <bool> <bool occur="must"> <term occur="should">fillet</term> <term occur="should">malice</term> </bool> <term occur="must">snake</term> </bool> </query> |
请注意如何
- 标准Lucene语法中的分组可以用XML语法中的嵌套来表达。
- 对于嵌套的<bool>运算符,也可以指定@occur属性。
请注意,如果在字符串中包含通配符,则必须使用<wildcard>元素将字符串括起来。
以下
//SPEECH[ft:query(., 'fennny sna*')]
等效于
xquery version "1.0";
let $query :=
<query>
<term>fen</term>
<wildcard>sna*</wildcard>
</query>
return
//SPEECH[ft:query(., $query)]
- eXist Lucene XML语法 Ron Van den Branden的博客文章