vlambda博客
学习文章列表

读书笔记《elasticsearch-server-third-edition》搜索您的数据

Chapter 3. Searching Your Data

在上一章中,我们深入研究了 Elasticsearch 索引。我们在数据处理方面学到了很多东西。我们看到了如何调整 Elasticsearch 无模式机制,现在我们知道如何创建自己的映射。我们还看到了 Elasticsearch 的核心类型,并使用了分析器——既有 Elasticsearch 开箱即用的分析器,也有我们自己定义的分析器。我们使用批量索引,并在我们的索引中添加了额外的内部信息。最后,我们了解了段合并是什么,我们如何对其进行微调,以及如何在 Elasticsearch 中使用路由以及它为我们提供了什么。本章完全致力于查询。在本章结束时,您将学习以下主题:

  • 如何查询 Elasticsearch

  • 运行查询时内部发生的情况

  • Elasticsearch 中的基本查询有哪些

  • Elasticsearch 中有哪些复合查询允许我们对其他查询进行分组

  • 如何使用位置感知查询——跨度查询

  • 如何为工作选择正确的查询

Querying Elasticsearch


到目前为止,当我们搜索我们的数据时,我们使用了 REST API 和一个简单的查询或 GET 请求。同样,当我们更改索引时,我们也使用了 REST API 并将 JSON 结构的数据发送到 Elasticsearch。无论我们想要执行哪种类型的操作,无论是映射更改还是文档索引,我们都使用 JSON 结构化请求体来告知 Elasticsearch 操作细节。

当我们想要向 Elasticsearch 发送的不仅仅是一个简单的查询时,也会发生类似的情况,我们使用 JSON 对象对其进行结构化,然后在请求正文中将其发送到 Elasticsearch。这称为查询 DSL。从更广泛的角度来看,Elasticsearch 支持两种查询:基本查询和复合查询。 term 查询等基本查询用于查询实际数据。我们将在本章的基本查询部分介绍这些内容。第二种查询是复合查询,比如bool查询,可以组合多个查询。我们将在本章的复合查询部分介绍这些内容。

然而,这还不是全部。除了这两种类型的查询之外,某些查询还可以具有 过滤器,用于根据特定条件缩小结果范围。过滤查询不影响评分,通常非常高效且易于缓存。

更复杂的是,查询可以包含其他查询(别担心;我们将尝试解释所有这些!)。此外,一些查询可以包含过滤器,而其他查询可以同时包含查询和过滤器。虽然这还不是全部,但我们现在将坚持这个可行的解释。我们将在本章的复合查询部分和过滤你的第 4 章中的结果部分, 扩展您的查询知识。

The example data

如果没有另外说明,以下映射将用于本章的其余部分:

{
  "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 章,索引您的数据。

A simple query

查询 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
      }
    } ]
  }
}

好的!我们使用查询 DSL 获得了我们的第一个搜索结果。

Paging and result size

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

Returning the version value

除了返回的所有信息外,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
      }
    } ]
  }
}

如您所见,_version 部分是 出现在我们获得的单次点击中。

Limiting the score

对于非标准 用例,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,因此我们没有'没有得到任何文件作为回应。

限制 分数通常没有多大意义,因为比较查询之间的分数非常困难。但是,也许在您的情况下,将需要此功能。

Choosing the fields that we want to return

通过在请求正文中使用字段数组,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 字段相比,获取多个存储字段可能会更慢。

Source filtering

除了选择返回哪些 字段外,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" }
 }
}'

Using the script fields

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 字段作为响应。

Passing parameters to the script fields

让我们看看 脚本字段的另一个特性——附加参数的传递。我们可以使用变量名并将其值传递给等式,而不是在等式中使用值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 以稍微更有效的方式执行具有不同参数值的相同脚本。

Understanding the querying process


在阅读了前面的 部分之后,我们现在知道了 Elasticsearch 中的查询是如何工作的。您知道,Elasticsearch 在大多数情况下需要将查询分散到多个节点,获取结果,合并它们,从一个或多个分片中获取相关文档,并将最终结果返回给请求文档的客户端。我们没有讨论的是定义查询行为方式的另外两件事:搜索类型和查询执行偏好。我们现在将专注于 Elasticsearch 的这些功能。

Query logic

Elasticsearch 是一个分布式 搜索引擎,因此提供的所有功能都必须是分布式的。与查询完全相同。因为我们想讨论一些关于如何控制查询过程的更高级的话题,所以我们首先需要知道它是如何工作的。

现在让我们回到查询的工作原理。我们在第一章开始了这个理论,我们想回到它。默认情况下,如果我们不更改任何内容,查询过程将包括两个阶段:分散和收集阶段。聚合器节点(接收请求的节点)将首先运行分散阶段。在那个阶段,查询被分发到我们构建索引的所有分片(当然,如果不使用路由)。例如,如果它由 5 个分片和 1 个副本构建,则将查询 5 个物理分片(我们不需要查询分片及其副本,因为它们包含相同的数据)。每个查询的分片将只返回文档标识符和文档的分数。发送分散查询的节点将等待所有分片完成任务,收集结果,并对它们进行适当的排序(在这种情况下,从最高分到最低分)。

之后,将发送一个新的请求来构建搜索结果。但是,现在只针对那些保存文档的碎片来构建响应。在大多数情况下,Elasticsearch 不会将请求发送到所有分片,而是发送到其子集。那是因为我们通常不会得到查询的完整结果,而只能得到其中的一部分。这个阶段称为收集阶段。在收集所有文档后,构建最终响应并作为查询结果返回。这是基本和默认的 Elasticsearch 行为,但我们可以更改它。

Search type

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" }
 }
}'

Search execution preference

除了可以控制查询如何执行之外,我们还可以控制在哪些分片上执行查询。默认情况下,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" }
 }
}'

Search shards API

在讨论搜索 偏好时,我们还想提及 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)。这使我们能够诊断集群状态同步的问题。

Basic queries


Elasticsearch 具有广泛的搜索和数据分析功能,这些功能以不同的查询、过滤器、聚合等形式公开。在本节中,我们将重点介绍 Elasticsearch 提供的基本查询。我们所说的基本查询是指那些不将其他查询组合在一起而是单独运行的查询。

The term query

术语查询是 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 我们想要使用的值。

The terms query

terms 查询是 term 查询的扩展。它允许我们匹配在其 内容中具有特定术语的文档,而不是单个术语。 term 查询允许我们匹配单个未分析的术语,terms 查询允许我们匹配 其中的多个。例如,假设我们想要获取所有在标签字段中具有词小说或书籍的文档。为此,我们将运行以下查询:

{
  "query" : {
  "terms" : {
    "tags" : [ "novel", "book" ]
  }
  }
}

前面的查询返回所有在标签字段中包含一个或两个搜索词的文档。这是一个要记住的关键 点——terms 查询将查找具有任何提供的术语的文档。

The match all query

匹配所有查询是 Elasticsearch 中可用的最简单查询之一。它允许 我们匹配索引中的所有文档。如果我们想从索引中获取所有文档,我们只需运行以下查询:

{
  "query" : {
  "match_all" : {}
  }
}

我们还可以在查询中包含 boost,它将被赋予它匹配的所有文档。例如,如果我们想为 match all 查询中的所有文档添加 2.0 的提升,我们将发送 以下查询到 Elasticsearch:

{
  "query" : {
  "match_all" : { 
    "boost" : 2.0 
  }
  }
}

The type query

一个非常简单的查询,可以让我们找到所有特定类型的文档。例如,如果我们想在我们的图书馆索引中搜索所有具有 book 类型的文档,我们将运行 以下查询:

{
  "query" : {
  "type" : {
    "value" : "book"
  }
  }
}

The exists query

一个允许我们查找 所有在 中具有值的文档的查询定义的字段。例如,要查找在 tags 字段中有值的文档,我们将运行以下查询:

{
  "query" : {
  "exists" : {
    "field" : "tags"
  }
  }
}

The missing query

与 exists 查询相反, 缺失查询返回具有 null 的文档给定字段中的值或根本没有值。例如,要查找 tags 字段中没有值的所有文档,我们将运行以下查询:

{
  "query" : {
  "missing" : {
    "field" : "tags"
  }
  }
}

The common terms query

常用词查询 是一种现代 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 以获得不太精确的评分,但查询速度稍快。

    Note

    与 term 和 terms 查询不同,常用词查询由 Elasticsearch 分析。

The match query

match 查询采用 query 值> 参数,对其进行分析,并从中构造适当的查询。当使用 match 查询时,Elasticsearch 会为我们选择的字段选择合适的分析器,因此您可以确定 terms 传递给 match 查询将由索引期间使用的同一分析器处理。请记住,match 查询(以及 multi_match 查询)不支持 Lucene 查询语法;但是,它非常适合作为搜索框的查询处理程序。最简单的匹配(和默认)查询如下所示:

{
  "query" : {
    "match" : {
      "title" : "crime and punishment"
    }
  }
}

前面的查询 将匹配所有具有术语crime、and,或title 字段中的“literal">惩罚。但是,前面的查询只是最简单的查询;我们现在将讨论多种类型的匹配查询。

The Boolean match query

Boolean match 查询是分析提供的文本并从中进行布尔查询的查询。这也是匹配查询的默认类型。有一些参数可以让我们控制 Boolean match 查询的行为:

  • operator:这个参数可以取或 或 and,并控制使用哪个布尔运算符连接创建的布尔子句。默认值为或。如果我们想要匹配查询中的所有术语,我们应该使用 and 布尔运算符。

  • analyzer:指定将用于分析查询文本的分析器的名称,默认为 默认分析器。

  • fuzziness:提供这个参数的值可以让我们构造模糊查询。此参数的值可以变化。对于数字字段,应设置为数值;对于基于日期的字段,可以设置为 毫秒 或 时间 值,例如 2h ;对于文本字段,可以设置为 0、1 或 2 (Levenshtein 算法中的编辑距离 - https://en.wikipedia.org/wiki/Levenshtein_distance),AUTO(它允许 Elasticsearch 控制模糊查询的程度构造,这是一个首选值)。最后,对于文本字段,它也可以设置为 0.0 到 1.0 之间的值,这导致编辑距离被计算为术语长度减去 1.0 乘以提供的模糊度值。一般来说,模糊度越高,允许的术语之间的差异就越大。

  • prefix_length:这允许控制行为 的模糊查询。有关此参数值的更多信息,请参阅本章中的模糊查询部分。

  • max_expansions:这允许 控制模糊查询的行为。有关此参数值的更多信息,请参阅本章中的模糊查询部分。

  • zero_terms_query:这允许我们 指定查询的行为,当所有术语都被分析器(例如,因为停用词)。它可以设置为 none 或 all,默认为 none。设置为 none 时,分析器删除所有查询词时不会返回任何文档。如果设置为 all,则返回所有文档。

  • cutoff_frequency:它允许将查询分为两组:一组是高频词,一组是低频条款。请参阅常用术语查询的说明,了解如何使用此参数。

  • lenient:当设置为true时(默认它是false),它可以让我们忽略数据不兼容导致的异常,例如尝试使用字符串值查询数字字段。

参数应该包含在我们正在对其运行查询的字段的名称中。因此,如果我们想针对 title 字段运行示例布尔匹配查询,我们发送如下查询:

{
  "query" : {
  "match" : {
    "title" : {
    "query" : "crime and punishment",
    "operator" : "and"
    }
  }
  }
}

The phrase match query

phrase match 查询类似于 Boolean 查询,但它不是从分析的文本构造布尔子句,而是构造phrase 查询。您可能想知道 Lucene 和 Elasticsearch 是什么短语——嗯,它是按 顺序依次排列的两个或多个术语。以下参数可用:

  • slop:一个整数值,定义在 text 查询要被视为短语的匹配项。该参数的默认值为 0,表示不允许添加额外的单词。

  • analyzer:指定将用于分析查询文本的分析器的名称,默认为默认分析器。

针对标题字段的示例 phrase match 查询类似于以下代码:

{
  "query" : {
  "match_phrase" : {
    "title" : {
    "query" : "crime punishment",
    "slop" : 1
    }
  }
  }
}

请注意,我们从查询中删除了 and 术语,但是因为 slop 设置为 1, 它仍然会匹配我们的文档因为我们允许在我们的术语之间出现一个术语。

The match phrase prefix query

匹配查询的最后一种类型是 匹配短语前缀 查询。此查询与 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。

The multi match query

它与 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 公开了一些额外的,允许对其行为进行更多控制:

  • tie_breaker:这允许我们指定最小和最大评分查询项之间的平衡,值可以从 0.0 到 1.0。使用时,文档的得分等于最佳得分元素加上 tie_breaker 乘以文档中所有其他匹配字段的得分。因此,当设置为 0.0, Elasticsearch 将只使用得分最高的匹配元素的得分。您可以在本章 的dis_max 查询部分了解更多信息.

The query string 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 查询提供了很多允许我们控制查询行为的参数,并且该查询的参数列表相当广泛:

  • query:指定 query 文本。

  • default_field:这指定了将执行查询的 默认字段。它默认为 index.query.default_field 属性,默认设置为 _all。

  • default_operator:指定默认逻辑运算符(or 或 and) 在未指定运算符时使用。该参数的默认值为或。

  • analyzer:指定用于分析查询参数中提供的查询的分析器的名称。

  • allow_leading_wildcard:这指定是否允许通配符作为 术语的第一个字符。它默认为 true。

  • lowercase_expand_terms:这 指定作为查询重写结果的术语是否应小写。它默认为 true,这意味着重写的术语将小写。

  • enable_position_increments:这 指定是否应转动位置增量 在结果查询中打开。它默认为 true。

  • fuzzy_max_expansions:如果模糊查询是用过的。默认为 50。

  • fuzzy_prefix_length:指定生成的模糊查询的前缀长度,默认为0 。要了解更多信息,请查看 fuzzy 查询描述。

  • phrase_slop:指定短语slop,默认为0 。要了解更多信息,请查看 phrase match 查询描述。

  • boost:这指定 boost 值,它将可以使用,默认为 1.0。

  • analyze_wildcard:此指定是否应分析通配符查询生成的术语。它默认为 false,这意味着不会分析这些术语。

  • auto_generate_phrase_queries:指定是否从 查询中自动生成短语查询。它默认为 false,这意味着不会自动生成短语查询。

  • minimum_should_match:这个控制生成的布尔应该有多少 code> 子句应与文档匹配,以使文档被视为命中。该值可以以百分比形式提供;例如,50%,这意味着至少 50% 的给定术语应该匹配。它也可以作为整数值提供,例如 2,这意味着至少有 2 个术语必须匹配。

  • fuzziness:控制生成的fuzzy行为> 查询。有关详细信息,请参阅 match 查询说明。

  • max_determined_states:这个 默认为10000,设置自动机处理正则表达式的状态数查询。它用于禁止使用正则表达式的非常昂贵的查询。

  • locale:设置应该用于字符串值转换的语言环境。默认情况下,它设置为 ROOT。

  • time_zone:设置在基于日期的字段上运行的范围查询应该使用的时间区域.

  • lenient:这可以取 true的值或 false。如果设置为 true,则将忽略基于格式的失败。默认情况下,它设置为 false。

请注意,Elasticsearch 可以重写 查询字符串 查询,因此,Elasticsearch 允许我们传递额外的控制重写方法的参数。但是,有关此过程的更多详细信息,请转到本章中的了解查询过程部分。

Running the query string query against multiple fields

可以对多个字段运行 查询字符串 查询。为此,需要在查询正文中提供 fields 参数,该参数应包含字段名称的数组。对多个字段运行查询字符串查询有两种方法:默认方法使用 Boolean 查询进行查询,另一种方法可以使用 dis_max 查询。

为了使用 dis_max 查询,应该在查询正文中添加 use_dis_max 属性并将其设置为 “真”。示例查询将类似于以下代码:

{
 "query" : {
  "query_string" : {
   "query" : "crime punishment",
   "fields" : [ "title", "otitle" ],
   "use_dis_max" : true
  }
 }
}

The simple query string query

简单查询字符串 查询使用最新查询之一 解析器 - 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 属性。

The identifiers query

这是一个简单的查询, 将返回的文档过滤为仅提供了 的文档身份标识。它适用于内部 _uid 字段,因此不需要启用 _id 字段。这种查询的最简单版本如下所示:

{
  "query" : {
  "ids" : {
   "values" : [ "1", "2", "3" ]
  }
  }
}

此查询将仅返回那些在 values 数组中存在标识符之一的文档。我们可以使 identifiers 查询复杂一点,并根据文档类型限制文档。例如,如果我们只想包含书籍类型中的文档,我们将发送以下查询:

{
 "query" : {
  "ids" : {
   "type" : "book",
   "values" : [ "1", "2", "3" ]
  }
 }
}

如您所见,我们已将 type 属性添加到查询中,并将其值设置为 type 我们有兴趣。

The prefix query

此查询在其配置中类似于 term 查询和 multi term 查询其逻辑时。 prefix 查询允许我们匹配 文档,这些文档在以给定开头的特定字段中具有值字首。例如,如果我们想在 title 字段中查找所有值以 cri 开头的文档,我们将运行以下查询:

{
  "query" : {
    "prefix" : {
      "title" : "cri"
    }
  }
}

与 term 查询类似,您还可以在前缀查询中包含 boost 属性,这将影响给定前缀的重要性.例如,如果我们想更改我们之前的查询并给我们的查询一个 3.0 的 boost,我们将发送以下内容询问:

{
  "query" : {
  "prefix" : {
    "title" : {
    "value" : "cri",
    "boost" : 3.0
    }
  }
  }
}

Note

请注意,前缀查询是由 Elasticsearch 重写的,因此 Elasticsearch 允许我们传递一个额外的参数,即控制重写方法。但是,有关该过程的更多详细信息,请参阅本章中的了解查询过程部分。

The fuzzy query

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 查询行为:

  • value:指定实际查询。

  • boost:指定查询的 boost 值。它默认为 1.0。

  • fuzziness:控制生成的fuzzy行为> 查询。有关详细信息,请参阅 match 查询说明。

  • prefix_length:这是差异词的公共前缀的长度。默认为 0。

  • max_expansions:这指定 查询将扩展到的最大术语数。默认值是无界的。

参数应该包裹在我们正在运行查询的字段的名称中。因此,如果我们想修改之前的查询并添加额外的参数,查询将类似于以下代码:

{
 "query" : {
  "fuzzy" : {
   "title" : {
    "value" : "crme",
    "fuzziness" : 2
   }
  }
 }
}

The wildcard query

一个查询 允许我们使用 * 和 ? 我们搜索的值中的通配符。除此之外,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
   }
  }
 }
}

Note

请注意,通配符查询不是非常注重性能的查询,应尽可能避免;尤其要避免使用前导通配符(以通配符开头的术语)。 wildcard 查询被 Elasticsearch 重写,因此 Elasticsearch 允许我们传递一个额外的参数,即即,控制重写方法。有关此过程的更多详细信息,请参阅本章中的了解查询过程部分。另请记住,不分析 wildcard 查询。

The range query

一个查询,允许我们查找 具有特定范围内的字段值并且适用于数字字段以及基于字符串的字段和日期的文档基于字段(只是映射到不同的 Apache Lucene 查询)。 range 查询应该针对单个字段运行,并且查询 参数应该包含在字段名称中.支持以下参数:

  • gte:查询将匹配 值大于或等于此参数提供的值的文档

  • gt:查询将匹配 值大于此参数提供的值的文档

  • lte:查询将匹配值小于或等于此参数提供的值的文档

  • lt:查询将匹配文档 的值低于此参数提供的值

举个例子,如果我们要在 year 字段,我们将运行以下查询:

{
 "query" : {
  "range" : {
   "year" : {
    "gte" : 1700,
    "lte" : 1900
   }
  }
 }
}

Regular expression query

正则表达式查询 允许我们使用正则表达式作为 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。

The more like this query

在 Elasticsearch 2.0 中进行了重大修改的查询之一,更像 this 查询允许我们检索 与提供的文本或提供的文档相似(或不相似)的文档。

这个查询越相似,我们就可以获取与提供的文本相似的文档。 Elasticsearch 支持一些参数来定义更像这个查询应该如何工作:

  • fields:查询应该运行的字段数组。它默认为 _all 字段。

  • like:这个参数有两种形式:它允许我们提供一个文本,返回的文档应该是 similar to 或返回文档应该与之相似的文档数组。

  • unlike:这类似于 like 参数, 但它允许我们定义返回的文档不应与之相似的文本或文档。

  • min_term_freq:最小词条频率(对于文档中的词条)低于该词条将被忽略。默认为 2。

  • max_query_terms:将包含在任何生成的查询中的最大 字词数。默认为 25。较高的值可能意味着较高的精度,但较低的性能。

  • stop_words:一个数组,当比较文档和查询。默认为空。

  • min_doc_freq:为了不被忽略,该词必须出现在其中的最小文档数.它默认为 5,这意味着一个术语需要出现在至少五个文档中。

  • max_doc_freq:为了不被忽略,该术语可能出现的最大文档数。默认情况下,它是无界的(设置为 0)。

  • min_word_len: 单个单词的最小长度,低于该长度的单词将被忽略。默认为 0。

  • max_word_len:单个单词的最大长度,超过该长度将被忽略。它默认为无界(这意味着将值设置为 0)。

  • boost_terms:将使用的 boost 值在提升每个术语时。默认为 0。

  • boost:将使用的 boost 值提升查询时。它默认为 1。

  • include:这指定 输入文档是否应包含在查询返回的结果中。默认为 false,表示不包含输入文档。

  • minimum_should_match:这个控制需要在结果文档中匹配的词条的数量。默认情况下,它设置为 30%。

  • analyzer:用于分析我们提供的文本的分析器的名称。

更类似于此查询的示例如下所示:

{
 "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"
    ]
  }
  }
}

Compound queries


在本章的基本查询部分,我们讨论了 Elasticsearch 公开的最简单的查询。我们还讨论了 在 Span 查询 部分中称为跨度查询的位置感知查询。然而,简单查询和跨度查询并不是 Elasticsearch 提供的唯一查询。我们称之为复合查询,允许我们将多个查询连接在一起或改变其他查询的行为。您可能想知道是否需要这样的功能。您的部署可能 不需要它,但除了简单查询之外的任何东西都可能需要复合查询。例如,将简单的术语查询与 match_phrase 查询相结合以获得更好的搜索结果,这可能是复合查询使用的一个很好的候选。

The bool query

bool 查询允许我们 包装几乎无限数量的查询并将它们与逻辑值连接< a id="id542" class="indexterm">使用以下部分之一:

  • should:包含在此部分中的查询 可能匹配也可能不匹配。必须匹配的 should 部分的数量由 minimum_should_match 参数控制

  • 必须:包装到本节中的查询必须匹配才能返回文档。

  • must_not:包装到此部分时的查询必须不匹配才能返回文档。

前面提到的每个部分都可以在单个 bool 查询中出现多次。这允许我们构建具有多个嵌套级别的非常复杂的查询(您可以将 bool 查询包含在另一个 bool 查询中) .请记住,结果文档的分数将通过对文档匹配的所有包装查询求和来计算。

除了前面的部分,我们还可以在查询体中添加以下参数来控制其行为:

  • filter:这允许我们 指定应该用作过滤器的查询部分。您可以在 的 过滤您的结果 部分阅读有关过滤器的更多信息第 4 章,扩展您的查询知识。

  • boost:指定查询中使用的 boost ,默认为 1.0 。 boost 越高,匹配文档的得分越高。

  • 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"
        }
      }
    }
  }
}

Note

请注意,must、should 和 must_not 部分可以包含单个查询或查询数组。

The dis_max query

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 参数,该参数保存将运行并用于生成结果文档联合的查询数组。

The boosting query

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
    }
  }
}

The constant_score query

constant_score 查询 包装另一个查询并返回一个 包装查询返回的每个文档的恒定分数。我们使用 boost 属性指定应该给予文档的分数,该属性默认为 1.0。它允许我们严格控制为查询匹配的文档分配的分值。例如,如果我们希望所有具有 术语的文档的得分为 2.0在 title 字段中的 code class="literal">crime,我们将以下查询发送到 Elasticsearch:

{
  "query" : {
    "constant_score" : {
      "query" : {
        "term" : {
          "title" : "crime"
        }
      }, 
      "boost" : 2.0
    }
  }
}

The indices query

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 将导致索引中没有与该部分的查询不匹配的文档。

Using span queries


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"
}'

A span

在我们的 上下文中,跨度是字段中的开始和结束标记位置。例如,在我们的例子中,worldbreaks everyone 可以是一个 span,world 也可以是一个 span。大家可能知道,Lucene在分析时,除了token之外,还包括一些附加参数,比如在token流中的位置。位置信息与术语相结合使我们能够使用 Elasticsearch 跨度查询(映射到 Lucene 跨度查询)来构建跨度。在接下来的几页中,我们将学习如何使用不同的 span 查询构建 span,以及如何控制匹配的文档。

Span term query

span_term 查询是其他跨度查询的构建器。 span_term 查询类似于已经讨论过的 term 查询。就其本身而言,它就像 提到的术语查询一样工作——它匹配一个术语。它的定义很简单,如下所示(我们故意省略了部分查询,因为我们稍后会讨论):

{
  "query" : {
 ...
    "span_term" : { 
    "description" : { 
     "value" : "world", 
     "boost" : 5.0 
    }
   }
  }
}

如您所见,它与标准术语查询非常相似。上面的查询是针对描述字段运行的,我们希望返回具有 world 术语的文档。我们还指定了升压,这也是允许的。

要记住的一件事是 span_term 查询,类似于标准术语查询,没有被分析。

Span first query

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" : [ ]
  }
}

所以它按预期工作。这 是因为我们索引中的第一个术语将是术语 the 而不是术语 world 我们搜索的。

Span near query

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
  }
 }
}

Elasticsearch 返回的结果如下:

{
  "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 query

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 query

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 query

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 containing query

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
    }
   }
  }
 }
}

Span multi query

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">。

Performance considerations

在讨论跨度查询的最后几句话。请记住,在处理 功率时,它们的成本更高,因为不仅必须匹配术语,而且还必须计算和检查位置。这意味着 Lucene 和 Elasticsearch 将需要更多的 CPU 周期来计算所有需要的信息来查找匹配的文档。您可以预期跨度查询比不考虑位置的查询要慢。

Choosing the right query


到目前为止,我们已经看到 Elasticsearch 中有哪些查询可用,包括简单查询和可以对其他查询进行分组的查询。在继续讨论更复杂的主题之前,我们想讨论哪些查询应该用于哪个用例。当然,你可以用整本书来展示不同的查询用例,所以我们只展示其中的几个来帮助你了解你可以期待什么以及使用哪个查询。

The use cases

您已经知道 哪些查询可用于查找哪些数据,我们想向您展示的是使用我们在 第 2 章,索引您的数据。为此,我们将从一些关于如何选择查询的指导方针开始,然后我们将向您展示示例用例并讨论为什么可以使用这些查询。

Limiting results to given tags

查询 Elasticsearch 的最简单示例之一是搜索确切的术语。确切地说,我们指的是被索引并写入 Lucene 倒排索引的术语的字符与字符比较。要运行这样的查询,我们可以使用 Elasticsearch 提供的 term 查询。这是因为它的内容没有被 Elasticsearch 分析。例如,假设我们要搜索 tags< 中所有值为 novel 的书籍/code> 字段,正如我们从映射中知道的那样,它没有被分析。为此,我们将运行以下命令:

curl -XGET 'localhost:9200/library/_search?pretty' -d '{
 "query" : {
 "term" : {
 "tags" : "novel"
 }
 }
}'

Searching for values in a range

可以运行的最简单的查询之一是在给定值范围内匹配文档的查询。通常这样的查询是更大查询或过滤器的一部分。对于 示例,将返回书籍的副本数从 1 到 3 包含在内,如下所示:

curl -XGET 'localhost:9200/library/_search?pretty' -d '{
 "query" : {
 "range" : {
 "copies" : {
 "gte" : 1,
 "lte" : 3
 }
 }
 }
}'

Boosting some of the matched documents

有许多使用 bool 查询的常见 示例。例如,非常简单的,例如查找具有术语列表的文档。我们想向您展示的是如何使用 bool 查询来提升一些文档。例如,如果我们要查找所有具有一份或多份副本并且具有在 1950 之后发布的文档,我们将运行以下查询:

curl -XGET 'localhost:9200/library/_search?pretty' -d '{
 "query" : {
 "bool" : {
 "must" : [
 { 
 "range" : {
 "copies" : {
 "gte" : 1
 } 
 }
 }
 ],
 "should" : [
 {
 "range" : {
 "year" : {
 "gt" : 1950
 }
 }
 }
 ]
 }
 }
}'

Ignoring lower scoring partial queries

dis_max 查询,正如我们 所讨论的,允许我们控制得分较低的部分查询的影响力。例如,如果我们只想为 titlecrime惩罚 的文档分配最高得分部分查询的分数> 字段或 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。

Using Lucene query syntax in queries

拥有一个简单的搜索语法对用户来说非常有用,而且我们已经有了这样的——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 查询语法来传递所有匹配要求,并让查询解析器构造适当的查询。

Handling user queries without errors

使用 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"]
 }
 }
}'

如果您运行前面的 查询,您将看到 Elasticsearch 返回了正确的文档,即使查询没有正确构造。

Autocomplete using prefixes

一个非常常见的使用 案例是为索引数据提供自动完成功能。正如我们所知,前缀查询不会被分析,而是根据字段中索引的术语来工作。因此,实际功能取决于索引期间生成的令牌。例如,假设我们想为 title 字段中的任何标记提供自动完成功能,并且用户提供了 wes 前缀.符合此类要求的查询如下所示:

curl -XGET 'localhost:9200/library/_search?pretty' -d '{
 "query" : {
 "prefix" : {
 "title" : "wes"
 }
 }
}'

Finding terms similar to a given one

一个非常简单的 示例是使用 fuzzy 查询来查找具有与给定术语相似的术语的文档。例如,如果我们要查找所有值类似于 crimea 的文档, 我们将运行以下查询:

curl -XGET 'localhost:9200/library/_search?pretty' -d '{
 "query" : {
 "fuzzy" : {
 "title" : {
 "value" : "crimea",
 "fuzziness" : 2,
 "max_expansions" : 50
 }
 }
 }
}'

Matching phrases

最简单的位置感知查询,phrase 查询允许我们查找不是带有词条的文档,而是一个接一个地定位的词条——那些构成词组的词条。例如,仅匹配 otitle 字段中具有 westen nichts neues 短语的文档的查询如下所示:

curl -XGET 'localhost:9200/library/_search?pretty' -d '{
 "query" : {
 "match_phrase" : {
 "otitle" : "westen nichts neues"
 }
 }
}'

Spans, spans everywhere

我们要 讨论的最后一个用例是一个更复杂的位置感知查询示例,称为跨度查询。想象一下,我们想运行一个查询来查找在词 quietwestern 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:",

Summary


本章一直是关于查询过程的。我们首先研究了如何查询 Elasticsearch 以及 Elasticsearch 在需要处理查询时会做什么。我们还了解了基本查询和复合查询,因此我们现在既可以使用简单查询,也可以使用将多个小查询组合在一起的查询。最后,我们讨论了如何为给定的用例选择正确的查询。

在下一章中,我们将扩展我们的查询知识。我们将从过滤我们的查询开始,然后转到突出显示可能性和使用 Elasticsearch API 验证我们的查询的方法。我们将讨论搜索结果的排序和查询重写,这将向我们展示 Elasticsearch 内部的某些查询会发生什么。