在信息检索的世界里,我们常常面临两种不同但又互补的需求:
全文检索 (Full-text Search): 就像你在 Google 或百度中输入一个关键词,搜索引擎会返回一系列相关的网页。这种搜索方式关注的是文档与查询之间的 相关性,它会考虑词频、词的位置等因素,对结果进行排序。Elasticsearch 中的 match
查询(如上一篇博客所述)就是典型的全文检索方式。
精准查询 (Exact Value Search): 想象一下,你正在一个电商网站上浏览商品,你只想看 “在售” 状态的商品,或者只想找 ID 为 “12345” 的特定商品。这种情况下,你关心的不是商品与查询的 相关程度,而是商品是否 完全符合 你的要求。这就是精准查询的用武之地。
Elasticsearch 作为一款强大的搜索引擎,不仅擅长全文检索,也提供了强大的精准查询功能。在本文中,我们将深入探讨两种核心的精准查询方式: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
查询:单个值的精准匹配基本概念: 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
查询不会对查询值分词。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"
}
}
]
}
}
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(过滤器)是 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"]
}
}
}
}
}
为了更好地理解 Filter 的作用和优势,我们将它与 Query 进行对比:
特性 | Query | Filter |
---|---|---|
核心思想 | 过程导向:关心文档与查询的 相关程度,计算相关性得分(_score )。 |
结果导向:只关心文档是否 匹配 过滤条件,不计算得分。 |
性能 | 通常较慢,因为需要计算得分。 | 通常较快,因为不计算得分,且结果可以被缓存。 |
缓存 | 默认情况下不缓存结果。 | 自动缓存结果,提高查询效率。 |
使用场景 | 当你需要根据相关性得分对文档进行排序时。 | 当你只关心文档是否匹配,不关心匹配程度,且过滤条件不影响文档的排序时。 |
当你需要执行全文检索,且查询条件会影响文档的排序时(例如,使用 match 查询搜索包含特定关键词的文档)。 |
当你需要对结果进行过滤,且过滤条件不影响文档的排序时(例如,筛选特定状态、类别或范围的文档)。 |
何时使用 Filter?
何时使用 Query?
match
查询搜索包含特定关键词的文档)。在实际应用中,Query 和 Filter 经常 结合使用。例如,你可以使用 Query 来查找与关键词相关的文档,然后使用 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
查询的用法和更多高级特性。
在本文中,我们深入探讨了 ElasticSearch 7.10 中的两种核心精准查询方式:Term Query 和 Filter。
keyword
类型字段的精确匹配。terms
查询是 term
查询的扩展,允许指定多个值进行匹配。range
查询允许进行范围查询constant_score
查询结合使用。全文检索 vs. 精准查询:
特性 | 全文检索 (如 match ) |
精准查询 (如 term , Filter) |
---|---|---|
关注点 | 文档与查询的 相关性 | 文档是否 完全符合 条件 |
分词 | 对查询值进行分词 | 不对查询值分词 (Term Query) |
得分 | 计算相关性得分 (_score ) |
不计算得分 (Filter) 或固定得分 (constant_score) |
适用场景 | 查找与关键词 相关 的文档 | 查找与特定值 完全匹配 的文档,或进行数据过滤 |
性能 | 相对较低,因为需要计算得分 | 相对较高,因为不计算得分,且 Filter 可缓存 |
最佳实践:
bool
查询,我们将在下一章节详细介绍)。希望通过本文,你已经对 Elasticsearch 中的 Term Query 和 Filter 有了深入的理解。在下一章节中,我们将深入探讨 bool
查询,学习如何构建更复杂的查询组合。