
2.2 检索数据
我们已经为服务器准备了一些文档,尽管数量有限。现在是时候行动起来了,看看如何检索、搜索和聚合这些文档。从2.2.1节开始,我们将通过执行查询和检索数据来进行实践操作。
让我们从找出库存(在这个案例中是books索引)中总共有多少本书这样一个基本的需求开始。Elasticsearch提供了一个_count API来满足这个需求。它非常简单明了,下面我们就来看一下。
2.2.1 计算文档数量
_count API能够满足计算索引中文档总数的需求。在books索引上调用_count端点,就会得到此索引中存储的文档数量,如代码清单2-3所示。
代码清单2-3 使用_count API计算文档数量
GET books/_count ←--- 计算书的数量
这里应该返回如图2-9所示的响应。

图2-9 调用_count API返回的JSON响应
用粗体展示的count字段表示books索引中的文档总数。我们还可以通过添加逗号分隔的索引来使用相同的API一次性从多个索引中获取数据,如GET books1,books2/_count。(注意,如果现在执行这个查询,系统将抛出一个index_not_found_exception的异常,表明books2索引并不存在。)
我们也可以通过发起一个GET _count调用来获取所有索引中的文档数量。这将返回所有可用的文档的数量,包括系统和隐藏的索引。下面我们就使用API来检索文档。
2.2.2 检索文档
就像把钱存入银行账户一样,我们之前把文档索引到了Elasticsearch中。现在我们需要访问这些文档(就像从银行账户中取钱一样)。
每个被索引的文档中都有一个唯一的标识符,有些标识符是由我们提供的,剩余的标识符则是由Elasticsearch自动生成的。检索文档的操作依赖于对文档ID的访问。在本节中,我将介绍以下操作:
❏ 给定一个ID,获取单个文档;
❏ 给定一组ID,获取多个文档;
❏ 一次性获取所有文档(没有ID,没有搜索条件)。
我们可以通过调用给定ID的文档API从Elasticsearch中获取单个文档,也可以使用另一个API查询(ids)获取多个文档。我还将介绍如何使用_search API一次性检索所有文档。
1. 检索单个文档
从Elasticsearch中检索文档就像使用主键从数据库中获取记录一样简单直接,只要有文档的ID即可。为此,我们在Kibana控制台发出一个带有API URL和文档ID的GET命令。使用标识符检索文档的通用格式是 GET <index>/_doc/<id>。要检索单个文档,可以发出GET命令,并提供ID 1,如代码清单2-4所示。
代码清单2-4 通过ID检索单个文档
GET books/_doc/1
如果这个命令执行成功,在代码编辑器的右侧窗格中将显示一个响应,如图2-10所示。

图2-10 通过ID检索图书文档
响应包含两部分信息:在_source 标签下的原始文档(源数据)和这个文档的元数据(如index、id、found、version等)。如果不需要文档的元数据,而只想要原始文档,可以将_doc端点改为_source,即GET books/_source/1,然后重新执行查询。
2. 根据ID检索多个文档
当我们需要根据一组标识符检索一组文档时,可以使用ids查询。此查询可以根据一组文档ID来获取文档。这是一种更简单的方式,给定一个文档ID列表,一次性获取所有文档。
需要注意的一点是,与使用文档API来获取文档的其他查询不同,ids查询使用搜索API,具体来说就是_search端点。让我们来看看实际的操作,如代码清单2-5所示。
注意 Elasticsearch提供了一种用于编写查询的领域特定语言(domain specific language,DSL),通常称为Query DSL。它是一种简单的基于JSON的查询编写语言,在Kibana中被广泛用于编写查询。本章(及本书的其余部分)中的大多数查询都是基于Query DSL编写的。
代码清单2-5 使用ids查询检索多个文档
GET books/_search { "query": { "ids": { "values": [1,2,3] } } }
代码清单2-5中查询的请求体是使用query对象构造的,这个query对象有一个名为ids的内部对象。文档ID是在查询中以数组形式提供的值。如图2-11所示,响应表明查询成功命中(结果),返回了3个指定ID的文档。

图2-11 在_search端点上使用ids查询根据一组ID检索文档
抑制响应中的源数据
有时,我们可以避免检索不必要的源数据,从而防止网络阻塞和带宽浪费。例如,假设原始文档有500个字段,但并非所有字段都需要检索。我们可以通过调整响应中的字段来控制哪些字段发送给客户端,哪些不发送。
我们也可以在请求中设置标志"_source": false来完全抑制在响应中发送源数据。此标志在根层级设置(与query层级相同):
GET books/_search { "_source": false, ←--- 抑制源数据 "query": { "ids": { "values": [1,2,3] } } }
当然,这种检索文档的方式很麻烦。想象一下,如果想用1000个ID来检索1000个文档,该怎么办?幸运的是,我们不需要使用ids查询,而是可以利用本书稍后介绍的其他搜索功能。
到目前为止,我们已经了解了文档API,它根据ID来获取文档。这并不是真正意义上的搜索功能,而是一种数据检索的方式。Elasticsearch提供了大量的搜索功能,可以根据各种条件和标准来获取结果。在本书的后面,我会专门用几章来讨论从基础到高级的搜索内容。作为好奇的开发者,我们现在就想看到高级搜索查询的实际效果,不是吗?下面我们就讨论一下基于搜索功能的查询集合。
3. 检索所有文档
在前面检索多个文档的操作中,我们使用了基础的_search端点。使用相同的语法,我们可以编写一个查询来从索引中获取所有书,如代码清单2-6所示。
代码清单2-6 从books索引中检索所有文档
GET books/_search ←--- URL不需要请求体
正如所见,当搜索所有记录时,不需要将请求体附加到查询中(参见下页的“通用搜索是match_all查询的简写形式”,其中解释了这个查询的语法)。如图2-12所示,响应按预期返回了books索引中的3个文档。
如果你对查询语法或响应字段感到困惑,不用担心,本书后面我会介绍其中的语义。

图2-12 使用搜索API检索所有文档
通用搜索是match_all查询的简写形式
之前的查询GET books/_search是一个名为match_all的特殊查询的简写形式。通常,我们会在URL中添加一个包含query子句的请求体,如代码清单2-5所示。如果我们在调用_search端点时省略了请求体,就相当于告诉Elasticsearch这个查询是一个match_all查询,这意味着匹配所有内容。但是,如果我们打算在不附加任何query子句的情况下获取所有记录,就几乎不需要编写请求体,完整的查询如下所示:
GET books/_search { "query": { "match_all": { } } }
然而,如果我们出于某种原因需要提升所有结果的分数,那么可以创建match_all查询并声明一个额外的boost参数:
GET books/_search { "query": { "match_all": { "boost":2 ←--- 所有结果的分数都将是2 } } }
结论是,如果调用不带请求体的_search端点,它就等同于一个match_all查询。
到目前为止,我们看到的查询并没有真正展现出Elasticsearch的全部能力,它们只是简单地获取数据。有时,我们可能希望发出带有条件的查询,例如搜索某个作者的第1版且总体评分高于4星的书。Elasticsearch真正的威力隐藏在其搜索功能的灵活性和深度之中。接下来的几节我们将从高层次上探讨这一点。