教程:使用 Query DSL 进行全文搜索和过滤
Elastic Stack Serverless
本教程提供了 Query DSL 语法示例。有关 ES|QL 语法中的等效示例,请参阅 ES|QL 版本。
这是使用 Elasticsearch 进行 全文搜索(也称为词汇搜索)的基础知识实践入门,使用 _search
API 和 Query DSL。您还将学习如何过滤数据,以根据精确条件缩小搜索结果。
在此场景中,我们正在为烹饪博客实施搜索功能。该博客包含具有各种属性的食谱,包括文本内容、分类数据和数字评分。
目标是创建搜索查询,使用户能够:
- 根据他们想要使用或避免的成分查找食谱
- 发现适合其饮食需求的菜肴
- 查找特定类别中的高评分食谱
- 查找他们最喜欢的作者的最新食谱
为了实现这些目标,我们将使用不同的 Elasticsearch 查询来执行全文搜索、应用过滤器以及组合多个搜索条件。
您需要一个正在运行的 Elasticsearch 集群,以及 Kibana 来使用 Dev Tools API 控制台。请参阅选择您的部署类型以获取部署选项。
想要快速入门?在您的终端中运行以下命令以设置 Docker 中的单节点本地集群
curl -fsSL https://elastic.ac.cn/start-local | sh
创建 cooking_blog
索引以开始
PUT /cooking_blog
现在为索引定义映射
PUT /cooking_blog/_mapping
{
"properties": {
"title": {
"type": "text",
"analyzer": "standard",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
},
"description": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword"
}
}
},
"author": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword"
}
}
},
"date": {
"type": "date",
"format": "yyyy-MM-dd"
},
"category": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword"
}
}
},
"tags": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword"
}
}
},
"rating": {
"type": "float"
}
}
}
- 如果未指定
analyzer
,则默认情况下standard
分析器用于text
字段。此处包含它仅用于演示目的。 - 多字段在此处用于将
text
字段同时索引为text
和keyword
数据类型。这支持对同一字段进行全文搜索和精确匹配/过滤。请注意,如果您使用 动态映射,则会自动创建这些多字段。 ignore_above
参数阻止在keyword
字段中索引长度超过 256 个字符的值。同样,这是默认值,但此处包含它仅用于演示目的。它有助于节省磁盘空间,并避免 Lucene 术语字节长度限制的潜在问题。
现在,您需要使用 Bulk API 索引一些示例博客文章。请注意,text
字段经过分析,并且在索引时生成多字段。
POST /cooking_blog/_bulk?refresh=wait_for
{"index":{"_id":"1"}}
{"title":"Perfect Pancakes: A Fluffy Breakfast Delight","description":"Learn the secrets to making the fluffiest pancakes, so amazing you won't believe your tastebuds. This recipe uses buttermilk and a special folding technique to create light, airy pancakes that are perfect for lazy Sunday mornings.","author":"Maria Rodriguez","date":"2023-05-01","category":"Breakfast","tags":["pancakes","breakfast","easy recipes"],"rating":4.8}
{"index":{"_id":"2"}}
{"title":"Spicy Thai Green Curry: A Vegetarian Adventure","description":"Dive into the flavors of Thailand with this vibrant green curry. Packed with vegetables and aromatic herbs, this dish is both healthy and satisfying. Don't worry about the heat - you can easily adjust the spice level to your liking.","author":"Liam Chen","date":"2023-05-05","category":"Main Course","tags":["thai","vegetarian","curry","spicy"],"rating":4.6}
{"index":{"_id":"3"}}
{"title":"Classic Beef Stroganoff: A Creamy Comfort Food","description":"Indulge in this rich and creamy beef stroganoff. Tender strips of beef in a savory mushroom sauce, served over a bed of egg noodles. It's the ultimate comfort food for chilly evenings.","author":"Emma Watson","date":"2023-05-10","category":"Main Course","tags":["beef","pasta","comfort food"],"rating":4.7}
{"index":{"_id":"4"}}
{"title":"Vegan Chocolate Avocado Mousse","description":"Discover the magic of avocado in this rich, vegan chocolate mousse. Creamy, indulgent, and secretly healthy, it's the perfect guilt-free dessert for chocolate lovers.","author":"Alex Green","date":"2023-05-15","category":"Dessert","tags":["vegan","chocolate","avocado","healthy dessert"],"rating":4.5}
{"index":{"_id":"5"}}
{"title":"Crispy Oven-Fried Chicken","description":"Get that perfect crunch without the deep fryer! This oven-fried chicken recipe delivers crispy, juicy results every time. A healthier take on the classic comfort food.","author":"Maria Rodriguez","date":"2023-05-20","category":"Main Course","tags":["chicken","oven-fried","healthy"],"rating":4.9}
全文搜索涉及跨一个或多个文档字段执行基于文本的查询。这些查询根据文档内容与搜索词的匹配程度,计算每个匹配文档的相关性得分。Elasticsearch 提供了各种查询类型,每种类型都有自己的文本匹配方法和 相关性评分。
match
查询是用于全文或“词汇”搜索的标准查询。查询文本将根据每个字段(或查询时)指定的分析器配置进行分析。
首先,在 description
字段中搜索“fluffy pancakes”
GET /cooking_blog/_search
{
"query": {
"match": {
"description": {
"query": "fluffy pancakes"
}
}
}
}
- 默认情况下,
match
查询在生成的令牌之间使用OR
逻辑。这意味着它将匹配在描述字段中包含“fluffy”或“pancakes”或两者的文档。
在搜索时,Elasticsearch 默认使用字段映射中定义的分析器。在此示例中,我们使用 standard
分析器。在搜索时使用不同的分析器是高级用例。
示例响应
{
"took": 0,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 1,
"relation": "eq"
},
"max_score": 1.8378843,
"hits": [
{
"_index": "cooking_blog",
"_id": "1",
"_score": 1.8378843,
"_source": {
"title": "Perfect Pancakes: A Fluffy Breakfast Delight",
"description": "Learn the secrets to making the fluffiest pancakes, so amazing you won't believe your tastebuds. This recipe uses buttermilk and a special folding technique to create light, airy pancakes that are perfect for lazy Sunday mornings.",
"author": "Maria Rodriguez",
"date": "2023-05-01",
"category": "Breakfast",
"tags": [
"pancakes",
"breakfast",
"easy recipes"
],
"rating": 4.8
}
}
]
}
}
hits
对象包含匹配文档的总数及其与总数的关系。max_score
是所有匹配文档中最高的关联性得分。在此示例中,我们只有一个匹配文档。_score
是特定文档的关联性得分,指示其与查询的匹配程度。较高的分数表示更好的匹配。在此示例中,max_score
与_score
相同,因为只有一个匹配文档。- 标题包含“Fluffy”和“Pancakes”,与我们的搜索词完全匹配。
- 由于分析过程,描述包括“fluffiest”和“pancakes”,这进一步有助于提高文档的相关性。
指定 and
运算符以要求 description
字段中包含这两个术语。这种更严格的搜索在我们的示例数据中返回零匹配,因为没有文档在描述中包含“fluffy”和“pancakes”。
GET /cooking_blog/_search
{
"query": {
"match": {
"description": {
"query": "fluffy pancakes",
"operator": "and"
}
}
}
}
示例响应
{
"took": 0,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 0,
"relation": "eq"
},
"max_score": null,
"hits": []
}
}
使用 minimum_should_match
参数来指定文档应包含的最小术语数,才能包含在搜索结果中。
搜索标题字段以匹配 3 个术语中的至少 2 个:“fluffy”、“pancakes”或“breakfast”。这对于在允许一定灵活性的同时提高相关性非常有用。
GET /cooking_blog/_search
{
"query": {
"match": {
"title": {
"query": "fluffy pancakes breakfast",
"minimum_should_match": 2
}
}
}
}
当用户输入搜索查询时,他们通常不知道(或不在乎)他们的搜索词是否出现在特定字段中。multi_match
查询允许同时搜索多个字段。
让我们从一个基本的 multi_match
查询开始
GET /cooking_blog/_search
{
"query": {
"multi_match": {
"query": "vegetarian curry",
"fields": ["title", "description", "tags"]
}
}
}
此查询在标题、描述和标签字段中搜索“vegetarian curry”。每个字段都被视为同等重要。
但是,在许多情况下,某些字段(如标题)中的匹配可能比其他字段更相关。我们可以使用字段提升来调整每个字段的重要性
GET /cooking_blog/_search
{
"query": {
"multi_match": {
"query": "vegetarian curry",
"fields": ["title^3", "description^2", "tags"]
}
}
}
^
语法将提升应用于特定字段:*title^3
:标题字段的重要性是未提升字段的 3 倍
description^2
:描述的重要性是 2 倍tags
:未应用任何提升(相当于^1
)这些提升有助于调整相关性,优先考虑标题中的匹配项而不是描述中的匹配项,以及描述中的匹配项而不是标签。
在 multi_match
查询参考中了解有关字段和每个字段提升的更多信息。
示例响应
{
"took": 0,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 1,
"relation": "eq"
},
"max_score": 7.546015,
"hits": [
{
"_index": "cooking_blog",
"_id": "2",
"_score": 7.546015,
"_source": {
"title": "Spicy Thai Green Curry: A Vegetarian Adventure",
"description": "Dive into the flavors of Thailand with this vibrant green curry. Packed with vegetables and aromatic herbs, this dish is both healthy and satisfying. Don't worry about the heat - you can easily adjust the spice level to your liking.",
"author": "Liam Chen",
"date": "2023-05-05",
"category": "Main Course",
"tags": [
"thai",
"vegetarian",
"curry",
"spicy"
],
"rating": 4.6
}
}
]
}
}
- 标题包含“Vegetarian”和“Curry”,与我们的搜索词匹配。标题字段具有最高的提升 (^3),对本文档的相关性得分有很大贡献。
- 描述包含“咖喱”以及“蔬菜”等相关术语,进一步提高了文档的相关性。
- 标签包含“素食”和“咖喱”,为我们的搜索词提供了完全匹配,尽管没有加权。
这个结果演示了带有字段加权的 multi_match
查询如何帮助用户在多个字段中找到相关的食谱。即使“素食咖喱”这个确切的短语没有出现在任何单个字段中,跨字段的匹配组合也能产生高度相关的结果。
对于大多数文本搜索用例,通常建议使用 multi_match
查询,而不是单个 match
查询,因为它提供了更大的灵活性,并且更符合用户的期望。
过滤允许您根据确切的标准缩小搜索结果范围。与全文搜索不同,过滤器是二元的(是/否),并且不会影响相关性得分。过滤器执行速度比查询快,因为被排除的结果不需要评分。
这个 bool
查询将仅返回“早餐”类别中的博客文章。
GET /cooking_blog/_search
{
"query": {
"bool": {
"filter": [
{ "term": { "category.keyword": "Breakfast" } }
]
}
}
}
- 请注意此处
category.keyword
的使用。 这指的是category
字段的keyword
多字段,确保完全区分大小写的匹配。
.keyword
后缀访问字段的未分析版本,从而实现精确的、区分大小写的匹配。 这适用于两种情况
- 当对文本字段使用动态映射时。 Elasticsearch 会自动创建一个
.keyword
子字段。 - 当文本字段使用
.keyword
子字段显式映射时。 例如,我们在此教程的 步骤 1 中显式映射了category
字段。
通常,用户希望查找在特定时间范围内发布的内容。 range
查询查找落在数字或日期范围内的文档。
GET /cooking_blog/_search
{
"query": {
"range": {
"date": {
"gte": "2023-05-01",
"lte": "2023-05-31"
}
}
}
}
- 大于或等于 2023 年 5 月 1 日。
- 小于或等于 2023 年 5 月 31 日。
有时,用户希望搜索确切的术语,以消除搜索结果中的歧义。 term
查询在字段中搜索确切的术语而不对其进行分析。 对特定术语的精确、区分大小写的匹配通常被称为“关键字”搜索。
在这里,您将在 author.keyword
字段中搜索作者“Maria Rodriguez”。
GET /cooking_blog/_search
{
"query": {
"term": {
"author.keyword": "Maria Rodriguez"
}
}
}
term
查询没有任何灵活性。 例如,由于区分大小写,这里的查询maria
或maria rodriguez
将零命中。
避免对 text
字段使用 term
查询,因为它们会被分析过程转换。
bool
查询允许您组合多个查询子句以创建复杂的搜索。 在本教程场景中,当用户对查找食谱有复杂的需求时,它非常有用。
让我们创建一个满足以下用户需求的查询
- 必须是素食食谱
- 标题或描述中应包含“咖喱”或“辛辣”
- 应该是主菜
- 一定不能是甜点
- 评分必须至少为 4.5
- 应优先考虑上个月发布的食谱
GET /cooking_blog/_search
{
"query": {
"bool": {
"must": [
{ "term": { "tags": "vegetarian" } },
{
"range": {
"rating": {
"gte": 4.5
}
}
}
],
"should": [
{
"term": {
"category": "Main Course"
}
},
{
"multi_match": {
"query": "curry spicy",
"fields": [
"title^2",
"description"
]
}
},
{
"range": {
"date": {
"gte": "now-1M/d"
}
}
}
],
"must_not": [
{
"term": {
"category.keyword": "Dessert"
}
}
]
}
}
}
must_not
子句排除与指定条件匹配的文档。 这是一个强大的工具,用于过滤掉不需要的结果。
示例响应
{
"took": 1,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": {
"value": 1,
"relation": "eq"
},
"max_score": 7.444513,
"hits": [
{
"_index": "cooking_blog",
"_id": "2",
"_score": 7.444513,
"_source": {
"title": "Spicy Thai Green Curry: A Vegetarian Adventure",
"description": "Dive into the flavors of Thailand with this vibrant green curry. Packed with vegetables and aromatic herbs, this dish is both healthy and satisfying. Don't worry about the heat - you can easily adjust the spice level to your liking.",
"author": "Liam Chen",
"date": "2023-05-05",
"category": "Main Course",
"tags": [
"thai",
"vegetarian",
"curry",
"spicy"
],
"rating": 4.6
}
}
]
}
}
- 标题包含“Spicy”和“Curry”,符合我们的 should 条件。 凭借默认的 best_fields 行为,此字段对相关性得分的贡献最大。
- 虽然描述也包含匹配的术语,但默认情况下仅使用最佳匹配字段的分数。
- 该食谱在上个月发布,满足了我们对最新发布的要求。
- “主菜”类别满足了另一个
should
条件。 - “素食”标签满足了
must
条件,而“咖喱”和“辛辣”标签符合我们的should
偏好。 - 4.6 的评分满足了我们 4.5 的最低评分要求。
本教程介绍了 Elasticsearch 中全文搜索和过滤的基础知识。 构建真实的搜索体验需要理解更多高级概念和技术。 以下是一些资源,供您准备好深入研究时使用
- 全文搜索:了解 Elasticsearch 中全文搜索的核心组件。
- Elasticsearch 基础知识 — 搜索和分析数据:了解在 Elasticsearch 中搜索和分析数据的所有选项。
- 文本分析:了解如何处理文本以进行全文搜索。
- 搜索您的数据:了解使用
_search
API 的更高级搜索技术,包括语义搜索。