2.5.2 使用示例
既然我们已经了解了各类查询方式的适用场合以及期望结果,现在可以趁热打铁,用具体的使用示例来进一步加强对它们的认知。注意,这些例子并不能覆盖Elasticsearch查询的方方面面,而仅仅是对于我们通过查询获取所需信息的一些简单示例说明。
1. 测试数据
为了达到本节的目的,我们给library索引加入了两个新文档。
首先,我们需要微调一下索引映射以支持嵌套文档(本节某些例子中需要用到)。修改映射的命令如下:
然后,接着索引两个新文档。相关命令如下:
2. 基本查询示例
让我们看一下使用基本查询类别的例子。
(1)查询给定范围的数据
匹配给定取值范围的文档的查询是最简单的查询方式之一。通常,这种查询作为一个更复杂查询或过滤器的一部分而存在。举例来说,一个可以查出副本数在[1,3]区间的书籍的查询如下所示:
(2)简化的多词项查询
想象一个场景,用户需要传入一组书籍标签,期望查询出匹配这些标签的书籍。还有一个条件,如果用户给出的标签超过3个,则只要求至少75%的给定标签与索引中的书籍匹配。通常我们可以通过bool查询去实现这个目的,不过Elasticsearch提供了可以实现相同目的的terms查询。执行查询的命令如下:
3. 组合查询示例
现在我们看看如何使用组合查询来组合其他查询方式。
(1)对匹配文档加权
最简单的示例是使用包含一个可选的加权片段的bool查询来实现对部分文档的权重提升。举例来说,如果需要找出所有至少拥有一个副本的书籍,并对1950年后出版的书籍进行加权,可以使用如下查询命令:
(2)忽略查询的较低得分部分
我们之前提到的dis_max查询可以控制查询中较低得分部分的影响。举例来说,如果我们期望找出所有title字段匹配“crime punishment”或characters字段匹配“raskolnikov”的文档,并在文档打分时仅考虑得分最高的查询片段,可以执行如下查询命令:
查询结果如下:
我们来单独看一下查询各部分的打分。可以单独执行如下查询片段:
查询结果如下:
单独执行下一个查询片段的命令如下:
查询结果如下:
可以看出,dis_max查询返回的文档得分等于打分最高的查询片段的得分(上面的第一个查询片段)。这是因为我们设置tie_breaker属性为0.0。
4. 无分析查询示例
让我们看看两个不使用任何分析器的查询示例。
(1)找出符合标签的结果
Elasticsearch提供的term查询是最简单的无分析查询之一。我们一般很少单独使用term查询,而是常常将其使用在各种复合查询中。举个例子,假设我们想要查找出所有tags字段包含“novel”值的书籍。为了达到这个目的,需要执行如下查询命令:
(2)在查询时高效处理停用词
Elasticsearch提供了普通的terms查询,可以在查询时用一种高效的方式处理停用词。它将查询词项分成两组—重要的词项和不重要词项。重要词项的出现频率较低,相反,不重要词项的出现频率很高。Elasticsearch首先用重要词项执行查询并计算文档得分,然后再使用不重要词项执行查询,这时不再计算文档得分。因此查询可以变得更快。
举例来说,以下两个查询单就查询结果而言非常相似,而结果的打分却不一样。注意,如果想清楚地看出两者打分的不同,我们需要准备大量的数据样本,并在索引时禁用停用词。
第二个查询如下:
5. 全文检索查询示例
全文检索是一个宽泛的主题,其使用场景也十分广泛。在这里我们选出两个简单场景的查询示例加以展示。
(1)使用Lucene查询语法
某些时候,使用Lucene查询语法是不错的选择。我们曾在1.1.4节介绍过Lucene查询语法。举个例子,假如我们想找出title字段包含“sorrows”和“young”词项、author字段包含“von goethe”短语,并且副本数不超过5个的文档,可以执行如下查询:
在这个查询中,我们使用了Lucene查询语法来传递所有匹配条件,让Lucene通过查询解析器来构造合适的查询。
(2)对用户查询串进行容错处理
在某些场景下,来自用户的查询可能包含错误。比如,下面这个查询:
Elasticsearch将返回如下响应:
这意味着在构建查询时遇到了解析错误,查询无法被成功地构建出来。这也是Elasticsearch引入simple_query_string查询的原因。它使用一个可尝试处理用户输入错误的查询解析器,并试图猜测用户的查询用意。如果用simple_query_string查询来改写上面这个例子,代码如下:
如果执行这个查询,你将看到Elasticsearch能够返回合适的文档结果,尽管查询并未被恰当构造。
6. 模式匹配查询示例
模式匹配查询的例子很多,不过在这里我们只打算展示其中两个。
(1)使用前缀查询实现自动完成功能
针对索引数据提供自动完成功能是一种常见的应用场景。如我们所知,前缀查询不会被分析,直接工作于特定字段中被索引的词项上。因此,实际的功能依赖于索引时生成词条的方式。举例来说,假定我们希望针对title字段的所有词条提供自动完成功能。此时用户输入的前缀是“wes”,符合条件的对应查询构造如下:
(2)模式匹配
如果我们想匹配特定模式,而此时索引中的词条无法支持,可以尝试使用regexp查询。读者需要注意的是,这种查询非常昂贵,请尽量避免使用。当然,有时候我们不得不使用它。还有一点需要注意的是,regexp查询的执行性能与所选正则表达式相关。如果你选择了一个能够被改写成大量词项的正则表达式,执行性能将极其糟糕。
现在我们来看一下使用regexp查询的例子。假定我们需要找出符合以下条件的文档:文档的characters字段中包含以“wat”开头、以“n”结尾、中间有两个任意字符的词项。为了实现这些条件,可以使用类似下面的regexp查询命令:
7. 支持相似度操作的查询示例
让我们看两个关于如何查找近似文档和词项的简单示例。
(1)找出给定词项的近似词项
一个非常简单的例子是使用fuzzy查询找出包含给定词项近似词项的文档。比如,如果我们需要查找包含“crimea”的近似词项的文档,可以执行如下查询命令:
(2)找出拥有近似字段值的文档
另一个相似度查询的案例是,根据我们在查询中提供的字段值,找出包含类似字段值的文档。比如,我们想找出title字段值类似“western front battles”的书籍,可以构造如下查询:
查询结果如下:
从上面结果可见,有时候查询结果跟我们的期望有些出入(比如结果中第2本书的标题)。这是因为Elasticsearch认为它们之间有相似性。在前面这个查询中,Elasticsearch将对所有词项执行一次模糊查询,然后为匹配的文档选择出一组最佳查分词项。
8. 支持打分操作的查询示例
涉及相关度,Elasticsearch提供了一些可以按需修改文档得分的查询。当然,大多数查询方式都支持权重,可以让我们拥有更多的操作余地。接下来让我们看看两个支持打分操作的查询示例。
(1)偏爱新书
假定我们更喜欢新出版的书籍,因此1986年出版的书籍要比1870年出版的书籍拥有更高的得分。满足这个需求的查询命令如下:
我们将在第3章介绍function_score查询。在这里,如果你仔细观察刚才这个查询的响应结果,可以发现越新的书籍得分越高。
(2)对拥有某些值的书籍扣分
有时候,我们需要降低某些文档的重要性,却依然要在结果列表中输出它们。举个例子,我们可能想要列出所有的书籍,不过要通过降低书籍得分的方式把那些当前无货的书籍放到结果列表的末尾。我们不希望按标记是否有货的字段进行排序,因为用户有时候清楚地知道他要找什么书,因此针对全文检索查询的结果得分是很重要的。不过,如果仅仅想把当前无货的书籍排到结果尾部,可以执行如下查询命令:
9. 位置敏感查询示例
位置敏感查询允许我们匹配包含正确次序短语和词项的文档。这类查询因为资源占用问题,不经常被使用。让我们看两个例子。
(1)匹配短语
匹配短语的match_phrase查询是最简单的位置敏感查询,也是本类别中使用最多的。举例来说,otitle字段匹配短语“leiden des jungen”的查询命令如下:
(2)处处可见范围查询
当然,短语查询在处理位置敏感需求时非常简便。不过,如果我们想执行一个查询,找出符合以下条件的文档:在“die”词项后面不超过两个位置的地方包含一个“des jungen”短语,并且紧跟着短语后面是一个“werthers”词项。这时候,该怎么办呢?我们可以使用范围查询。符合这些条件的查询命令类似如下:
注意,范围查询是不经过分析器处理的。我们可以通过查看Explain API的响应来确认这一点。为了看到Explain API的响应,我们需要把刚才这个查询命令的请求体(或者叫查询内容)发送到/library/book/5/_explain这个REST端点。响应的有趣部分如下:
10. 结构敏感查询示例
如果涉及嵌套文档或父子文档关系,迟早会用到结构敏感查询。让我们从下面两个例子中看一下这类查询是如何使用的。
(1)返回包含某个嵌套子文档的父文档
第一个例子非常简单。假定我们要查找所有拥有4星及以上评论的书籍。相应的查询命令如下:
(2)嵌套子文档的得分影响父文档得分
假定我们要返回所有拥有评论的书籍,并且按照最高评论星级对这些书籍进行排序。满足这些条件的查询命令如下: