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

发布于:2025-02-24 ⋅ 阅读:(17) ⋅ 点赞:(0)

一、引言 (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 查询,学习如何构建更复杂的查询组合。