掌握 ElasticSearch 精准查询:Term Query 与 Filter 详解
一、引言 (Introduction)
在信息检索的世界里,我们常常面临两种不同但又互补的需求:
全文检索 (Full-text Search): 就像你在 Google 或百度中输入一个关键词,搜索引擎会返回一系列相关的网页。这种搜索方式关注的是文档与查询之间的 相关性,它会考虑词频、词的位置等因素,对结果进行排序。Elasticsearch 中的
match
查询(如上一篇博客所述)就是典型的全文检索方式。精准查询 (Exact Value Search): 想象一下,你正在一个电商网站上浏览商品,你只想看 “在售” 状态的商品,或者只想找 ID 为 “12345” 的特定商品。这种情况下,你关心的不是商品与查询的 相关程度,而是商品是否 完全符合 你的要求。这就是精准查询的用武之地。
Elasticsearch 作为一款强大的搜索引擎,不仅擅长全文检索,也提供了强大的精准查询功能。在本文中,我们将深入探讨两种核心的精准查询方式:Term Query 和 Filter。
- Term Query: 用于查找某个字段的值与查询值 完全相等 的文档。它不会对查询值进行分词,而是直接进行精确匹配。
- Filter: 用于筛选符合特定条件的文档,但 不计算相关性得分。它只关心文档是否匹配条件,不关心匹配程度,因此通常比计算得分的查询(如
match
)更高效。
通过本文,你将掌握 Term Query 和 Filter 的基本概念、用法、区别以及它们在实际应用中的价值。
二、准备工作:创建索引和添加示例数据
在开始学习查询之前,我们需要先创建一个索引并添加一些示例数据。请确保你已经安装并启动了 Elasticsearch 7.10。推荐使用 Kibana 的 Dev Tools 来执行以下操作。
创建索引
products
:我们创建一个名为
products
的索引,其中包含以下字段:product_id
(keyword): 产品ID,不分词。status
(keyword): 产品状态(如 “in_stock”, “out_of_stock”, “discontinued”),不分词。category
(keyword): 产品类别(如 “electronics”, “clothing”, “books”),不分词。price
(double): 产品价格。in_stock
(boolean): 是否有库存。launch_date
(date): 产品发布日期。
PUT products { "mappings": { "properties": { "product_id": { "type": "keyword" }, "status": { "type": "keyword" }, "category": { "type": "keyword" }, "price": { "type": "double" }, "in_stock": { "type": "boolean" }, "launch_date": { "type": "date" } } } }
添加示例数据:
我们使用
_bulk
API 批量添加一些产品数据:POST products/_bulk {"index":{"_index": "products"}} {"product_id": "12345", "status": "in_stock", "category": "electronics", "price": 299.99, "in_stock": true, "launch_date": "2023-01-15"} {"index":{"_index": "products"}} {"product_id": "67890", "status": "out_of_stock", "category": "clothing", "price": 49.99, "in_stock": false, "launch_date": "2023-03-10"} {"index":{"_index": "products"}} {"product_id": "13579", "status": "in_stock", "category": "books", "price": 19.99, "in_stock": true, "launch_date": "2023-05-20"} {"index":{"_index": "products"}} {"product_id": "24680", "status": "discontinued", "category": "electronics", "price": 199.99, "in_stock": false, "launch_date": "2022-11-01"} {"index":{"_index": "products"}} {"product_id": "11223", "status": "in_stock", "category": "electronics", "price": 599.99, "in_stock": true, "launch_date": "2023-08-01"} {"index":{"_index": "products"}} {"product_id": "33445", "status": "in_stock", "category": "clothing", "price": 79.99, "in_stock": true, "launch_date": "2023-07-15"}
三、Term Query:精准匹配
3.1 term
查询:单个值的精准匹配
基本概念: term
查询是 Elasticsearch 中最基本的精准查询方式。它用于查找指定字段的值与查询值 完全相等 的文档。需要特别注意的是,term
查询 不会 对查询值进行分词,而是直接将其作为一个整体进行匹配。
语法:
GET index/_search
{
"query": {
"term": {
"field_name": {
"value": "your_exact_value"
}
}
}
}
参数说明:
field_name
: 要搜索的字段名。value
: 要匹配的精确值。
示例:
查找
product_id
为 “12345” 的产品:GET products/_search { "query": { "term": { "product_id": { "value": "12345" } } } }
结果解释: 根据我们添加的数据,这个查询将返回
product_id
为 “12345” 的那一条文档。查找
status
为 “in_stock” 的产品:GET products/_search { "query": { "term": { "status": { "value": "in_stock" } } } }
结果解释: 这个查询将返回所有
status
字段值为 “in_stock” 的产品文档。
Code 运行结果
{
"took" : 0,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 4,
"relation" : "eq"
},
"max_score" : 0.44183272,
"hits" : [
{
"_index" : "products",
"_type" : "_doc",
"_id" : "PjDrMJUBaTLipzfiYli6",
"_score" : 0.44183272,
"_source" : {
"product_id" : "12345",
"status" : "in_stock",
"category" : "electronics",
"price" : 299.99,
"in_stock" : true,
"launch_date" : "2023-01-15"
}
},
{
"_index" : "products",
"_type" : "_doc",
"_id" : "QDDrMJUBaTLipzfiYli6",
"_score" : 0.44183272,
"_source" : {
"product_id" : "13579",
"status" : "in_stock",
"category" : "books",
"price" : 19.99,
"in_stock" : true,
"launch_date" : "2023-05-20"
}
},
{
"_index" : "products",
"_type" : "_doc",
"_id" : "QjDrMJUBaTLipzfiYli6",
"_score" : 0.44183272,
"_source" : {
"product_id" : "11223",
"status" : "in_stock",
"category" : "electronics",
"price" : 599.99,
"in_stock" : true,
"launch_date" : "2023-08-01"
}
},
{
"_index" : "products",
"_type" : "_doc",
"_id" : "QzDrMJUBaTLipzfiYli6",
"_score" : 0.44183272,
"_source" : {
"product_id" : "33445",
"status" : "in_stock",
"category" : "clothing",
"price" : 79.99,
"in_stock" : true,
"launch_date" : "2023-07-15"
}
}
]
}
}
重要提示:
term
查询对keyword
类型的字段效果最好,因为keyword
字段不会被分词。- 如果你对一个
text
类型的字段使用term
查询,很可能得不到你想要的结果。因为text
字段在索引时会被分词,而term
查询不会对查询值分词。
3.2 terms
查询:多个值的精准匹配
基本概念:
terms
查询是 term
查询的扩展,它允许你指定一个值的列表,只要文档的指定字段与列表中的 任意一个 值完全匹配,该文档就会被返回。这相当于 SQL 中的 IN
操作符。
语法:
GET index/_search
{
"query": {
"terms": {
"field_name": ["value1", "value2", "value3"]
}
}
}
field_name
: 要搜索的字段名。[]
: 一个包含多个值的数组,表示要匹配的多个精确值。
示例:
查找
category
为 “electronics” 或 “appliances” 的产品:GET products/_search { "query": { "terms": { "category": ["electronics", "clothing"] } } }
结果解释: 这个查询将返回所有
category
字段值为 “electronics” 或 “clothing” 的产品文档。根据我们的示例数据:product_id
为 “12345”、“24680” 和 “11223” 的产品 (category 为 “electronics”)product_id
为 “67890” 和 “33445” 的产品 (category 为 “clothing”)
都会被返回。
Code 运行结果
{
...
"hits" : {
"total" : {
"value" : 5,
"relation" : "eq"
},
"max_score" : 1.0,
"hits" : [
{
"_index" : "products",
"_type" : "_doc",
"_id" : "9WhxJpUBFTEr5wdT-FfA",
"_score" : 1.0,
"_source" : {
"product_id" : "12345",
"status" : "in_stock",
"category" : "electronics",
"price" : 299.99,
"in_stock" : true,
"launch_date" : "2023-01-15"
}
},
{
"_index" : "products",
"_type" : "_doc",
"_id" : "-GhxJpUBFTEr5wdT-FfA",
"_score" : 1.0,
"_source" : {
"product_id" : "11223",
"status" : "in_stock",
"category" : "electronics",
"price" : 599.99,
"in_stock" : true,
"launch_date" : "2023-08-01"
}
},
{
"_index" : "products",
"_type" : "_doc",
"_id" : "9mhxJpUBFTEr5wdT-FfA",
"_score" : 1.0,
"_source" : {
"product_id" : "67890",
"status" : "out_of_stock",
"category" : "clothing",
"price" : 49.99,
"in_stock" : false,
"launch_date" : "2023-03-10"
}
},
{
"_index" : "products",
"_type" : "_doc",
"_id" : "-WhxJpUBFTEr5wdT-FfA",
"_score" : 1.0,
"_source" : {
"product_id" : "24680",
"status" : "discontinued",
"category" : "electronics",
"price" : 199.99,
"in_stock" : false,
"launch_date" : "2022-11-01"
}
},
{
"_index" : "products",
"_type" : "_doc",
"_id" : "-mhxJpUBFTEr5wdT-FfA",
"_score" : 1.0,
"_source" : {
"product_id" : "33445",
"status" : "in_stock",
"category" : "clothing",
"price" : 79.99,
"in_stock" : true,
"launch_date" : "2023-07-15"
}
}
]
}
}
3.3 term
vs. match_phrase
为了更好地理解 term
查询的特性,我们将其与上一篇博客中介绍的 match_phrase
查询进行对比:
特性 | term 查询 |
match_phrase 查询 |
---|---|---|
查询类型 | 精准查询 | 全文检索 |
分词 | 不对查询值分词 | 对查询值分词 |
匹配要求 | 字段值与查询值完全相等 | 所有查询词项都必须出现,顺序和邻近度(默认情况下)必须与查询字符串完全一致。可以通过slop 参数调整 |
适用场景 | 查找与特定值完全匹配的文档(如 ID、状态码) | 查找包含特定短语的文档,且对短语中词项的顺序和邻近度有要求 |
假设我们有一个索引 my_index
,其中有一个 description
字段,类型为 text
。我们向该索引添加一个文档,其 description
值为 “The quick brown fox jumps over the lazy dog”。
准备数据:
PUT my_index
{
“mappings”: {
“properties”: {
“description”: {
“type”: “text”
}
}
}
}
POST my_index/_doc
{
“description”: “The quick brown fox jumps over the lazy dog”
}
```
- 使用
term
查询:
示例 1:查询 “quick brown”
GET my_index/_search { "query": { "term": { "description": { "value": "quick brown" } } } }
结果: 这个查询很 不会 返回任何结果(默认分词器下)。因为
term
查询不会对 “quick brown” 进行分词,而description
字段在索引时已经被分词为 “the”, “quick”, “brown”, “fox”, “jumps”, “over”, “the”, “lazy”, “dog” 等词项。没有一个词项与 “quick brown” 完全相等。示例 2:查询 “quick”
GET my_index/_search { "query": { "term": { "description": { "value": "quick" } } } }
结果: 这个查询 会 返回包含 “quick” 作为分词结果的文档。因为 “quick” 是
description
字段分词后的一个词项。
- 使用
match_phrase
查询:
示例 1:查询 “quick brown”
JSON
GET my_index/_search { "query": { "match_phrase": { "description": "quick brown" } } }
结果: 这个查询 会 返回包含 “quick brown” 这个短语的文档。因为
match_phrase
查询会对 “quick brown” 分词,然后要求这两个词项按顺序相邻出现。示例 2:查询 “brown fox jumps”
GET my_index/_search { "query": { "match_phrase": { "description": "brown fox jumps" } } }
结果: 这个查询也会返回文档,因为 “brown”, “fox”, “jumps” 三个词项按照顺序相邻出现。
term
查询适用于keyword
类型字段的精确匹配。对于
text
类型字段,term
查询匹配的是分词后的单个词项,而不是整个字段值。match_phrase
查询适用于text
类型字段的短语匹配,要求词项顺序和邻近度。
四、Filter:高效过滤
4.1 什么是 Filter?
基本概念:
Filter(过滤器)是 Elasticsearch 中一种特殊的查询方式,它用于筛选符合特定条件的文档,但 不计算相关性得分(
_score
)。Filter 的核心思想是 结果导向,它只关心文档是否 匹配 过滤条件,而不关心文档与查询的 相关程度。
由于不计算得分,Filter 通常比计算得分的查询(如
match
)更 高效。此外,Elasticsearch 会自动 缓存 Filter 的结果,进一步提高查询性能。
语法:
Filter 通常与 constant_score
查询结合使用。constant_score
查询会将 Filter 包装起来,并为所有匹配的文档赋予一个固定的得分(默认为 1.0)。
GET _search
{
"query": {
"constant_score": {
"filter": {
"term": {
"status": "in_stock"
}
}
}
}
}
参数说明:
constant_score
: 将 filter 查询包装成为一个不计算分数的查询。filter
: 包含具体的过滤条件。在filter
内部,你可以使用各种查询,如term
、terms
、range
、exists
、bool
等,就像在普通的query
中一样。
示例:
使用
term
Filter 筛选status
为 “in_stock” 的产品:GET products/_search { "query": { "constant_score": { "filter": { "term": { "status": "in_stock" } } } } }
结果解释: 这个查询将返回所有
status
为 “in_stock” 的产品,但所有返回文档的_score
都将是 1.0(或你在constant_score
中指定的其他值)。使用
range
Filter 筛选price
在 100 到 300 之间的产品:GET products/_search { "query": { "constant_score": { "filter": { "range": { "price": { "gte": 100, "lte": 300 } } } } } }
使用
terms
Filter 筛选category
为 “electronics” 或 “clothing” 的产品:
GET products/_search
{
"query": {
"constant_score": {
"filter": {
"terms": {
"category": ["electronics", "clothing"]
}
}
}
}
}
4.2 Query vs. Filter
为了更好地理解 Filter 的作用和优势,我们将它与 Query 进行对比:
特性 | Query | Filter |
---|---|---|
核心思想 | 过程导向:关心文档与查询的 相关程度,计算相关性得分(_score )。 |
结果导向:只关心文档是否 匹配 过滤条件,不计算得分。 |
性能 | 通常较慢,因为需要计算得分。 | 通常较快,因为不计算得分,且结果可以被缓存。 |
缓存 | 默认情况下不缓存结果。 | 自动缓存结果,提高查询效率。 |
使用场景 | 当你需要根据相关性得分对文档进行排序时。 | 当你只关心文档是否匹配,不关心匹配程度,且过滤条件不影响文档的排序时。 |
当你需要执行全文检索,且查询条件会影响文档的排序时(例如,使用 match 查询搜索包含特定关键词的文档)。 |
当你需要对结果进行过滤,且过滤条件不影响文档的排序时(例如,筛选特定状态、类别或范围的文档)。 |
何时使用 Filter?
- 当你只关心文档是否匹配过滤条件,而 不关心 匹配程度(相关性得分)时。
- 当你需要对结果进行 过滤,并且过滤条件 不影响 文档的排序时。
- 当你需要 提高查询性能 时,特别是对于经常使用的过滤条件,Filter 的缓存机制可以带来显著的性能提升。
何时使用 Query?
- 当你需要根据 相关性得分 对文档进行 排序 时。
- 当你需要执行 全文检索,并且查询条件 会影响 文档的排序时(例如,使用
match
查询搜索包含特定关键词的文档)。
在实际应用中,Query 和 Filter 经常 结合使用。例如,你可以使用 Query 来查找与关键词相关的文档,然后使用 Filter 来过滤出符合特定条件的文档。
5. 结合使用 Term 和 Filter
在实际应用中,我们经常需要将 Term 查询与其他查询或过滤器结合起来,以构建更复杂的查询逻辑。Filter 尤其适合与 Term 查询结合,因为它们都关注精确匹配,并且 Filter 可以提高查询效率。
示例:
假设我们需要找到 products
索引中所有类别为 “electronics” 且价格在 200 到 600 之间的在售产品。我们可以结合使用 term
、range
和 bool
查询,并将 range
查询放在 filter
子句中:
GET products/_search
{
"query": {
"bool": {
"must": [
{
"term": {
"category": "electronics"
}
},
{
"term": {
"status": "in_stock"
}
}
],
"filter": [
{
"range": {
"price": {
"gte": 200,
"lte": 600
}
}
}
]
}
}
}
结果解释:
bool
查询:用于组合多个查询子句。我们将在下一节详细学习bool
查询。must
子句:表示必须匹配的条件。这里我们使用了两个term
查询,要求category
为 “electronics” 且status
为 “in_stock”。filter
子句:表示过滤条件,不影响评分。这里我们使用了一个range
查询,要求price
在 200 到 600 之间。
- 由于
range
查询位于filter
子句中,它不会影响文档的得分,只起到过滤作用。 - 最终返回的结果是同时满足
must
和filter
条件的文档。
关于 bool
查询的进一步说明:
在上面的示例中,我们使用了 bool
查询来组合 Query 和 Filter。bool
查询提供了一种灵活的方式来组合多个查询子句:
must
: 类似于“与” (AND) 关系,要求所有子句都必须匹配。子句可以是 Query 或 Filter。filter
: 用于放置 Filter 子句,这些子句不影响评分,只进行过滤。should
: 类似于“或” (OR) 关系,至少有一个子句匹配即可。子句可以是 Query 或 Filter。must_not
: 类似于“非” (NOT) 关系,要求所有子句都不匹配。子句可以是 Query 或 Filter。
通过灵活组合 bool
查询的这四个子句,我们可以构建出非常复杂的查询逻辑,同时利用 Filter 来提高查询效率。我们将在下一章节详细介绍 bool
查询的用法和更多高级特性。
六、总结 (Conclusion)
在本文中,我们深入探讨了 ElasticSearch 7.10 中的两种核心精准查询方式:Term Query 和 Filter。
- Term Query:
- 用于查找某个字段的值与查询值 完全相等 的文档。
- 不对查询值进行分词,直接进行精确匹配。
- 适用于
keyword
类型字段的精确匹配。 terms
查询是term
查询的扩展,允许指定多个值进行匹配。range
查询允许进行范围查询
- Filter:
- 用于筛选符合特定条件的文档,但 不计算相关性得分。
- 结果导向,只关心文档是否匹配,不关心匹配程度。
- 通常比计算得分的查询更 高效,且结果可以被 缓存。
- 常与
constant_score
查询结合使用。
全文检索 vs. 精准查询:
特性 | 全文检索 (如 match ) |
精准查询 (如 term , Filter) |
---|---|---|
关注点 | 文档与查询的 相关性 | 文档是否 完全符合 条件 |
分词 | 对查询值进行分词 | 不对查询值分词 (Term Query) |
得分 | 计算相关性得分 (_score ) |
不计算得分 (Filter) 或固定得分 (constant_score) |
适用场景 | 查找与关键词 相关 的文档 | 查找与特定值 完全匹配 的文档,或进行数据过滤 |
性能 | 相对较低,因为需要计算得分 | 相对较高,因为不计算得分,且 Filter 可缓存 |
最佳实践:
- 对于精确匹配的场景,优先使用 Term Query 和 Filter。
- 对于不需要相关性得分的过滤,使用 Filter。
- 结合使用 Query 和 Filter,构建复杂的查询逻辑(可以使用
bool
查询,我们将在下一章节详细介绍)。 - 充分利用 Filter 的缓存机制,提高查询效率。
希望通过本文,你已经对 Elasticsearch 中的 Term Query 和 Filter 有了深入的理解。在下一章节中,我们将深入探讨 bool
查询,学习如何构建更复杂的查询组合。