> 技术文档 > 掌握 ElasticSearch 精准查询:Term Query 与 Filter 详解_elasticsearch term

掌握 ElasticSearch 精准查询:Term Query 与 Filter 详解_elasticsearch term


掌握 ElasticSearch 精准查询:Term Query 与 Filter 详解

    • 一、引言 (Introduction)
    • 二、准备工作:创建索引和添加示例数据
    • 三、Term Query:精准匹配
      • 3.1 `term` 查询:单个值的精准匹配
      • 3.2 `terms` 查询:多个值的精准匹配
      • 3.3 `term` vs. `match_phrase`
    • 四、Filter:高效过滤
      • 4.1 什么是 Filter?
      • 4.2 Query vs. Filter
    • 5. 结合使用 Term 和 Filter
    • 六、总结 (Conclusion)

一、引言 (Introduction)

在信息检索的世界里,我们常常面临两种不同但又互补的需求:

  1. 全文检索 (Full-text Search): 就像你在 Google 或百度中输入一个关键词,搜索引擎会返回一系列相关的网页。这种搜索方式关注的是文档与查询之间的 相关性,它会考虑词频、词的位置等因素,对结果进行排序。Elasticsearch 中的 match 查询(如上一篇博客所述)就是典型的全文检索方式。

  2. 精准查询 (Exact Value Search): 想象一下,你正在一个电商网站上浏览商品,你只想看 “在售” 状态的商品,或者只想找 ID 为 “12345” 的特定商品。这种情况下,你关心的不是商品与查询的 相关程度,而是商品是否 完全符合 你的要求。这就是精准查询的用武之地。

Elasticsearch 作为一款强大的搜索引擎,不仅擅长全文检索,也提供了强大的精准查询功能。在本文中,我们将深入探讨两种核心的精准查询方式:Term QueryFilter

  • Term Query: 用于查找某个字段的值与查询值 完全相等 的文档。它不会对查询值进行分词,而是直接进行精确匹配。
  • Filter: 用于筛选符合特定条件的文档,但 不计算相关性得分。它只关心文档是否匹配条件,不关心匹配程度,因此通常比计算得分的查询(如 match)更高效。

通过本文,你将掌握 Term Query 和 Filter 的基本概念、用法、区别以及它们在实际应用中的价值。

二、准备工作:创建索引和添加示例数据

在开始学习查询之前,我们需要先创建一个索引并添加一些示例数据。请确保你已经安装并启动了 Elasticsearch 7.10。推荐使用 Kibana 的 Dev Tools 来执行以下操作。

  1. 创建索引 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\" } } }}
  2. 添加示例数据:

    我们使用 _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”。

  1. 准备数据:

PUT my_index
{
“mappings”: {
“properties”: {
“description”: {
“type”: “text”
}
}
}
}

POST my_index/_doc
{
“description”: “The quick brown fox jumps over the lazy dog”
}
```

  1. 使用 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 字段分词后的一个词项。

  1. 使用 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 内部,你可以使用各种查询,如 termtermsrangeexistsbool 等,就像在普通的 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 之间的在售产品。我们可以结合使用 termrangebool 查询,并将 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 子句中,它不会影响文档的得分,只起到过滤作用。
  • 最终返回的结果是同时满足 mustfilter 条件的文档。

关于 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 查询,学习如何构建更复杂的查询组合。