读书笔记《elasticsearch-server-third-edition》搜索您的数据
在上一章中,我们深入研究了 Elasticsearch 索引。我们在数据处理方面学到了很多东西。我们看到了如何调整 Elasticsearch 无模式机制,现在我们知道如何创建自己的映射。我们还看到了 Elasticsearch 的核心类型,并使用了分析器——既有 Elasticsearch 开箱即用的分析器,也有我们自己定义的分析器。我们使用批量索引,并在我们的索引中添加了额外的内部信息。最后,我们了解了段合并是什么,我们如何对其进行微调,以及如何在 Elasticsearch 中使用路由以及它为我们提供了什么。本章完全致力于查询。在本章结束时,您将学习以下主题:
如何查询 Elasticsearch
运行查询时内部发生的情况
Elasticsearch 中的基本查询有哪些
Elasticsearch 中有哪些复合查询允许我们对其他查询进行分组
如何使用位置感知查询——跨度查询
如何为工作选择正确的查询
到目前为止,当我们搜索我们的数据时,我们使用了 REST API 和一个简单的查询或 GET
请求。同样,当我们更改索引时,我们也使用了 REST API 并将 JSON 结构的数据发送到 Elasticsearch。无论我们想要执行哪种类型的操作,无论是映射更改还是文档索引,我们都使用 JSON 结构化请求体来告知 Elasticsearch 操作细节。
当我们想要向 Elasticsearch 发送的不仅仅是一个简单的查询时,也会发生类似的情况,我们使用 JSON 对象对其进行结构化,然后在请求正文中将其发送到 Elasticsearch。这称为查询 DSL。从更广泛的角度来看,Elasticsearch 支持两种查询:基本查询和复合查询。 term
查询等基本查询用于查询实际数据。我们将在本章的基本查询部分介绍这些内容。第二种查询是复合查询,比如bool
查询,可以组合多个查询。我们将在本章的复合查询部分介绍这些内容。
然而,这还不是全部。除了这两种类型的查询之外,某些查询还可以具有 过滤器,用于根据特定条件缩小结果范围。过滤查询不影响评分,通常非常高效且易于缓存。
更复杂的是,查询可以包含其他查询(别担心;我们将尝试解释所有这些!)。此外,一些查询可以包含过滤器,而其他查询可以同时包含查询和过滤器。虽然这还不是全部,但我们现在将坚持这个可行的解释。我们将在本章的复合查询部分和过滤你的第 4 章中的结果部分, 扩展您的查询知识。
{ "book" : { "properties" : { "author" : { "type" : "string" }, "characters" : { "type" : "string" }, "copies" : { "type" : "long", "ignore_malformed" : false }, "otitle" : { "type" : "string" }, "tags" : { "type" : "string", "index" : "not_analyzed" }, "title" : { "type" : "string" }, "year" : { "type" : "long", "ignore_malformed" : false, "index" : "analyzed" }, "available" : { "type" : "boolean" } } } }
前面的映射表示一个简单的库,用于创建库索引。 记住的一件事是,如果我们不进行不同的配置,Elasticsearch 将分析基于字符串的字段。
前面的映射存储在 mapping.json
文件中,为了创建提到的库索引,我们可以使用以下命令:
curl -XPOST 'localhost:9200/library'
curl -XPUT 'localhost:9200/library/book/_mapping' -d @mapping.json
我们还使用以下示例数据作为本章的示例数据:
{ "index": {"_index": "library", "_type": "book", "_id": "1"}}
{ "title": "All Quiet on the Western Front","otitle": "Im Westen nichts Neues","author": "Erich Maria Remarque","year": 1929,"characters": ["Paul Bäumer", "Albert Kropp", "Haie Westhus", "Fredrich Müller", "Stanislaus Katczinsky", "Tjaden"],"tags": ["novel"],"copies": 1, "available": true, "section" : 3}
{ "index": {"_index": "library", "_type": "book", "_id": "2"}}
{ "title": "Catch-22","author": "Joseph Heller","year": 1961,"characters": ["John Yossarian", "Captain Aardvark", "Chaplain Tappman", "Colonel Cathcart", "Doctor Daneeka"],"tags": ["novel"],"copies": 6, "available" : false, "section" : 1}
{ "index": {"_index": "library", "_type": "book", "_id": "3"}}
{ "title": "The Complete Sherlock Holmes","author": "Arthur Conan Doyle","year": 1936,"characters": ["Sherlock Holmes","Dr. Watson", "G. Lestrade"],"tags": [],"copies": 0, "available" : false, "section" : 12}
{ "index": {"_index": "library", "_type": "book", "_id": "4"}}
{ "title": "Crime and Punishment","otitle": "Преступлéние и наказáние","author": "Fyodor Dostoevsky","year": 1886,"characters": ["Raskolnikov", "Sofia Semyonovna Marmeladova"],"tags": [],"copies": 0, "available" : true}
我们将示例数据存储在 documents.json
文件中,并使用以下命令对其进行索引:
curl -s -XPOST 'localhost:9200/_bulk' --data-binary @documents.json
此命令运行批量索引。您可以在 批量索引以加快索引过程部分了解更多信息="ch02">第 2 章,索引您的数据。
查询 Elasticsearch 的最简单方法是使用 URI 请求查询。我们已经在 使用 URI 请求查询搜索 部分讨论过它>第 1 章,Elasticsearch 集群入门。例如,要在标题字段中搜索单词 crime,您可以使用以下命令发送查询:
curl -XGET 'localhost:9200/library/book/_search?q=title:crime&pretty'
这是向 Elasticsearch 提交查询的一种非常简单但有限的方式。如果我们从 Elasticsearch 查询 DSL 的角度来看,前面的查询是 query_string
查询。它在标题字段中搜索具有术语犯罪的文档,并且可以重写如下:
{ "query" : { "query_string" : { "query" : "title:crime" } } }
使用查询 DSL 发送查询有点不同,但仍然不是火箭科学。我们发送 GET
(POST
也被接受,以防您的工具或库不允许在 HTTP GET
请求)对 _search
REST 端点的 HTTP 请求如前所述,并将查询包含在请求正文中。我们来看看下面的命令:
curl -XGET 'localhost:9200/library/book/_search?pretty' -d '{
"query" : {
"query_string" : { "query" : "title:crime" }
}
}'
如您所见,我们使用请求正文(-d
开关)将整个 JSON 结构的查询发送到 Elasticsearch。 pretty
请求参数告诉 Elasticsearch 以我们人类可以更容易阅读的方式构建响应。响应前面的命令,我们得到以下输出:
{ "took" : 4, "timed_out" : false, "_shards" : { "total" : 5, "successful" : 5, "failed" : 0 }, "hits" : { "total" : 1, "max_score" : 0.5, "hits" : [ { "_index" : "library", "_type" : "book", "_id" : "4", "_score" : 0.5, "_source" : { "title" : "Crime and Punishment", "otitle" : "Преступлéние и наказáние", "author" : "Fyodor Dostoevsky", "year" : 1886, "characters" : [ "Raskolnikov", "Sofia Semyonovna Marmeladova" ], "tags" : [ ], "copies" : 0, "available" : true } } ] } }
Elasticsearch 允许 我们控制我们想要(最多)获得多少结果以及从 我们要开始的结果。以下是可以在请求正文中设置的两个附加属性:
from
:此属性指定我们希望从中获取结果的文档。它的默认值是0
,这意味着我们想要从第一个文档中获取我们的结果。size
:该属性指定我们想要作为单个查询结果的最大文档数(默认为10
)。例如,如果我们只关心聚合结果而不关心查询返回的文档,我们可以将此参数设置为0
。
如果我们希望我们的查询从列表中的第 10 项开始获取文档并获取 20 个文档,我们发送以下查询:
curl -XGET 'localhost:9200/library/book/_search?pretty' -d '{
"from" : 9,
"size" : 20,
"query" : {
"query_string" : { "query" : "title:crime" }
}
}'
Tip
下载示例代码
您可以从 http://www.packtpub.com< /一>。如果您在其他地方购买了这本书,您可以访问 http://www.packtpub.com/support< /a> 并注册以将文件直接通过电子邮件发送给您。
使用您的电子邮件地址和密码登录或注册我们的网站
将鼠标指针悬停在顶部的“支持”选项卡上
点击代码下载 &勘误表
在搜索框中输入书名
选择您要为其下载代码文件的图书
从您购买此书的下拉菜单中选择
点击代码下载
下载文件后,请确保使用以下最新版本解压缩或解压缩文件夹:
WinRAR / 7-Zip for Windows
Zipeg / iZip / UnRarX for Mac
适用于 Linux 的 7-Zip / PeaZip
除了返回的所有信息外,Elasticsearch还可以返回文档的版本(我们在第 1 章,Elasticsearch Cluster 入门。为此,我们需要添加 version
属性的值为 true 到我们的 JSON 对象的顶层。因此,请求版本信息的最终查询将如下所示:
curl -XGET 'localhost:9200/library/book/_search?pretty' -d '{
"version" : true,
"query" : {
"query_string" : { "query" : "title:crime" }
}
}'
运行上述查询后,我们得到以下结果:
{
"took" : 4,
"timed_out" : false,
"_shards" : {
"total" : 5,
"successful" : 5,
"failed" : 0
},
"hits" : {
"total" : 1,
"max_score" : 0.5,
"hits" : [ {
"_index" : "library",
"_type" : "book",
"_id" : "4",
"_version" : 1,
"_score" : 0.5,
"_source" : {
"title" : "Crime and Punishment",
"otitle" : "Преступлéние и наказáние",
"author" : "Fyodor Dostoevsky",
"year" : 1886,
"characters" : [ "Raskolnikov", "Sofia Semyonovna Marmeladova" ],
"tags" : [ ],
"copies" : 0,
"available" : true
}
} ]
}
}
对于非标准 用例,Elasticsearch 提供了一项功能,可让我们根据必须将文档视为匹配的最小分值过滤结果.为了使用此功能,我们必须在 JSON 对象的顶层提供 min_score
值以及最低分数的值。例如,如果我们希望我们的查询只返回分数高于 0.75
的文档,我们发送以下查询:
curl -XGET 'localhost:9200/library/book/_search?pretty' -d '{
"min_score" : 0.75,
"query" : {
"query_string" : { "query" : "title:crime" }
}
}'
运行上述查询后,我们得到以下响应:
{ "took" : 3, "timed_out" : false, "_shards" : { "total" : 5, "successful" : 5, "failed" : 0 }, "hits" : { "total" : 0, "max_score" : null, "hits" : [ ] } }
如果你看前面的例子,我们文档的分数是 0.5
,低于 0.75
,因此我们没有'没有得到任何文件作为回应。
通过在请求正文中使用字段数组,Elasticsearch 允许我们定义要包含在响应中的字段。请记住,如果这些字段被标记为 存储在用于创建索引的映射中,或者如果 _source
字段被使用(Elasticsearch 使用 _source
字段来提供存储的值并且 _source
字段被打开默认开启)。
因此,例如,要仅返回结果中的标题和年份字段(对于每个文档),请将以下查询发送到 Elasticsearch:
curl -XGET 'localhost:9200/library/book/_search?pretty' -d '{
"fields" : [ "title", "year" ],
"query" : {
"query_string" : { "query" : "title:crime" }
}
}'
作为响应,我们得到以下输出:
{ "took" : 5, "timed_out" : false, "_shards" : { "total" : 5, "successful" : 5, "failed" : 0 }, "hits" : { "total" : 1, "max_score" : 0.5, "hits" : [ { "_index" : "library", "_type" : "book", "_id" : "4", "_score" : 0.5, "fields" : { "title" : [ "Crime and Punishment" ], "year" : [ 1886 ] } } ] } }
如您所见,一切都按照我们的意愿进行。在这一点上,我们想与您分享四件事,如下:
如果我们不定义 fields 数组,它将使用默认值并返回
_source
字段(如果可用)。如果我们使用
_source
字段并请求一个未存储的字段,则该字段将从_source
字段中提取(但是,这需要额外的处理)。如果我们想返回所有存储的字段,我们只需传递一个星号 (
*
) 作为字段名称。从性能的角度来看,最好返回
_source
字段而不是多个存储字段。这是因为与检索单个_source
字段相比,获取多个存储字段可能会更慢。
除了选择返回哪些 字段外,Elasticsearch 还允许我们使用所谓的源过滤。此功能 允许我们控制从 _source
字段返回哪些字段。 Elasticsearch 公开了几种方法来做到这一点。最简单的源过滤允许我们决定是否应该返回文档。考虑以下查询:
curl -XGET 'localhost:9200/library/book/_search?pretty' -d '{
"_source" : false,
"query" : {
"query_string" : { "query" : "title:crime" }
}
}'
Elasticsearch 重新调整的结果应该类似于以下结果:
{ "took" : 12, "timed_out" : false, "_shards" : { "total" : 5, "successful" : 5, "failed" : 0 }, "hits" : { "total" : 1, "max_score" : 0.5, "hits" : [ { "_index" : "library", "_type" : "book", "_id" : "4", "_score" : 0.5 } ] } }
请注意,响应仅限于有关文档的基本信息,并且不包括 _source
字段。如果您使用 Elasticsearch 作为第二个数据源,并且文档的内容是从 SQL 数据库或缓存中提供的,那么您只需要文档标识符。
第二种方式类似于前面字段中描述的方式,尽管我们定义了应该在文档源本身中返回哪些字段。让我们看看使用以下示例查询:
curl -XGET 'localhost:9200/library/book/_search?pretty' -d '{
"_source" : ["title", "otitle"],
"query" : {
"query_string" : { "query" : "title:crime" }
}
}'
我们想在返回的 _source
字段中获取标题和 otitle
文档字段。 Elasticsearch 从原始 _source
值中提取这些值,并仅包含 _source
字段 请求的字段。 Elasticsearch 返回的整个响应如下所示:
{ "took" : 2, "timed_out" : false, "_shards" : { "total" : 5, "successful" : 5, "failed" : 0 }, "hits" : { "total" : 1, "max_score" : 0.5, "hits" : [ { "_index" : "library", "_type" : "book", "_id" : "4", "_score" : 0.5, "_source" : { "otitle" : "Преступлéние и наказáние", "title" : "Crime and Punishment" } } ] } }
我们还可以使用星号来选择应该在 _source
字段中返回哪些字段;例如,title*
将为 title
字段和 title10
(如果我们的数据中有这样的字段)。如果我们有包含嵌套部分的文档,我们可以使用带点的符号;例如,title.*
选择嵌套在 title
对象下的所有字段。
最后,我们还可以明确指定要包含哪些字段以及要从 _source
字段中排除哪些字段。我们可以使用 include
属性包含字段,我们可以使用 exclude
属性排除字段(它们都是值数组) .例如,如果我们希望返回的 _source
字段包含所有以字母 t
开头的字段,但不包括标题字段,我们将运行以下查询:
curl -XGET 'localhost:9200/library/book/_search?pretty' -d '{
"_source" : {
"include" : [ "t*"],
"exclude" : ["title"]
},
"query" : {
"query_string" : { "query" : "title:crime" }
}
}'
Elasticsearch 允许我们 使用将与结果文档一起返回的脚本评估值(我们将在 第 6 章中的“emphasis”>Elasticsearch 的脚本功能 部分,让您的搜索更好)。要使用 script
字段功能,我们将 script_fields
部分添加到我们的 JSON 查询对象和一个具有我们选择的名称的对象,用于我们想要返回的每个脚本值。例如,要返回一个名为 correctYear
的值,它的计算方法是年份字段减去 1800,我们运行以下查询:
curl -XGET 'localhost:9200/library/book/_search?pretty' -d '{
"script_fields" : {
"correctYear" : {
"script" : "doc[\"year\"].value - 1800"
}
},
"query" : {
"query_string" : { "query" : "title:crime" }
}
}'
Note
默认情况下,Elasticsearch 不允许我们使用动态脚本。如果您尝试了前面的查询,您可能会收到错误信息,说明 [inline]
类型的脚本与操作 [search]
和语言
[groovy]
被禁用。为了使这个示例工作,您应该将 script.inline: on
属性添加到 elasticsearch.yml
文件中。然而,这暴露了安全威胁。请务必阅读 第 6 章中的 Elasticsearch 的脚本功能部分, 让您的搜索更好,了解后果。
使用 doc
表示法,就像我们在前面的示例中所做的那样,允许我们捕获返回的结果并以更高的内存消耗为代价加速脚本执行。我们也仅限于单值和单项字段。如果我们关心内存使用,或者如果我们使用更复杂的字段值,我们总是可以使用 _source
字段。使用 _source
字段的相同查询如下所示:
curl -XGET 'localhost:9200/library/book/_search?pretty' -d '{
"script_fields" : {
"correctYear" : {
"script" : "_source.year - 1800"
}
},
"query" : {
"query_string" : { "query" : "title:crime" }
}
}'
以下响应是启用了 动态脚本的 Elasticsearch 返回的:
{ "took" : 76, "timed_out" : false, "_shards" : { "total" : 5, "successful" : 5, "failed" : 0 }, "hits" : { "total" : 1, "max_score" : 0.5, "hits" : [ { "_index" : "library", "_type" : "book", "_id" : "4", "_score" : 0.5, "fields" : { "correctYear" : [ 86 ] } } ] } }
如您所见,我们得到了计算得到的 correctYear
字段作为响应。
让我们看看 脚本字段的另一个特性——附加参数的传递。我们可以使用变量名并将其值传递给等式,而不是在等式中使用值1800
params
部分。如果我们这样做,我们的查询将如下所示:
curl -XGET 'localhost:9200/library/book/_search?pretty' -d '{
"script_fields" : {
"correctYear" : {
"script" : "_source.year - paramYear",
"params" : {
"paramYear" : 1800
}
}
},
"query" : {
"query_string" : { "query" : "title:crime" }
}
}'
如您所见,我们添加了 paramYear
变量作为脚本方程的一部分,并在 中提供了它的值params
部分。这允许 Elasticsearch 以稍微更有效的方式执行具有不同参数值的相同脚本。
在阅读了前面的 部分之后,我们现在知道了 Elasticsearch 中的查询是如何工作的。您知道,Elasticsearch 在大多数情况下需要将查询分散到多个节点,获取结果,合并它们,从一个或多个分片中获取相关文档,并将最终结果返回给请求文档的客户端。我们没有讨论的是定义查询行为方式的另外两件事:搜索类型和查询执行偏好。我们现在将专注于 Elasticsearch 的这些功能。
Elasticsearch 是一个分布式 搜索引擎,因此提供的所有功能都必须是分布式的。与查询完全相同。因为我们想讨论一些关于如何控制查询过程的更高级的话题,所以我们首先需要知道它是如何工作的。
现在让我们回到查询的工作原理。我们在第一章开始了这个理论,我们想回到它。默认情况下,如果我们不更改任何内容,查询过程将包括两个阶段:分散和收集阶段。聚合器节点(接收请求的节点)将首先运行分散阶段。在那个阶段,查询被分发到我们构建索引的所有分片(当然,如果不使用路由)。例如,如果它由 5 个分片和 1 个副本构建,则将查询 5 个物理分片(我们不需要查询分片及其副本,因为它们包含相同的数据)。每个查询的分片将只返回文档标识符和文档的分数。发送分散查询的节点将等待所有分片完成任务,收集结果,并对它们进行适当的排序(在这种情况下,从最高分到最低分)。
之后,将发送一个新的请求来构建搜索结果。但是,现在只针对那些保存文档的碎片来构建响应。在大多数情况下,Elasticsearch 不会将请求发送到所有分片,而是发送到其子集。那是因为我们通常不会得到查询的完整结果,而只能得到其中的一部分。这个阶段称为收集阶段。在收集所有文档后,构建最终响应并作为查询结果返回。这是基本和默认的 Elasticsearch 行为,但我们可以更改它。
Elasticsearch 允许我们选择我们希望如何在内部处理我们的查询。我们可以通过指定搜索类型来做到这一点。在不同的情况下,不同的搜索类型是合适的:有时人们只关心性能,而有时查询相关性是最重要的因素。您应该记住,每个分片都是一个小的 Lucene 索引,为了返回更多相关的结果,需要在分片之间传输一些信息,例如频率。为了控制查询的执行方式,我们可以传递 search_type
请求参数并将其设置为以下之一以下值:
query_then_fetch
:第一步,执行查询以获取对文档进行排序和排名所需的信息。此步骤针对所有分片执行。然后仅查询相关分片以获取文档的实际内容。如果查询没有提供搜索类型,这是默认使用的搜索类型,这是我们之前描述的查询类型。dfs_query_then_fetch
:这类似于query_then_fetch
。但是,与计算分布式词频的query_then_fetch
相比,它包含一个额外的查询阶段。
还有两种不推荐使用的搜索类型:计数和扫描。第一个从 Elasticsearch 2.0 开始被弃用,第二个从 Elasticsearch 2.1 开始被弃用。第一种搜索类型用于提供仅与聚合或文档数量相关的好处,但现在将等于 0 的大小添加到您的查询中就足够了。扫描请求用于滚动功能。
因此,如果我们想使用 最简单的搜索类型,我们将运行以下命令:
curl -XGET 'localhost:9200/library/book/_search?pretty&search_type=query_then_fetch' -d '{
"query" : {
"term" : { "title" : "crime" }
}
}'
除了可以控制查询如何执行之外,我们还可以控制在哪些分片上执行查询。默认情况下,Elasticsearch 以循环方式在任何节点上使用分片和副本——因此每个分片的查询次数相似。对于大多数用例,默认行为是分片执行首选项的正确方法。但有时我们可能想要更改默认行为。例如,您可能希望 仅在主分片上执行搜索。为此,我们可以将首选项请求参数设置为以下值之一:
_primary
:该操作只会在主分片上执行,因此不会使用副本。当我们需要使用索引中的最新信息但我们的数据没有立即复制时,这可能很有用。_primary_first
:如果主分片可用,该操作将在主分片上执行。如果没有,它将在其他分片上执行。_replica
:操作只会在副本分片上执行。_replica_first
:此操作类似于_primary_first
,但使用副本分片。如果可能,该操作将在副本分片上执行,如果副本不可用,则在主分片上执行。_local
:操作将在发送请求的节点上可用的分片上执行,如果这些分片不存在,请求将被转发到适当的节点._only_node:node_id
:此操作将在具有提供的节点标识符的节点上执行。_only_nodes:nodes_spec
:该操作将在nodes_spec
中定义的节点上执行。这可以是 IP 地址、名称、使用通配符的名称或 IP 地址等。例如,如果nodes_spec
设置为192.168.1.*
,则该操作将在 IP 地址开头的节点上运行使用192.168.1
。_prefer_node:node_id
:Elasticsearch 将尝试在具有提供标识符的节点上执行操作。但是,如果节点不可用,它将在可用的节点上执行。_shards:1,2
:Elasticsearch 将对给定标识符的分片执行操作;在这种情况下,在标识符为1
和2
的分片上。_shards
参数可以与其他首选项结合使用,但需要先提供分片标识符。例如,_shards:1,2;_local
。自定义值
:可以传递任何自定义的字符串值。提供相同值的请求将在相同的分片上执行。
例如,如果我们 只想在本地分片上执行查询,我们将运行以下命令:
curl -XGET 'localhost:9200/library/_search?pretty&preference=_local' -d '{
"query" : {
"term" : { "title" : "crime" }
}
}'
在讨论搜索 偏好时,我们还想提及 Elasticsearch 公开的搜索分片 API。这个 API 允许我们检查查询将在哪些分片上执行。要使用此 API,请针对 search_shards
休息端点运行请求。例如,要查看查询将如何执行,我们运行以下命令:
curl -XGET 'localhost:9200/library/_search_shards?pretty' -d '{"query":"match_all":{}}'
对上述命令的响应如下:
{ "nodes" : { "my0DcA_MTImm4NE3cG3ZIg" : { "name" : "Cloud 9", "transport_address" : "127.0.0.1:9300", "attributes" : { } } }, "shards" : [ [ { "state" : "STARTED", "primary" : true, "node" : "my0DcA_MTImm4NE3cG3ZIg", "relocating_node" : null, "shard" : 0, "index" : "library", "version" : 4, "allocation_id" : { "id" : "9ayLDbL1RVSyJRYIJkuAxg" } } ], [ { "state" : "STARTED", "primary" : true, "node" : "my0DcA_MTImm4NE3cG3ZIg", "relocating_node" : null, "shard" : 1, "index" : "library", "version" : 4, "allocation_id" : { "id" : "wfpvtaLER-KVyOsuD46Yqg" } } ], [ { "state" : "STARTED", "primary" : true, "node" : "my0DcA_MTImm4NE3cG3ZIg", "relocating_node" : null, "shard" : 2, "index" : "library", "version" : 4, "allocation_id" : { "id" : "zrLPWhCOSTmjlb8TY5rYQA" } } ], [ { "state" : "STARTED", "primary" : true, "node" : "my0DcA_MTImm4NE3cG3ZIg", "relocating_node" : null, "shard" : 3, "index" : "library", "version" : 4, "allocation_id" : { "id" : "efnvY7YcSz6X8X8USacA7g" } } ], [ { "state" : "STARTED", "primary" : true, "node" : "my0DcA_MTImm4NE3cG3ZIg", "relocating_node" : null, "shard" : 4, "index" : "library", "version" : 4, "allocation_id" : { "id" : "XJHW2J63QUKdh3bK3T2nzA" } } ] ] }
如您所见,在 Elasticsearch 返回的响应中,我们有关于将在查询过程中使用的分片的信息。当然,通过搜索分片 API,我们可以使用额外的参数来控制查询过程。这些属性是 routing
、preference、
和 local
。前两个我们已经很熟悉了。 local
参数是一个布尔值(值 true
或 false
),一个这允许我们告诉 Elasticsearch 使用存储在 local
节点上的集群状态信息(将 local
设置为 true
) 而不是 master
节点中的 之一(设置 local
到 false
)。这使我们能够诊断集群状态同步的问题。
Elasticsearch 具有广泛的搜索和数据分析功能,这些功能以不同的查询、过滤器、聚合等形式公开。在本节中,我们将重点介绍 Elasticsearch 提供的基本查询。我们所说的基本查询是指那些不将其他查询组合在一起而是单独运行的查询。
术语查询是 Elasticsearch 中最简单的查询之一。它只匹配在给定字段中具有术语的 文档 - 确切的,未分析的术语。最简单的术语查询如下:
{ "query" : { "term" : { "title" : "crime" } } }
它将匹配标题字段中包含“犯罪”一词的文档。请记住,不分析术语查询,因此您需要提供与索引文档中的术语匹配的确切术语。请注意,在我们的输入数据中,我们有 title
字段,其值为 Crime
和 Punishment
(大写),但我们正在搜索 crime
,因为 Crime
术语变为 犯罪
。
除了我们想要查找的词条之外,我们还可以在词条查询中包含boost属性,这会影响给定词条的重要性。我们将在 第 6 章,让您的搜索更好。 现在,我们只需要记住它会改变给定部分的重要性的查询。
例如,要更改我们之前的查询并使我们的术语查询提高 10.0
,请发送以下查询:
{ "query" : { "term" : { "title" : { "value" : "crime", "boost" : 10.0 } } } }
如您所见,查询发生了一些变化。我们嵌套了一个新的 JSON 对象,而不是简单的术语值,它 包含 value
属性和 < code class="literal">boost 属性。 value
属性的值应该包含我们感兴趣的词并且 boost
属性是 boost
我们想要使用的值。
terms
查询是 term
查询的扩展。它允许我们匹配在其 内容中具有特定术语的文档,而不是单个术语。 term
查询允许我们匹配单个未分析的术语,terms
查询允许我们匹配 其中的多个。例如,假设我们想要获取所有在标签字段中具有词小说或书籍的文档。为此,我们将运行以下查询:
{ "query" : { "terms" : { "tags" : [ "novel", "book" ] } } }
前面的查询返回所有在标签字段中包含一个或两个搜索词的文档。这是一个要记住的关键 点——terms
查询将查找具有任何提供的术语的文档。
匹配所有查询是 Elasticsearch 中可用的最简单查询之一。它允许 我们匹配索引中的所有文档。如果我们想从索引中获取所有文档,我们只需运行以下查询:
{ "query" : { "match_all" : {} } }
我们还可以在查询中包含 boost,它将被赋予它匹配的所有文档。例如,如果我们想为 match all 查询中的所有文档添加 2.0 的提升,我们将发送 以下查询到 Elasticsearch:
{ "query" : { "match_all" : { "boost" : 2.0 } } }
一个非常简单的查询,可以让我们找到所有特定类型的文档。例如,如果我们想在我们的图书馆索引中搜索所有具有 book
类型的文档,我们将运行 以下查询:
{ "query" : { "type" : { "value" : "book" } } }
一个允许我们查找 所有在 中具有值的文档的查询定义的字段。例如,要查找在 tags
字段中有值的文档,我们将运行以下查询:
{ "query" : { "exists" : { "field" : "tags" } } }
与 exists 查询相反, 缺失查询返回具有 null 的文档给定字段中的值或根本没有值。例如,要查找 tags
字段中没有值的所有文档,我们将运行以下查询:
{ "query" : { "missing" : { "field" : "tags" } } }
常用词查询 是一种现代 Elasticsearch 解决方案,用于在我们不使用常用词时提高查询的相关性和准确性 停用词 (http://en.wikipedia.org/ wiki/Stop_words)。例如,犯罪和惩罚查询会产生三个词条查询,并且每个词条查询都会产生性能成本。但是,and
这个词是一个很常见的词,它对文档分数的影响会很小。解决方案是将查询分为两组的常用术语查询。第一组是具有重要术语的术语,即频率较低的术语。第二组是不太重要的术语,即频率较高的术语。第一个查询首先执行,Elasticsearch 计算第一组中所有术语的分数。这样,通常会考虑到通常更重要的低频项。然后 Elasticsearch 对第二组术语执行第二个查询,但只计算与第一个查询匹配的文档的分数。这种方式只计算相关文档的分数,因此可以获得更高的性能。
常用词查询示例如下:
{ "query" : { "common" : { "title" : { "query" : "crime and punishment", "cutoff_frequency" : 0.001 } } } }
query
:实际的查询内容。cutoff_frequency
:百分比(0.001 表示 0.1%)或绝对值(当属性设置为等于或大于 1 的值时)。使用此值构建高频和低频组。将此参数设置为 0.001 意味着将为频率为 0.1% 或更低的术语构建低频术语组。low_freq_operator
:可以设置为or
或and
,但默认为或
。它指定用于在低频术语组中构造查询的Boolean
运算符。如果我们希望文档中出现的所有术语都被认为是匹配的,我们应该将此参数设置为and
。high_freq_operator
:可以设置为or
或and
,但默认为或
。它指定用于在高频术语组中构造查询的Boolean
运算符。如果我们希望文档中出现的所有术语都被认为是匹配的,我们应该将此参数设置为and
。minimum_should_match
:我们可以使用low_freq_operator
和high_freq_operator
class="literal">minimum_should_match。就像其他查询一样,它允许我们指定应该在文档中找到的最小术语数,以便将其视为匹配。我们还可以在minimum_should_match
对象中指定high_freq
和low_freq
,这允许我们来定义需要为高频和低频项匹配的不同数量的项。boost
:文档分数的提升。analyzer
:将用于分析查询文本的分析器的名称,默认为默认分析器。disable_coord
:默认为false
并允许我们启用或禁用基于所有查询分数的分数因子计算文档包含的术语。将其设置为true
以获得不太精确的评分,但查询速度稍快。
match
查询采用 query
值> 参数,对其进行分析,并从中构造适当的查询。当使用 match
查询时,Elasticsearch 会为我们选择的字段选择合适的分析器,因此您可以确定 terms 传递给 match
查询将由索引期间使用的同一分析器处理。请记住,match
查询(以及 multi_match
查询)不支持 Lucene 查询语法;但是,它非常适合作为搜索框的查询处理程序。最简单的匹配(和默认)查询如下所示:
{ "query" : { "match" : { "title" : "crime and punishment" } } }
前面的查询 将匹配所有具有术语crime
、and,或title
字段中的“literal">惩罚。但是,前面的查询只是最简单的查询;我们现在将讨论多种类型的匹配查询。
Boolean match
查询是分析提供的文本并从中进行布尔查询的查询。这也是匹配查询的默认类型。有一些参数可以让我们控制 Boolean match
查询的行为:
operator
:这个参数可以取或 或
and
,并控制使用哪个布尔运算符连接创建的布尔子句。默认值为或
。如果我们想要匹配查询中的所有术语,我们应该使用and
布尔运算符。fuzziness
:提供这个参数的值可以让我们构造模糊查询。此参数的值可以变化。对于数字字段,应设置为数值;对于基于日期的字段,可以设置为毫秒
或时间
值,例如2h
;对于文本字段,可以设置为0
、1
或2 (Levenshtein 算法中的编辑距离 - https://en.wikipedia.org/wiki/Levenshtein_distance),
AUTO
(它允许 Elasticsearch 控制模糊查询的程度构造,这是一个首选值)。最后,对于文本字段,它也可以设置为 0.0 到 1.0 之间的值,这导致编辑距离被计算为术语长度减去 1.0 乘以提供的模糊度值。一般来说,模糊度越高,允许的术语之间的差异就越大。zero_terms_query
:这允许我们 指定查询的行为,当所有术语都被分析器(例如,因为停用词)。它可以设置为 none 或 all,默认为 none。设置为 none 时,分析器删除所有查询词时不会返回任何文档。如果设置为 all,则返回所有文档。cutoff_frequency
:它允许将查询分为两组:一组是高频词,一组是低频条款。请参阅常用术语查询的说明,了解如何使用此参数。lenient
:当设置为true
时(默认它是false
),它可以让我们忽略数据不兼容导致的异常,例如尝试使用字符串值查询数字字段。
参数应该包含在我们正在对其运行查询的字段的名称中。因此,如果我们想针对 title
字段运行示例布尔匹配查询,我们发送如下查询:
{ "query" : { "match" : { "title" : { "query" : "crime and punishment", "operator" : "and" } } } }
phrase match
查询类似于 Boolean
查询,但它不是从分析的文本构造布尔子句,而是构造phrase
查询。您可能想知道 Lucene 和 Elasticsearch 是什么短语——嗯,它是按 顺序依次排列的两个或多个术语。以下参数可用:
针对标题字段的示例 phrase match
查询类似于以下代码:
{ "query" : { "match_phrase" : { "title" : { "query" : "crime punishment", "slop" : 1 } } } }
请注意,我们从查询中删除了 and
术语,但是因为 slop 设置为 1,
它仍然会匹配我们的文档因为我们允许在我们的术语之间出现一个术语。
匹配查询的最后一种类型是 匹配短语前缀
查询。此查询与 phrase match
查询几乎相同,但此外,它允许对查询文本中的最后一个词进行前缀匹配。此外,除了匹配短语查询公开的参数之外,它还公开了一个额外的参数——max_expansions
参数,该参数控制最后一个术语将被重写到多少个前缀。我们的示例查询更改为 match_phrase_prefix
查询将如下所示:
{ "query" : { "match_phrase_prefix" : { "title" : { "query" : "crime punishm", "slop" : 1, "max_expansions" : 20 } } } }
请注意,我们没有提供完整的犯罪和惩罚短语,而仅提供 crimepunctum
并且查询仍然会匹配我们的文档。这是因为我们使用了 match_phrase_prefix
查询结合 slop 设置为 1
。
它与 match
查询相同,但不是针对单个字段运行,而是可以使用 fields
参数针对多个字段运行。当然,您在 match
查询中使用的所有参数都可以与 多重匹配
查询。因此,如果我们想修改 match
查询以针对 title
和 otitle
字段,我们将运行以下查询:
{ "query" : { "multi_match" : { "query" : "crime punishment", "fields" : [ "title^10", "otitle" ] } } }
如上例所示,multi match
查询的好处是其中定义的字段支持boosting,因此我们可以增加或减少某些字段匹配的重要性.
但是,与 match
查询进行比较时,这并不是唯一的区别。我们还可以 通过使用 type
属性并将其设置为以下值:
best_fields
:这是默认行为,它从定义的字段中查找在任何字段中匹配的文档,但将文档分数设置为最佳匹配字段的分数。在搜索多个单词并希望提升在同一字段中包含这些单词的文档时最有用的类型。most_fields
:此值查找与任何字段匹配的文档,并将文档的分数设置为所有匹配字段的组合分数。cross_fields
:该值将查询视为所有术语都在一个大字段中,因此返回与任何字段匹配的文档。phrase
:该值对每个字段使用match_phrase
查询,并将文档的分数设置为所有字段组合的分数.phrase_prefix
:该值对每个字段使用match_phrase_prefix
查询,并将文档的分数设置为所有字段组合的分数.
除了match
查询和type
中提到的参数外,多重匹配
code>query 公开了一些额外的,允许对其行为进行更多控制:
与 可用的其他查询相比,查询字符串
查询支持完整的 Apache Lucene 查询 语法,我们之前在 Lucene 查询语法 部分讨论过link" href="#" linkend="ch01">第 1 章,Elasticsearch 集群入门。它使用查询解析器使用提供的文本构造实际查询。示例查询字符串查询将类似于以下代码:
{ "query" : { "query_string" : { "query" : "title:crime^10 +title:punishment -otitle:cat +author:(+Fyodor +dostoevsky)", "default_field" : "title" } } }
因为我们熟悉 Lucene 查询语法的基础知识,所以我们可以讨论前面的查询是如何工作的。正如您所见,我们想要获取标题字段中可能包含“犯罪”一词的文档,并且应该将这些文档的值提升为 10。接下来,我们只想要在标题字段中带有词条惩罚的文档,而不想要在 otitle
字段中带有词条 cat 的文档。最后,我们告诉 Lucene,我们只想要在作者字段中包含 fyodor
和 dostoevsky
术语的文档。
与 Elasticsearch 中的大多数查询类似,query string
查询提供了很多允许我们控制查询行为的参数,并且该查询的参数列表相当广泛:
default_field
:这指定了将执行查询的 默认字段。它默认为index.query.default_field
属性,默认设置为_all
。lowercase_expand_terms
:这 指定作为查询重写结果的术语是否应小写。它默认为true
,这意味着重写的术语将小写。enable_position_increments
:这 指定是否应转动位置增量 在结果查询中打开。它默认为true
。fuzzy_max_expansions
:如果模糊查询是用过的。默认为50
。fuzzy_prefix_length
:指定生成的模糊查询的前缀长度,默认为0 。要了解更多信息,请查看
fuzzy
查询描述。analyze_wildcard
:此指定是否应分析通配符查询生成的术语。它默认为false
,这意味着不会分析这些术语。auto_generate_phrase_queries
:指定是否从 查询中自动生成短语查询。它默认为false
,这意味着不会自动生成短语查询。minimum_should_match
:这个控制生成的布尔应该有多少 code> 子句应与文档匹配,以使文档被视为命中。该值可以以百分比形式提供;例如,50%,这意味着至少 50% 的给定术语应该匹配。它也可以作为整数值提供,例如 2,这意味着至少有 2 个术语必须匹配。
fuzziness
:控制生成的fuzzy行为> 查询。有关详细信息,请参阅
match
查询说明。max_determined_states
:这个 默认为10000,设置自动机处理正则表达式的状态数查询。它用于禁止使用正则表达式的非常昂贵的查询。lenient
:这可以取true
的值或false
。如果设置为true
,则将忽略基于格式的失败。默认情况下,它设置为false
。
请注意,Elasticsearch 可以重写 查询字符串
查询,因此,Elasticsearch 允许我们传递额外的控制重写方法的参数。但是,有关此过程的更多详细信息,请转到本章中的了解查询过程部分。
可以对多个字段运行 查询字符串
查询。为此,需要在查询正文中提供 fields 参数,该参数应包含字段名称的数组。对多个字段运行查询字符串查询有两种方法:默认方法使用 Boolean
查询进行查询,另一种方法可以使用 dis_max
查询。
为了使用 dis_max
查询,应该在查询正文中添加 use_dis_max
属性并将其设置为 “真”
。示例查询将类似于以下代码:
{ "query" : { "query_string" : { "query" : "crime punishment", "fields" : [ "title", "otitle" ], "use_dis_max" : true } } }
简单查询字符串 查询使用最新查询之一 解析器 - SimpleQueryParser (https://lucene.apache.org/core/5_4_0/queryparser/org/apache/lucene/queryparser/simple/SimpleQueryParser.html)。与查询字符串查询类似,它接受 Lucene 查询语法作为查询;但是,与它不同的是,当发生解析错误时,它从不抛出异常。它不会抛出异常,而是丢弃查询的无效部分并运行其余部分。
{ "query" : { "simple_query_string" : { "query" : "crime punishment", "default_operator" : "or" } } }
查询支持query
、fields
、default_operator
、
分析器,
lowercase_expanded_terms
,
locale
,
宽松< /code> 和
minimum_should_match,
也可以使用 多个字段运行"literal">fields
属性。
这是一个简单的查询, 将返回的文档过滤为仅提供了 的文档身份标识。它适用于内部 _uid
字段,因此不需要启用 _id
字段。这种查询的最简单版本如下所示:
{ "query" : { "ids" : { "values" : [ "1", "2", "3" ] } } }
此查询将仅返回那些在 values 数组中存在标识符之一的文档。我们可以使 identifiers
查询复杂一点,并根据文档类型限制文档。例如,如果我们只想包含书籍类型中的文档,我们将发送以下查询:
{ "query" : { "ids" : { "type" : "book", "values" : [ "1", "2", "3" ] } } }
如您所见,我们已将 type
属性添加到查询中,并将其值设置为 type
我们有兴趣。
此查询在其配置中类似于 term
查询和 multi term
查询其逻辑时。 prefix
查询允许我们匹配 文档,这些文档在以给定开头的特定字段中具有值字首。例如,如果我们想在 title
字段中查找所有值以 cri
开头的文档,我们将运行以下查询:
{ "query" : { "prefix" : { "title" : "cri" } } }
与 term
查询类似,您还可以在前缀查询中包含 boost
属性,这将影响给定前缀的重要性.例如,如果我们想更改我们之前的查询并给我们的查询一个 3.0
的 boost
,我们将发送以下内容询问:
{ "query" : { "prefix" : { "title" : { "value" : "cri", "boost" : 3.0 } } } }
fuzzy
查询让我们能够 找到与我们在询问。词条的相似度是根据编辑距离算法计算的。编辑距离是根据我们在查询中提供的术语和搜索的文档计算的。这个查询在 CPU 资源方面可能很昂贵,但在我们需要模糊匹配时可以帮助我们;例如,当用户出现拼写错误时。在我们的示例中,假设我们的用户不是犯罪,而是在搜索框中输入 crme
单词,我们希望运行最简单形式的 模糊
查询。这样的查询将如下所示:
{ "query" : { "fuzzy" : { "title" : "crme" } } }
此类查询的响应如下:
{ "took" : 81, "timed_out" : false, "_shards" : { "total" : 5, "successful" : 5, "failed" : 0 }, "hits" : { "total" : 1, "max_score" : 0.5, "hits" : [ { "_index" : "library", "_type" : "book", "_id" : "4", "_score" : 0.5, "_source" : { "title" : "Crime and Punishment", "otitle" : "Преступлéние и наказáние", "author" : "Fyodor Dostoevsky", "year" : 1886, "characters" : [ "Raskolnikov", "Sofia Semyonovna Marmeladova" ], "tags" : [ ], "copies" : 0, "available" : true } } ] } }
即使我们打错了字,Elasticsearch 还是设法找到了我们感兴趣的文档。
我们可以通过使用以下参数来控制 fuzzy
查询行为:
参数应该包裹在我们正在运行查询的字段的名称中。因此,如果我们想修改之前的查询并添加额外的参数,查询将类似于以下代码:
{ "query" : { "fuzzy" : { "title" : { "value" : "crme", "fuzziness" : 2 } } } }
一个查询 允许我们使用 *
和 ?
我们搜索的值中的通配符。除此之外,wildcard
查询非常 类似于 term 查询的主体。要发送一个查询,该查询将匹配所有具有 cr?me
术语值的文档(?
匹配任何字符),我们将发送以下查询:
{ "query" : { "wildcard" : { "title" : "cr?me" } } }
它将匹配在 title
字段中具有与 cr?me
匹配的所有术语的文档。但是,您也可以 将 boost
属性添加到您的 通配符< /code> 查询将影响与给定值匹配的每个术语的重要性。例如,如果我们想改变我们之前的查询,并给我们的术语查询一个
20.0
的 boost
,我们将发送以下查询:
{ "query" : { "wildcard" : { "title" : { "value" : "cr?me", "boost" : 20.0 } } } }
一个查询,允许我们查找 具有特定范围内的字段值并且适用于数字字段以及基于字符串的字段和日期的文档基于字段(只是映射到不同的 Apache Lucene 查询)。 range
查询应该针对单个字段运行,并且查询 参数应该包含在字段名称中.支持以下参数:
举个例子,如果我们要在 year
字段,我们将运行以下查询:
{ "query" : { "range" : { "year" : { "gte" : 1700, "lte" : 1900 } } } }
正则表达式查询 允许我们使用正则表达式作为 query
文本。请记住,此类查询的性能取决于所选的常规 表达式。如果我们的正则表达式匹配很多词,查询会很慢。一般规则是正则表达式匹配的词越多,查询越慢。
一个示例正则表达式查询如下所示:
{ "query" : { "regexp" : { "title" : { "value" : "cr.m[ae]", "boost" : 10.0 } } } }
前面的查询将导致 Elasticsearch 重写查询。重写后的查询将有 个术语查询,具体取决于与给定正则表达式匹配的索引内容。查询中看到的 boost
参数 指定 boost
生成查询的值。
Elasticsearch 接受的完整正则表达式语法可以在 https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-regexp-query.html#regexp-syntax。
在 Elasticsearch 2.0 中进行了重大修改的查询之一,更像 this
查询允许我们检索 与提供的文本或提供的文档相似(或不相似)的文档。
这个查询越相似,我们就可以获取与提供的文本相似的文档。 Elasticsearch 支持一些参数来定义更像这个查询应该如何工作:
更类似于此查询的示例如下所示:
{ "query" : { "more_like_this" : { "fields" : [ "title", "otitle" ], "like" : "crime and punishment", "min_term_freq" : 1, "min_doc_freq" : 1 } } }
正如我们之前所说,like
属性也可以用来显示结果应该与哪些文档相似。例如,以下查询将使用 like
属性指向一个 给定文档(请注意,以下查询不会返回我们示例数据中的 文档):
{ "query" : { "more_like_this" : { "fields" : [ "title", "otitle" ], "min_term_freq" : 1, "min_doc_freq" : 1, "like" : [ { "_index" : "library", "_type" : "book", "_id" : "4" } ] } } }
我们还可以将文档和文本混合在一起:
{ "query" : { "more_like_this" : { "fields" : [ "title", "otitle" ], "min_term_freq" : 1, "min_doc_freq" : 1, "like" : [ { "_index" : "library", "_type" : "book", "_id" : "4" }, "crime and punishment" ] } } }
在本章的基本查询部分,我们讨论了 Elasticsearch 公开的最简单的查询。我们还讨论了 在 Span 查询
部分中称为跨度查询的位置感知查询。然而,简单查询和跨度查询并不是 Elasticsearch 提供的唯一查询。我们称之为复合查询,允许我们将多个查询连接在一起或改变其他查询的行为。您可能想知道是否需要这样的功能。您的部署可能 不需要它,但除了简单查询之外的任何东西都可能需要复合查询。例如,将简单的术语查询与 match_phrase
查询相结合以获得更好的搜索结果,这可能是复合查询使用的一个很好的候选。
bool
查询允许我们 包装几乎无限数量的查询并将它们与逻辑值连接< a id="id542" class="indexterm">使用以下部分之一:
前面提到的每个部分都可以在单个 bool
查询中出现多次。这允许我们构建具有多个嵌套级别的非常复杂的查询(您可以将 bool
查询包含在另一个 bool
查询中) .请记住,结果文档的分数将通过对文档匹配的所有包装查询求和来计算。
除了前面的部分,我们还可以在查询体中添加以下参数来控制其行为:
filter
:这允许我们 指定应该用作过滤器的查询部分。您可以在 的 过滤您的结果 部分阅读有关过滤器的更多信息第 4 章,扩展您的查询知识。minimum_should_match
:这 描述了为了使被检查的文档必须匹配的 should 子句的最小数量算作一场比赛。例如,它可以是整数值(例如 2)或百分比值(例如 75%)。更多信息,请参考https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-minimum-should-match .html。disable_coord
:一个Boolean
参数(默认为false
),它允许我们启用或禁用基于文档包含的所有查询词的分数的 score 因子计算。我们应该将其设置为true
以获得不太精确的评分,但查询速度会稍微快一些。
想象一下,我们想要 找到所有在标题字段中包含术语crime
的文档。此外,文档在年份字段中可能有也可能没有 1900
到 2000
的范围,并且可能没有 1900
到 2000
otitle
字段中的 code class="literal">nothing 术语。使用 bool
查询进行的此类查询将如下所示:
{ "query" : { "bool" : { "must" : { "term" : { "title" : "crime" } }, "should" : { "range" : { "year" : { "from" : 1900, "to" : 2000 } } }, "must_not" : { "term" : { "otitle" : "nothing" } } } } }
dis_max
查询非常 非常有用,因为它会生成由所有子查询返回的文档并返回结果。这个查询的好处是我们可以控制得分较低的子查询如何影响文档的最终得分。对于 dis_max
查询,我们指定 使用 queries< /code> 属性(查询或查询数组)和 tie_breaker,带有
tie_breaker
属性。我们还可以通过指定 boost
参数来包含额外的提升。
最终的文档分数计算为最大评分查询的分数之和,以及从其余查询返回的分数之和,乘以 tie 参数的值。因此,tie_breaker
参数允许我们控制得分较低的查询如何影响最终得分。如果我们将 tie_breaker
参数设置为 1.0
,我们会得到准确的总和,同时将 tie 参数设置为 0.1
导致只有 10% 的分数(除了最大分数查询之外的所有分数)被添加到最终分数中。
dis_max
查询示例如下:
{ "query" : { "dis_max" : { "tie_breaker" : 0.99, "boost" : 10.0, "queries" : [ { "match" : { "title" : "crime" } }, { "match" : { "author" : "fyodor" } } ] } } }
如您所见,我们包含了 tie_breaker
和 boost
参数。除此之外,我们指定了 queries
参数,该参数保存将运行并用于生成结果文档联合的查询数组。
boosting
查询将 包裹在两个查询周围,并降低其中一个查询返回的文档的分数。需要定义 boosting 查询的三个部分: positive
部分,其中包含 文档分数将保持不变,negative
部分的结果文档的分数将降低,而 negative_boost
部分包含boost
值将用于降低第二部分的查询分数。 boosting
查询的优点是查询的结果(否定和肯定)都会出现在结果中,尽管某些查询的分数会降低.为了比较,如果我们将 bool
查询与 must_not
部分一起使用,我们将不会得到这样的结果询问。
假设我们想要在 title
字段中对术语 crime
进行简单术语查询的结果,并希望得到分数此类文件不得更改。但是,我们还希望在 year 字段中有从 1800
到 1900
范围内的文档,以及返回的文档分数通过这样的查询获得额外的0.5
。这样的查询将如下所示:
{ "query" : { "boosting" : { "positive" : { "term" : { "title" : "crime" } }, "negative" : { "range" : { "year" : { "from" : 1800, "to" : 1900 } } }, "negative_boost" : 0.5 } } }
constant_score
查询 包装另一个查询并返回一个 包装查询返回的每个文档的恒定分数。我们使用 boost
属性指定应该给予文档的分数,该属性默认为 1.0
。它允许我们严格控制为查询匹配的文档分配的分值。例如,如果我们希望所有具有 术语的文档的得分为 2.0
在 title
字段中的 code class="literal">crime,我们将以下查询发送到 Elasticsearch:
{ "query" : { "constant_score" : { "query" : { "term" : { "title" : "crime" } }, "boost" : 2.0 } } }
indices
查询在 对多个索引执行查询时很有用。它允许我们提供一组索引(indices
属性)和两个查询,如果我们查询 列表中的索引(query
属性)和将在所有其他索引上执行的第二个索引(no_match_query
属性)。例如,假设我们有一个名为 books 的别名,其中包含两个索引:library 和 users。我们想要做的是使用这个别名。但是,我们希望根据用于搜索的索引运行不同的查询。遵循此逻辑的示例查询如下所示:
{ "query" : { "indices" : { "indices" : [ "library" ], "query" : { "term" : { "title" : "crime" } }, "no_match_query" : { "term" : { "user" : "crime" } } } } }
在前面的查询中,query
属性中描述的查询是针对库索引和 no_match_query
部分中定义的查询运行的针对集群中存在的所有其他索引运行,对于我们的假设别名,这意味着用户索引。
no_match_query
属性 也可以有字符串值而不是查询。此字符串值可以是 all 或 none,但默认为 all。如果 no_match_query
属性设置为 all,则将返回索引中不匹配的文档。将 no_match_query
属性设置为 none
将导致索引中没有与该部分的查询不匹配的文档。
Elasticsearch 利用 Lucene 跨度查询,这允许我们在某些标记或短语靠近其他标记或短语时进行查询。基本上,我们可以称它们为位置感知查询。使用标准的非跨度查询时,我们无法进行位置感知查询;在某种程度上,phrase
查询允许这样做,但仅在某种程度上。因此,对于 Elasticsearch 和底层的 Lucene,术语是在句子的开头还是在结尾或靠近另一个术语并不重要。使用跨度查询时,它确实很重要。
Elasticsearch 中公开了以下跨度查询:
span term
查询span first
查询span near
查询span or
查询span not
查询span within
查询span contains
查询span multi
查询
在继续描述之前,让我们将文档索引到一个全新的索引,我们将使用它来展示跨度查询的工作原理。为此,我们使用以下命令:
curl -XPUT 'localhost:9200/spans/book/1' -d '{
"title" : "Test book",
"author" : "Test author",
"description" : "The world breaks everyone, and afterward, some are strong at the broken places"
}'
在我们的 上下文中,跨度是字段中的开始和结束标记位置。例如,在我们的例子中,worldbreaks everyone
可以是一个 span,world
也可以是一个 span。大家可能知道,Lucene在分析时,除了token之外,还包括一些附加参数,比如在token流中的位置。位置信息与术语相结合使我们能够使用 Elasticsearch 跨度查询(映射到 Lucene 跨度查询)来构建跨度。在接下来的几页中,我们将学习如何使用不同的 span 查询构建 span,以及如何控制匹配的文档。
span_term
查询是其他跨度查询的构建器。 span_term
查询类似于已经讨论过的 term
查询。就其本身而言,它就像 提到的术语查询一样工作——它匹配一个术语。它的定义很简单,如下所示(我们故意省略了部分查询,因为我们稍后会讨论):
{ "query" : { ... "span_term" : { "description" : { "value" : "world", "boost" : 5.0 } } } }
如您所见,它与标准术语查询非常相似。上面的查询是针对描述字段运行的,我们希望返回具有 world
术语的文档。我们还指定了升压,这也是允许的。
要记住的一件事是 span_term
查询,类似于标准术语查询,没有被分析。
span first
查询允许我们匹配仅在字段的第一个位置匹配的文档。为了 定义一个跨度优先查询,我们需要在其中嵌套任何其他跨度查询;对于 示例,我们已经知道一个跨度词查询。因此,让我们在 description
字段的前两个位置找到包含术语 world
的文档。我们通过发送以下查询来做到这一点:
{ "query" : { "span_first" : { "match" : { "span_term" : { "description" : "world" } }, "end" : 2 } } }
在结果中,我们将获得在本节开头已索引的文档。在 span first 查询的 match
部分,我们应该至少包含一个 span 查询,该查询应该在 指定的最大位置匹配结束
参数。
所以,要理解 一切,如果我们将 end
参数设置为 1
,我们不应该使用前面的查询来获取我们的文档。因此,让我们通过发送以下查询来检查它:
{ "query" : { "span_first" : { "match" : { "span_term" : { "description" : "world" } }, "end" : 1 } } }
对上述查询的响应如下:
{ "took" : 1, "timed_out" : false, "_shards" : { "total" : 5, "successful" : 5, "failed" : 0 }, "hits" : { "total" : 0, "max_score" : null, "hits" : [ ] } }
span near
查询允许我们 匹配具有其他跨度彼此靠近的文档,我们可以称之为 查询 compound
查询,因为它包装了另一个跨度查询。例如,如果我们要查找在术语 everyone
附近具有术语 world
的文档,我们将运行以下查询:
{ "query" : { "span_near" : { "clauses" : [ { "span_term" : { "description" : "world" } }, { "span_term" : { "description" : "everyone" } } ], "slop" : 0, "in_order" : true } } }
如您所见,我们在 span near
查询的 clauses
部分指定我们的查询。它是其他跨度查询的数组。 slop
参数定义跨度之间允许的术语数。 in_order
参数可用于将匹配项限制为仅与我们的查询以与定义相同的顺序匹配的那些文档。因此,在我们的例子中,我们将获取文档在描述字段中有 world everyone
,但没有 everyone world
。
所以让我们回到我们的 查询,现在它会返回0
结果。如果您查看我们的示例文档,您会注意到在术语 world 和每个人之间,存在一个附加术语,我们将 slop 参数设置为 0
(slop 在phrase
查询描述)。如果我们将它增加到 1,
我们将得到我们的结果。为了测试它,让我们发送以下查询:
{ "query" : { "span_near" : { "clauses" : [ { "span_term" : { "description" : "world" } }, { "span_term" : { "description" : "everyone" } } ], "slop" : 1, "in_order" : true } } }
{ "took" : 6, "timed_out" : false, "_shards" : { "total" : 5, "successful" : 5, "failed" : 0 }, "hits" : { "total" : 1, "max_score" : 0.10848885, "hits" : [ { "_index" : "spans", "_type" : "book", "_id" : "1", "_score" : 0.10848885, "_source" : { "title" : "Test book", "author" : "Test author", "description" : "The world breaks everyone, and afterward, some are strong at the broken places" } } ] } }
span or
查询允许我们包装其他 span 查询并聚合我们所有的匹配包裹。与 span_near
查询类似,span_or
查询使用子句数组来 指定其他跨度查询。例如,如果我们想获取在描述字段的前两个位置具有术语 world
的文档,或者具有术语 world
不超过 everyone
一词的单个位置,我们将向 Elasticsearch 发送以下查询:
{ "query" : { "span_or" : { "clauses" : [ { "span_first" : { "match" : { "span_term" : { "description" : "world" } }, "end" : 2 } }, { "span_near" : { "clauses" : [ { "span_term" : { "description" : "world" } }, { "span_term" : { "description" : "everyone" } } ], "slop" : 1, "in_order" : true } } ] } } }
span not
查询 允许我们指定查询的两个部分。第一个是包含部分,指定应该匹配哪些跨度查询,第二个部分是排除一个,它指定不应重叠的跨度查询第一个。为简单起见,如果来自排除项的查询与来自包含部分的查询匹配相同的跨度(或其中的一部分),则不会将此类文档作为此类
因此,为了说明该查询,让我们进行一个查询,该查询将返回所有具有从 单个术语构造的跨度并且具有术语 description
字段中的 class="literal">breaks。让我们也排除跨度与术语 world
和 everyone
相匹配的文档,这些文档最多位于彼此的单个位置,当这样的跨度与第一个跨度查询中定义的跨度重叠时。
{ "query" : { "span_not" : { "include" : { "span_term" : { "description" : "breaks" } }, "exclude" : { "span_near" : { "clauses" : [ { "span_term" : { "description" : "world" } }, { "span_term" : { "description" : "everyone" } } ], "slop" : 1 } } } } }
结果如下:
{ "took" : 1, "timed_out" : false, "_shards" : { "total" : 5, "successful" : 5, "failed" : 0 }, "hits" : { "total" : 0, "max_score" : null, "hits" : [ ] } }
正如您可能已经注意到的,查询的结果与我们预期的一样。找不到我们的文档,因为来自 exclude
部分的 span
查询与 来自 include
部分。
span_within
查询 允许我们找到包含在 另一个跨度。我们在 span_within
查询中定义了两个部分:小部分和大部分。 little
部分定义了一个 span
查询,需要用 span
使用 big
部分定义的查询。
例如,如果我们想找到一个文档,其术语 world
靠近术语中断,并且这些术语应该在由术语 world
和 afterward
彼此不超过 10 个术语,执行该操作的查询如下所示:
{ "query" : { "span_within" : { "little" : { "span_near" : { "clauses" : [ { "span_term" : { "description" : "world" } }, { "span_term" : { "description" : "breaks" } } ], "slop" : 0, "in_order" : false } }, "big" : { "span_near" : { "clauses" : [ { "span_term" : { "description" : "world" } }, { "span_term" : { "description" : "afterward" } } ], "slop" : 10, "in_order" : false } } } } }
span_contaning
查询可以看作与我们刚刚讨论的 span_within
查询相反。它允许我们匹配与其他跨度重叠的跨度。同样,我们使用跨度查询的两个部分:小部分和大部分。 little
部分定义了一个跨度查询,该查询需要包含在使用 big
部分定义的跨度查询中。
我们可以使用相同的 示例。如果我们想在 breaks
附近找到一个包含术语 world
的文档,并且这些术语应该在一个范围内受术语 world
和 afterward
约束,彼此之间不超过 10 个术语,执行该操作的查询看起来像如下:
{ "query" : { "span_containing" : { "little" : { "span_near" : { "clauses" : [ { "span_term" : { "description" : "world" } }, { "span_term" : { "description" : "breaks" } } ], "slop" : 0, "in_order" : false } }, "big" : { "span_near" : { "clauses" : [ { "span_term" : { "description" : "world" } }, { "span_term" : { "description" : "afterward" } } ], "slop" : 10, "in_order" : false } } } } }
Elasticsearch 支持的最后一种 查询是span_multi
查询。它允许我们包装我们讨论过的任何 多词查询(term
查询,< code class="literal">range 查询,wildcard
查询,regex
查询,fuzzy
查询,或 prefix
查询)作为 span
查询。
例如,如果我们想在描述字段的前两个位置查找词以前缀 wor
开头的文档,我们可以通过发送以下查询来实现:
{ "query" : { "span_multi" : { "match" : { "prefix" : { "description" : { "value" : "wor" } } } } } }
需要记住一件事——我们要使用的 multi 术语查询需要包含在匹配部分中span_multi 查询的“indexterm">。
到目前为止,我们已经看到 Elasticsearch 中有哪些查询可用,包括简单查询和可以对其他查询进行分组的查询。在继续讨论更复杂的主题之前,我们想讨论哪些查询应该用于哪个用例。当然,你可以用整本书来展示不同的查询用例,所以我们只展示其中的几个来帮助你了解你可以期待什么以及使用哪个查询。
您已经知道 哪些查询可用于查找哪些数据,我们想向您展示的是使用我们在 第 2 章,索引您的数据。为此,我们将从一些关于如何选择查询的指导方针开始,然后我们将向您展示示例用例并讨论为什么可以使用这些查询。
查询 Elasticsearch 的最简单示例之一是搜索确切的术语。确切地说,我们指的是被索引并写入 Lucene 倒排索引的术语的字符与字符比较。要运行这样的查询,我们可以使用 Elasticsearch 提供的 term
查询。这是因为它的内容没有被 Elasticsearch 分析。例如,假设我们要搜索 tags< 中所有值为 novel 的书籍/code> 字段,正如我们从映射中知道的那样,它没有被分析。为此,我们将运行以下命令:
curl -XGET 'localhost:9200/library/_search?pretty' -d '{
"query" : {
"term" : {
"tags" : "novel"
}
}
}'
可以运行的最简单的查询之一是在给定值范围内匹配文档的查询。通常这样的查询是更大查询或过滤器的一部分。对于 示例,将返回书籍的副本数从 1
到 3
包含在内,如下所示:
curl -XGET 'localhost:9200/library/_search?pretty' -d '{
"query" : {
"range" : {
"copies" : {
"gte" : 1,
"lte" : 3
}
}
}
}'
有许多使用 bool
查询的常见 示例。例如,非常简单的,例如查找具有术语列表的文档。我们想向您展示的是如何使用 bool
查询来提升一些文档。例如,如果我们要查找所有具有一份或多份副本并且具有在 1950
之后发布的文档,我们将运行以下查询:
curl -XGET 'localhost:9200/library/_search?pretty' -d '{
"query" : {
"bool" : {
"must" : [
{
"range" : {
"copies" : {
"gte" : 1
}
}
}
],
"should" : [
{
"range" : {
"year" : {
"gt" : 1950
}
}
}
]
}
}
}'
dis_max
查询,正如我们 所讨论的,允许我们控制得分较低的部分查询的影响力。例如,如果我们只想为 title
crime惩罚 的文档分配最高得分部分查询的分数> 字段或 raskolnikov
在字符字段中,我们将运行以下查询:
curl -XGET 'localhost:9200/library/_search?pretty' -d '{
"fields" : ["_id", "_score"],
"query" : {
"dis_max" : {
"tie_breaker" : 0.0,
"queries" : [
{
"match" : {
"title" : "crime punishment"
}
},
{
"match" : {
"characters" : "raskolnikov"
}
}
]
}
}
}'
{ "took" : 2, "timed_out" : false, "_shards" : { "total" : 5, "successful" : 5, "failed" : 0 }, "hits" : { "total" : 1, "max_score" : 0.70710677, "hits" : [ { "_index" : "library", "_type" : "book", "_id" : "4", "_score" : 0.70710677 } ] } }
现在让我们单独看看部分查询的得分。为此,我们将使用以下命令运行部分查询:
curl -XGET 'localhost:9200/library/_search?pretty' -d '{
"fields" : [ "_id", "_score" ],
"query" : {
"match" : {
"title" : "crime punishment"
}
}
}'
{ "took" : 4, "timed_out" : false, "_shards" : { "total" : 5, "successful" : 5, "failed" : 0 }, "hits" : { "total" : 1, "max_score" : 0.70710677, "hits" : [ { "_index" : "library", "_type" : "book", "_id" : "4", "_score" : 0.70710677 } ] } }
以下是下一条命令:
curl -XGET 'localhost:9200/library/_search?pretty' -d '{
"fields" : [ "_id", "_score" ],
"query" : {
"match" : {
"characters" : "raskolnikov"
}
}
}'
响应如下:
{ "took" : 2, "timed_out" : false, "_shards" : { "total" : 5, "successful" : 5, "failed" : 0 }, "hits" : { "total" : 1, "max_score" : 0.5, "hits" : [ { "_index" : "library", "_type" : "book", "_id" : "4", "_score" : 0.5 } ] } }
可以看到,我们的dis_max
查询返回的文档的分数等于得分最高的部分查询(第一个部分查询)。那是因为我们将 tie_breaker
属性设置为 0.0
。
拥有一个简单的搜索语法对用户来说非常有用,而且我们已经有了这样的——Lucene 查询语法。使用 query_string
查询是一个示例,我们可以通过允许 用户输入查询来利用它额外的控制字符。例如,如果我们想查找标题中包含术语 crime
和 punishment
并且 author
字段中的literal">fyodor dostoevsky 短语,并且在2000
(独家)和< code class="literal">2015(含),我们将使用以下命令:
curl -XGET 'localhost:9200/library/_search?pretty' -d '{
"query" : {
"query_string" : {
"query" : "+title:crime +title:punishment +author:\"fyodor dostoevsky\" -copies:{2000 TO 2015]"
}
}
}'
如您所见,我们使用 Lucene 查询语法来传递所有匹配要求,并让查询解析器构造适当的查询。
使用 query_string
查询非常方便,但它不能容错。如果我们的用户提供了不正确的 Lucene 语法, 查询将返回错误。因此,Elasticsearch 公开了第二个查询,它支持分析和完整的 Lucene 查询语法——simple_query_string
查询。使用这样的查询允许我们运行用户查询,而根本不关心解析错误。例如,让我们看一下以下查询:
curl -XGET 'localhost:9200/library/_search?pretty' -d '{
"query" : {
"query_string" : {
"query" : "+crime +punishment \"",
"default_field" : "title"
}
}
}'
响应将包含:
{ "error" : { "root_cause" : [ { "type" : "query_parsing_exception", "reason" : "Failed to parse query [+crime +punishment \"]", "index" : "library", "line" : 6, "col" : 3 } ], "type" : "search_phase_execution_exception", "reason" : "all shards failed", "phase" : "query", "grouped" : true, "failed_shards" : [ { "shard" : 0, "index" : "library", "node" : "7jznW07BRrqjG-aJ7iKeaQ", "reason" : { "type" : "query_parsing_exception", "reason" : "Failed to parse query [+crime +punishment \"]", "index" : "library", "line" : 6, "col" : 3, "caused_by" : { "type" : "parse_exception", "reason" : "Cannot parse '+crime +punishment \"': Lexical error at line 1, column 21. Encountered: <EOF> after : \"\"", "caused_by" : { "type" : "token_mgr_error", "reason" : "Lexical error at line 1, column 21. Encountered: <EOF> after : \"\"" } } } } ] }, "status" : 400 }
这意味着查询构造不正确并且发生了解析错误。这就是引入 simple_query_string
查询的原因。它使用查询解析器尝试处理用户错误并尝试猜测查询的外观。我们使用该解析器的查询将如下所示:
curl -XGET 'localhost:9200/library/_search?pretty' -d '{
"query" : {
"simple_query_string" : {
"query" : "+crime +punishment \"",
"fields" : ["title"]
}
}
}'
一个非常常见的使用 案例是为索引数据提供自动完成功能。正如我们所知,前缀查询不会被分析,而是根据字段中索引的术语来工作。因此,实际功能取决于索引期间生成的令牌。例如,假设我们想为 title
字段中的任何标记提供自动完成功能,并且用户提供了 wes
前缀.符合此类要求的查询如下所示:
curl -XGET 'localhost:9200/library/_search?pretty' -d '{
"query" : {
"prefix" : {
"title" : "wes"
}
}
}'
一个非常简单的 示例是使用 fuzzy
查询来查找具有与给定术语相似的术语的文档。例如,如果我们要查找所有值类似于 crimea 的文档,
我们将运行以下查询:
curl -XGET 'localhost:9200/library/_search?pretty' -d '{
"query" : {
"fuzzy" : {
"title" : {
"value" : "crimea",
"fuzziness" : 2,
"max_expansions" : 50
}
}
}
}'
最简单的位置感知查询,phrase
查询允许我们查找不是带有词条的文档,而是一个接一个地定位的词条——那些构成词组的词条。例如,仅匹配 otitle
字段中具有 westen nichts neues
短语的文档的查询如下所示:
curl -XGET 'localhost:9200/library/_search?pretty' -d '{
"query" : {
"match_phrase" : {
"otitle" : "westen nichts neues"
}
}
}'
我们要 讨论的最后一个用例是一个更复杂的位置感知查询示例,称为跨度查询。想象一下,我们想运行一个查询来查找在词 quiet
western front 短语的文档> 以及 all
术语之后的所有内容?这可以通过跨度查询来完成,以下命令显示了此类查询的外观:
curl -XGET 'localhost:9200/library/_search?pretty' -d '{
"query": {
"span_near": {
"clauses": [
{
"span_term": {
"title": "all"
}
},
{
"span_near": {
"clauses": [
{
"span_term": {
"title": "quiet"
}
},
{
"span_near": {
"clauses": [
{
"span_term": {
"title": "western"
}
},
{
"span_term": {
"title": "front"
}
}
],
"slop": 0,
"in_order": true
}
}
],
"slop": 3,
"in_order": true
}
}
],
"slop": 0,
"in_order": true
}
}
}'
请注意,跨度查询 未分析。我们可以通过查看 Explain API 的响应来看到这一点。要查看该响应,我们应该向 /library/book/1/_explain
REST 端点运行相同的请求正文(我们的查询)。输出中有趣的部分如下所示:
"description" : "weight(spanNear([title:all, spanNear([title:quiet, spanNear([title:western, title:front], 0, true)], 3, true)], 0, true) in 0) [PerFieldSimilarity], result of:",