
2.5 复合查询
Elasticsearch中的复合查询提供了一种机制来创建复杂的搜索查询。它们将单个查询,即我们目前见到的叶子查询(leaf query)组合起来,构建出强大、健壮的查询,以满足复杂场景的需求。(在本节中我们将对复合查询进行简要讨论,在第11章中我们会详细讨论它们。)
复合查询包括以下几种:
❏ 布尔(bool)查询;
❏ 常数分数(constant_score)查询;
❏ 函数分数(function_score)查询;
❏ 提升(boosting)查询;
❏ 分离最大化(dis_max)查询。
其中,bool查询是最常用的,下面我就介绍一下bool查询的实际应用。
2.5.1 bool查询
bool 查询用于根据布尔条件组合其他查询,从而创建复杂的查询逻辑。bool查询可以使用must、must_not、should和filter这4种子句来构建搜索。代码清单2-18展示了bool查询的格式。
代码清单2-18 包含子句的bool查询的格式
GET books/_search { "query": { "bool": { ←--- 一个bool查询是由多个 条件布尔子句组合而成的 "must": [{ }], ←--- 查询条件必须匹配文档 "must_not": [{ }], ←--- 查询条件必须不匹配(不影响分数) "should": [{ }], ←--- 查询应该匹配 "filter": [{ }] ←--- 查询必须匹配(不影响分数) } } }
正如所见,bool查询需要使用must、must_not、should和filter这几种子句中的一种或多种来定义查询条件(见表2-2)。可以通过组合这些子句来表达多个查询条件。
表2-2 bool查询子句

假设我们的需求是搜索满足以下条件的书:
❏ Joshua撰写的;
❏ 评分高于4.7;
❏ 2015年之后出版。
我们需要使用bool查询,并借助表2-2中的一些子句来将这些条件组合成一个查询。在接下来的几节中,我们将为我们的搜索条件构建复合bool查询。我不会一次性介绍完整的查询(这可能会让人不知所措),而是将其拆分为单独的搜索条件,最后再将它们组合在一起。下面我就介绍如何使用must子句来检索作者。
2.5.2 must子句
我们想找到所有Joshua撰写的书,因此可以创建一个带有must子句的bool查询。在must子句中,我们编写了一个match查询来搜索Joshua撰写的书(我们在2.2.3节中学过match查询),如代码清单2-19所示。
代码清单2-19 带有must子句的bool查询
GET books/_search { "query": { "bool": { ←--- bool查询 "must": [{ ←--- must子句:文档必须匹配这一查询条件 "match": { ←--- 其中一个查询(match查询)匹配Joshua撰写的书 "author": "Joshua Bloch" } }] } } }
注意,bool查询被包含在query对象中。它有一个must子句,这个子句又接受一个由多个查询条件(在这个例子中是匹配Joshua Bloch撰写的所有书)组成的数组。这个查询应该返回两本书(Effective Java和Java Concurrency in Practice),说明我们的文档存储中只有这两本书是Joshua Bloch撰写的。
当我们说must子句接受一组由多个查询组成的数组时,意味着什么?这意味着我们可以向must子句中添加多个查询,使其变得更加复杂。例如,代码清单2-20中包含一个带有两个叶子查询的must子句,其中一个叶子查询是搜索作者的match查询,另一个叶子查询是搜索短语的match_phrase查询。
代码清单2-20 带有多个叶子查询的must子句
GET books/_search { "query": { "bool": { "must": [{ ←--- 带有两个叶子查询的must子句 "match": { ←--- match查询查找Joshua撰写的书 "author": "Joshua Bloch" } }, { "match_phrase": { ←--- 第二个查询在字段中搜索一个短语 "synopsis": "best Java programming books" } }] } } }
让我们继续,添加一个must_not子句,用来创建一个否定条件。
2.5.3 must_not子句
让我们改进一下查询条件。我们不应该获取Joshua撰写的书中评分低于4.7的那些书。为了满足这个条件,我们使用一个must_not子句,其中包含一个range查询,将评分设置为小于(lt)4.7。代码清单2-21展示了must_not子句,也包含了来自2.5.2节的查询中的must子句。
代码清单2-21 带有must和must_not子句的bool查询
GET books/_search { "query": { "bool": { "must": [{ "match": { "author": "Joshua" } }], ←--- must子句搜索Joshua的书 "must_not": [{ "range": { "amazon_rating": { "lt": 4.7}}}] ←--- must_not子句中包含range查询,用来排除评分较低的书 } } }
此查询只返回了一本书,即Effective Java。Joshua Bloch的另一本书Java Concurrency in Practice因为不符合must_not条件(其评分为4.3,低于4.7的要求)而被排除在外。
除了搜索Joshua撰写的评分不低于4.7的书,我们还可以再添加一个条件来检查书是否匹配标签(如tag: "Software")。如果匹配,我们期望分数增加;否则,它不会对结果产生影响。为此,我们可以使用接下来要介绍的should子句。
2.5.4 should子句
should子句的操作类似于OR运算符。也就是说,如果搜索词与should查询匹配,相关性分数就会提高。如果搜索词不匹配,查询不会失败,但这个子句会被忽略。should子句更多是用于提高相关性分数,而不是影响结果本身。
将should子句添加到bool查询中,如代码清单2-22所示。它会尝试去匹配文档中包含Software标签的搜索文本。
代码清单2-22 使用should查询来提高相关性
GET books/_search
{
"query": {
"bool": {
"must": [{"match": {"author": "Joshua"}}],
"must_not":[{"range":{"amazon_rating":{"lt":4.7}}}],
"should": [{"match": {"tags": "Software"}}] ←--- 带有match查询的should子句
}
}
}
此查询返回了相关性分数提高后的结果(此处省略),结果中的分数是2.267993,而之前的分数是1.9459882(你可以执行查询来观察分数)。
如果感兴趣,不妨将查询条件改为包含一个不匹配的词(如tags等于"Recipes"),然后重新执行查询。虽然查询不会失败,但分数将保持不变,这证明should查询只影响分数。
接下来我们就来看一下filter子句的实际应用,它的作用与must子句完全相同,但不影响分数。
2.5.5 filter子句
让我们进一步改进我们的查询,这次过滤掉2015年之前出版的书(也就是说,我们不希望任何2015年之前出版的书出现在结果集中)。我们可以使用filter子句来达到这个目的,任何不匹配过滤条件的结果都会被丢弃。代码清单2-23中的查询添加了带有release_date查询条件的filter子句。
代码清单2-23 一个不影响相关性的filter子句
GET books/_search { "query": { "bool": { "must": [{"match": {"author": "Joshua"}}], "must_not":[{"range":{"amazon_rating":{"lt":4.7}}}], "should": [{"match": {"tags": "Software"}}], "filter":[{"range":{"release_date":{"gte": "2015-01-01"}}}]} ←--- 带有range查询的filter子句 } }
此查询只会返回一本书,即Effective Java,因为这是我们的索引中唯一匹配bool查询中所有3个子句的书。如果在Kibana中执行这个查询,你会发现输出结果中的分数没有变化。filter子句不会影响分数:它在过滤(filter)上下文中执行,意味着分数不会被改变(关于上下文的更多内容参见第8章)。
最后,我们还希望找到Joshua撰写的第3版的书。为此,我们要更新一下filter子句,在其中添加一个term查询,如代码清单2-24所示。
代码清单2-24 带有额外filter子句的bool查询
GET books/_search { "query": { "bool": { "must": [{"match": {"author": "Joshua"}}], "must_not":[{"range":{"amazon_rating":{"lt":4.7}}}], "should": [{"match": {"tags": "Software"}}], "filter":[ {"range":{"release_date":{"gte": "2015-01-01"}}}, {"term": {"edition": 3}} ←--- filter子句中的term查询 ]} } }
此查询是全文查询和词项级查询的组合,它们协同工作以满足我们的复杂要求。bool查询是一种瑞士军刀般的搜索工具。在第11章中,我们将用大量的篇幅来讨论各种复合查询、选项和技巧,以增强我们对这个工具箱的理解。
到目前为止,我们已经初步实现了一些简单的搜索功能。搜索还可用于分析。Elasticsearch 可以帮助我们获取统计数据和聚合结果,并使用条形图、热力图、地图、标签云等对数据进行可视化展示。
搜索有助于我们大海捞针,而聚合能让我们从宏观层面上对数据进行总结,如最近1小时的服务器错误总数、第三季度的平均图书销量、按票房分类的电影等。我将在第13章中介绍聚合和其他功能,这里先看一些简单的例子。