在信息检索和数据分析的场景中,我们经常需要面对复杂的查询需求。简单的关键词搜索可能无法满足我们的要求,我们需要更精细的控制,例如:
为了应对这些复杂的查询需求,Elasticsearch 提供了 bool
查询。bool
查询就像一个强大的工具箱,它允许你将多个查询条件组合在一起,形成更复杂的查询逻辑。你可以将 bool
查询看作是乐高积木,通过组合不同的积木块(查询子句),你可以搭建出各种各样的结构(查询逻辑)。
本文将深入探讨 Elasticsearch 7.10 版本中的 bool
查询。你将学习到:
bool
查询的基本概念和工作原理。bool
查询的四种核心子句:must
、filter
、should
、must_not
。minimum_should_match
参数控制 should
子句的行为。bool
查询,构建更复杂的查询逻辑。bool
查询如何影响文档的相关性得分。bool
查询。bool
查询是 Elasticsearch 中一种复合查询(compound query),它允许你将多个查询子句(query clause)组合在一起。这些子句可以是任何类型的查询,例如 match
、term
、range
等,甚至是另一个 bool
查询(嵌套)。
bool
查询的核心思想是 “more_matches_is_better”,也就是说,文档匹配的子句越多,它的相关性得分(_score
)就越高。这使得 bool
查询非常适合用于构建复杂的查询逻辑,同时兼顾查询结果的相关性排序。
为了帮助你更好地理解 bool
查询,我们可以将其类比为编程语言中的逻辑运算符:
must
类似于逻辑与 (AND):要求所有条件都必须满足。filter
也类似于逻辑与(AND):要求所有条件都必须满足。should
类似于逻辑或 (OR):至少有一个条件满足即可。must_not
类似于逻辑非 (NOT):要求所有条件都不满足。bool
查询包含四种核心子句,每种子句都有不同的作用和对得分的影响:
子句 | 作用 | 对得分的影响 |
---|---|---|
must |
必须匹配。文档必须满足 must 子句中的所有条件。 |
匹配的 must 子句越多,文档得分越高。 |
filter |
必须匹配(过滤器上下文)。文档必须满足 filter 子句中的所有条件。 |
不影响得分。 |
should |
可以匹配。文档应该满足 should 子句中的一个或多个条件。 |
匹配的 should 子句越多,文档得分越高。 |
must_not |
必须不匹配(过滤器上下文)。文档必须不满足 must_not 子句中的所有条件。 |
不影响得分。 |
导出到 Google 表格
要点:
must
和 filter
子句都要求文档必须匹配,但 filter
子句不参与得分计算,因此通常比 must
子句更高效。should
子句是可选的,但匹配 should
子句会提高文档的得分。must_not
子句用于排除文档,它也不参与得分计算。filter
和 must_not
子句处于_过滤器上下文_中,这意味着它们不计算得分,并且 Elasticsearch 会自动缓存这些子句的结果,以提高后续查询的性能。bool
查询的基本语法结构如下:
GET /_search
{
"query": {
"bool" : {
"must" : [
{ /* 查询 1 */ },
{ /* 查询 2 */ }
],
"filter": [
{ /* 过滤条件 1 */ },
{ /* 过滤条件 2 */ }
],
"should" : [
{ /* 查询 3 */ },
{ /* 查询 4 */ }
],
"must_not" : [
{ /* 排除条件 1 */ },
{ /* 排除条件 2 */ }
]
}
}
}
解释:
query
: 这是 ElasticSearch 查询 DSL 的根元素。bool
: 表示这是一个 bool
查询。must
、filter
、should
、must_not
: 这是 bool
查询的四种子句,每个子句都可以包含一个或多个查询条件(可以是任何类型的查询,例如 match
、term
、range
等)。接下来,我们将深入探讨 must
、filter
、should
和 must_not
四种子句的用法,并通过示例演示如何在实际场景中应用它们。
数据准备:
首先,我们创建一个名为 articles
的索引,并添加一些示例数据。我们将使用这些数据来演示 bool
查询的各种用法。
PUT articles
{
"mappings": {
"properties": {
"title": { "type": "text" },
"content": { "type": "text" },
"category": { "type": "keyword" },
"status": { "type": "keyword" },
"author_id": { "type": "keyword" },
"is_featured": { "type": "boolean" },
"discount": { "type": "double" },
"created_at": { "type": "date" },
"out_of_stock":{ "type":"boolean"}
}
}
}
POST articles/_bulk
{"index":{"_index": "articles"}}
{"title": "Elasticsearch Tutorial for Beginners", "content": "This tutorial covers the basics of Elasticsearch.", "category": "technology", "status": "published", "author_id": "123", "is_featured": true, "discount": 0.0, "created_at": "2023-10-26", "out_of_stock": false}
{"index":{"_index": "articles"}}
{"title": "Advanced Logstash Techniques", "content": "Learn advanced techniques for processing logs with Logstash.", "category": "technology", "status": "published", "author_id": "456", "is_featured": false, "discount": 10.0, "created_at": "2023-10-27", "out_of_stock": false}
{"index":{"_index": "articles"}}
{"title": "Introduction to Kibana", "content": "Visualize your Elasticsearch data with Kibana.", "category": "technology", "status": "draft", "author_id": "123", "is_featured": false, "discount": 0.0, "created_at": "2023-10-28", "out_of_stock": false}
{"index":{"_index": "articles"}}
{"title": "Elasticsearch and Logstash Integration", "content": "Integrate Elasticsearch and Logstash for log management.", "category": "devops", "status": "published", "author_id": "789", "is_featured": true, "discount": 20.0, "created_at": "2023-10-29", "out_of_stock": false}
{"index":{"_index": "articles"}}
{"title": "Elasticsearch for Java Developers", "content": "A comprehensive guide to using Elasticsearch with Java.", "category": "technology", "status": "published", "author_id": "123", "is_featured": false, "discount": 0.0, "created_at": "2023-10-25", "out_of_stock": true}
{"index":{"_index": "articles"}}
{"title": "Elasticsearch for Python Developers", "content": "Learn how to use Elasticsearch with Python.", "category": "technology", "status": "published", "author_id": "456", "is_featured": true, "discount": 5.0, "created_at": "2023-10-24", "out_of_stock": false}
must
子句must
子句要求文档 必须 匹配其中包含的所有查询条件。可以将其理解为逻辑“与” (AND) 操作。must
子句中的查询条件会 增加 文档的相关性得分(_score
)。匹配的 must
子句越多,得分越高。示例:
查找同时包含 “Elasticsearch” 和 “tutorial” 关键词的文章(使用 match
查询):
GET articles/_search
{
"query": {
"bool": {
"must": [
{ "match": { "title": "Elasticsearch" } },
{ "match": { "content": "tutorial" } }
]
}
}
}
这个查询要求文档的 title
字段必须包含 “Elasticsearch”,并且 content
字段必须包含 “tutorial”。只有同时满足这两个条件的文章才会被返回。根据我们创建的数据,只有第一条数据 符合这两个条件:
查找 category
为 “technology” 且 status
为 “published” 的文章(结合 term
查询):
GET articles/_search
{
"query": {
"bool": {
"must": [
{ "term": { "category": "technology" } },
{ "term": { "status": "published" } }
]
}
}
}
结果:根据我们创建的数据,第一,二,五,六条会被搜索出来
filter
子句filter
子句要求文档 必须 匹配其中包含的所有查询条件,但与 must
子句不同的是,filter
子句 不参与 相关性得分计算。它只起到过滤的作用。filter
子句的文档得分都相同(默认为 1.0,可以通过 constant_score
查询修改)。filter
子句的执行速度通常比 must
子句更快。filter
子句的结果,以提高后续相同过滤条件的查询性能。示例:
在搜索结果中过滤出 created_at
在过去三年内并且标题包含「Elasticsearch」的文章(使用 range
查询):
GET articles/_search
{
"query": {
"bool": {
"must": [
{ "match": { "title": "Elasticsearch" } }
],
"filter": [
{ "range": { "created_at": { "gte": "now-3y/y" } } }
]
}
}
}
结果:根据我们创建的数据,第一,二,三,四条会被搜索出来
在搜索结果中过滤出 author_id
为 “123” 的文章(使用 term
查询):
GET articles/_search
{
"query": {
"bool": {
"must": [
{ "match": { "content": "Elasticsearch" } }
],
"filter": [
{ "term": { "author_id": "123" } }
]
}
}
}
结果:根据我们创建的数据,有 3 条会被搜索出来
should
子句should
子句表示文档 应该 匹配其中包含的查询条件,但 不是必须 的。可以将其理解为逻辑“或” (OR) 操作。should
子句中的查询条件会 增加 文档的相关性得分。匹配的 should
子句越多,得分越高。bool
查询只包含 should
子句,而没有 must
或 filter
子句,则至少需要匹配一个 should
子句(minimum_should_match
默认为 1)。bool
查询包含 must
或 filter
子句,则 should
子句变为可选的加分项,即使一个 should
子句都不匹配,文档也会被返回(只要满足 must
或 filter
条件)。示例:
搜索文章,优先显示标题中包含 “Elasticsearch” 或 “Logstash” 的文章(使用 match
查询):
GET articles/_search
{
"query": {
"bool": {
"should": [
{ "match": { "title": "Elasticsearch" } },
{ "match": { "title": "Logstash" } }
]
}
}
}
这个查询会返回 title
中包含 “Elasticsearch” 或 “Logstash” 或两者都包含的文章。包含的词项越多,得分越高。由于没有 must
或 filter
子句,至少需要匹配一个 should
子句(minimum_should_match
默认为 1)。根据我们创建的数据,有 5 条会被搜索出来。
搜索文章,优先显示 is_featured
为 true
或 discount
大于 0 的文章:
GET articles/_search
{
"query": {
"bool": {
"must": [
{ "match": { "title": "Elasticsearch" } }
],
"should": [
{ "term": { "is_featured": true } },
{ "range": { "discount": { "gt": 0 } } }
]
}
}
}
这个查询首先使用 match
查询查找 title
中包含 “Elasticsearch” 的文章, 然后,它使用 should
子句来提升 is_featured
为 true
或 discount
大于 0 的文章的得分。即使文章不满足 should
子句中的任何条件,只要满足 must
子句,仍然会被返回。根据我们创建的数据,第一,四,五,六条会被搜索出来,并且第一,四,六条分数会更高。
must_not
子句must_not
子句要求文档 必须不 匹配其中包含的所有查询条件。可以将其理解为逻辑“非” (NOT) 操作。filter
子句类似,must_not
子句不参与相关性得分计算。must_not
子句处于过滤器上下文中,因此 Elasticsearch 会自动缓存其结果以提高性能。示例:
搜索文章,排除 status
为 “draft” 的文章(使用 term
查询):
GET articles/_search
{
"query": {
"bool": {
"must": [
{ "match": { "title": "Elasticsearch" } }
],
"must_not": [
{ "term": { "status": "draft" } }
]
}
}
}
这个查询首先使用 match
查询查找 title
包含 “Elasticsearch” 的文章(must
子句),然后使用 must_not
子句排除 status
为 “draft” 的文章。只有满足 must
条件且不满足 must_not
条件的文章才会被返回。根据我们创建的数据,第一,四,五,六条会被搜索出来。
搜索文章,排除 out_of_stock
为 true
且不是is_featured的文章:
GET articles/_search
{
"query": {
"bool": {
"must": [
{"match":{"title": "Elasticsearch"}}
],
"must_not": [
{ "term": { "out_of_stock": true } },
{"term": {"is_featured": false}}
]
}
}
}
这个查询首先使用 match
查询查找 title
包含 “Elasticsearch” 的文章, 然后使用 must_not
子句排除 out_of_stock
为 true
且不是is_featured的文章。根据我们创建的数据,第一,四,六条会被搜索出来。
minimum_should_match
参数minimum_should_match
?minimum_should_match
参数用于控制 should
子句的行为。它指定了在 bool
查询中,至少需要匹配多少个 should
子句,文档才会被认为是匹配的。
你可以将 minimum_should_match
设置为:
should
子句的数量。should
子句占总 should
子句数量的百分比(向下取整)。minimum_should_match
的默认值取决于 bool
查询的结构:
should
子句: 如果 bool
查询只包含 should
子句,而没有 must
或 filter
子句,则 minimum_should_match
默认为 1。这意味着至少需要匹配一个 should
子句。must
或 filter
子句: 如果 bool
查询包含 must
或 filter
子句,则 minimum_should_match
默认为 0。这意味着 should
子句变为可选的加分项,即使一个 should
子句都不匹配,文档也会被返回(只要满足 must
或 filter
条件)。设置 minimum_should_match
为具体数值:
假设我们要搜索文章,要求标题中包含 “Elasticsearch” 或 “Logstash” 或 “Kibana”,并且至少需要匹配其中两个关键词:
GET articles/_search
{
"query": {
"bool": {
"should": [
{ "match": { "title": "Elasticsearch" } },
{ "match": { "title": "Logstash" } },
{ "match": { "title": "Kibana" } }
],
"minimum_should_match": 2
}
}
}
这个查询要求至少匹配两个 should
子句。例如,如果一篇文章的标题只包含 “Elasticsearch”,则不会被返回;如果标题包含 “Elasticsearch” 和 “Logstash”,则会被返回。
设置 minimum_should_match
为百分比:
假设我们要搜索文章,要求标题或内容中包含 “Elasticsearch”、“Logstash”、“Kibana” 或 “Beats”,并且至少需要匹配其中 50% 的关键词:
GET articles/_search
{
"query": {
"bool": {
"should": [
{ "match": { "title": "Elasticsearch" } },
{ "match": { "title": "Logstash" } },
{ "match": { "content": "Kibana" } },
{ "match": { "content": "Beats" } }
],
"minimum_should_match": "50%"
}
}
}
这个查询有 4 个 should
子句,minimum_should_match
设置为 “50%”,这意味着至少需要匹配 4 * 50% = 2 个子句。
当 should
和 must
或者 filter
一起出现的时候,should
会退化为一个加分项,如果一个文档不满足任何 should
中的条件,但是满足 must
中的条件,也是会被搜索出来的。
嵌套 Bool 查询是指在一个 bool
查询的子句(must
、filter
、should
、must_not
)中,再嵌套另一个 bool
查询。通过这种嵌套,你可以构建出非常复杂的查询逻辑,以满足各种细粒度的搜索需求。
嵌套 Bool 查询通常用于以下场景:
bool
查询的 should
子句中,而将另一些条件放在内层 bool
查询的 must
子句中。假设我们需要实现以下搜索需求:
搜索标题中包含 “Elasticsearch” 且 (作者 ID 为 “123” 或为精选文章) 且发布状态不是 “draft” 的文章。
这个需求的逻辑关系比较复杂,可以用如下的逻辑表达式来表示:
(title 包含 "Elasticsearch") AND ((author_id = "123") OR (is_featured = true)) AND (status != "draft")
我们可以使用嵌套 bool
查询来实现这个需求:
GET articles/_search
{
"query": {
"bool": {
"must": [
{ "match": { "title": "Elasticsearch" } },
{
"bool": {
"should": [
{ "term": { "author_id": "123" } },
{ "term": { "is_featured": true } }
],
"minimum_should_match": 1
}
},
{
"bool": {
"must_not": [
{ "term": { "status": "draft" } }
]
}
}
]
}
}
}
解释:
bool
查询:
must
子句组合三个条件:
match
查询:标题包含 “Elasticsearch”。bool
查询:作者 ID 为 “123” 或为精选文章。bool
查询 (使用must_not
): 排除 status 为 draft 的文章。bool
查询(作者/精选):
should
子句组合两个条件:
term
查询:作者 ID 为 “123”。term
查询:is_featured
为 true
。minimum_should_match
: 1,表示至少需要匹配一个 should
子句。bool
查询 (排除状态):
must_not
子句,其内部是一个 term
查询,用于排除 status
为 draft
。bool
查询的子句对文档相关性得分(_score
)的影响,我们已经在前面的章节中详细讨论过:
must
子句: 匹配的 must
子句越多,文档得分越高。filter
子句: 不影响得分。should
子句: 匹配的 should
子句越多,文档得分越高。must_not
子句: 不影响得分。总结: must
和 should
子句会影响得分,而 filter
和 must_not
子句不影响得分。
在某些情况下,你可能希望调整不同查询子句对得分的贡献。例如,你可能希望标题中包含关键词的文档比内容中包含关键词的文档得分更高。
你可以使用 boost
参数来调整查询子句的权重。boost
参数是一个正数,用于增加或减少查询子句的相对重要性。boost
的默认值为 1.0。
示例:
假设我们搜索文章,希望标题中包含 “Elasticsearch” 的文档比内容中包含 “Elasticsearch” 的文档得分更高:
GET articles/_search
{
"query": {
"bool": {
"should": [
{ "match": { "title": { "query": "Elasticsearch", "boost": 2.0 } } },
{ "match": { "content": "Elasticsearch" } }
]
}
}
}
在这个查询中,我们为标题的 match
查询设置了 boost
值为 2.0。这意味着标题中包含 “Elasticsearch” 的文档的得分将比内容中包含 “Elasticsearch” 的文档的得分更高(大约是两倍)。
注意: boost
参数只是一个提示,Elasticsearch 不保证得分的精确比例。实际得分还受到其他因素的影响,例如词频、逆文档频率等。
bool
查询是 Elasticsearch 中一种非常强大且灵活的查询工具,它可以帮助你构建复杂的查询逻辑,以满足各种搜索需求。通过组合 must
、filter
、should
和 must_not
四种子句,以及使用 minimum_should_match
参数和嵌套 bool
查询,你可以实现对搜索结果的精细控制。
希望本文能够帮助你深入理解 Elasticsearch 7.10 版本中的 bool
查询,并在实际应用中灵活运用。