专业的编程技术博客社区

网站首页 > 博客文章 正文

ES6.0.0官方参考指南翻译~入门指南~探索数据

baijin 2024-10-24 08:47:15 博客文章 24 ℃ 0 评论

样本数据集

现在我们已经学习了一些基本知识,让我们尝试一下更加真实的数据集。 我准备了一个关于银行账户信息的虚构JSON文档样本。 每个文档都有以下模式:

{
 "account_number": 0,
 "balance": 16623,
 "firstname": "Bradshaw",
 "lastname": "Mckenzie",
 "age": 29,
 "gender": "F",
 "address": "244 Columbus Place",
 "employer": "Euron",
 "email": "bradshawmckenzie@euron.com",
 "city": "Hobucken",
 "state": "CO"
}

这些数据是使用www.json-generator.com/生成的,所以请忽略数据的实际值和语义,因为这些数据都是随机生成的。

加载样本数据集

你可以从这里下载样本数据集(accounts.json).将其解压到我们的当前目录,并将其加载到我们的集群中,如下所示:

curl -H "Content-Type: application/json" -XPOST 'localhost:9200/bank/account/_bulk?pretty&refresh' --data-binary "@accounts.json"
curl 'localhost:9200/_cat/indices?v'

其响应为:

health status index uuid pri rep docs.count docs.deleted store.size pri.store.size
yellow open bank l7sSYV2cQXmu6_4rJWVIww 5 1 1000 0 128.6kb 128.6kb

上述响应表明:我们刚刚成功地将1000个文档批量索引到了bank索引的account类型中.

Search API

现在我们来做一些简单搜索.有两种基本方式可执行搜索:一种是通过REST请求URI发送搜索参数,另一种是通过REST请求主体发送搜索参数。请求主体方法允许你使用更具表现力,更具可读性的JSON格式来定义搜索. 我们先尝试一个使用请求URI方法的示例,但在本教程的剩余部分中,我们将专注使用请求主体方法。

用于搜索的REST API可从_search端点访问。 下面的示例展示了如何返回bank索引中的所有文档:

GET /bank/_search?q=*&sort=account_number:asc&pretty

我们首先解析搜索调用。 我们在bank索引中搜索(_search端点),并且q = *参数指示Elasticsearch需匹配索引中的所有文档。

sort=account_number:asc 参数指示使用每个文档的account_number字段按升序对结果进行排序. pretty参数再次告诉Elasticsearch返回漂亮的JSON结果.

其响应为(部分展示):

{
 "took": 63,
 "timed_out": false,
 "_shards": {
 "total": 5,
 "successful": 5,
 "skipped": 0,
 "failed": 0
 },
 "hits": {
 "total": 1000,
 "max_score": null,
 "hits": [
 {
 "_index": "bank",
 "_type": "account",
 "_id": "0",
 "sort": [
 0
 ],
 "_score": null,
 "_source": {
 "account_number": 0,
 "balance": 16623,
 "firstname": "Bradshaw",
 "lastname": "Mckenzie",
 "age": 29,
 "gender": "F",
 "address": "244 Columbus Place",
 "employer": "Euron",
 "email": "bradshawmckenzie@euron.com",
 "city": "Hobucken",
 "state": "CO"
 }
 },
 {
 "_index": "bank",
 "_type": "account",
 "_id": "1",
 "sort": [
 1
 ],
 "_score": null,
 "_source": {
 "account_number": 1,
 "balance": 39225,
 "firstname": "Amber",
 "lastname": "Duke",
 "age": 32,
 "gender": "M",
 "address": "880 Holmes Lane",
 "employer": "Pyrami",
 "email": "amberduke@pyrami.com",
 "city": "Brogan",
 "state": "IL"
 }
 },...
 ]
 }
}

从响应中,我们可以看到如下部分:

  • took – Elasticsearch执行搜索的时间(以毫秒为单位)
  • timed_out – 搜索是否超时
  • _shards –搜索了多少分片,以及搜索分片成功/失败的次数
  • hits – 搜索结果
  • hits.total – 符合我们搜索条件的文档总数
  • hits.hits – 实际的搜索结果数组(默认为前10个文档)
  • hits.sort - 结果排序键(若按分数(score)进行排序则会丢失)
  • hits._score和max_score - 现在可忽略这些字段

下面是使用请求主体方法进行相同搜索的示例:

GET /bank/_search
{
 "query": { "match_all": {} },
 "sort": [
 { "account_number": "asc" }
 ]
}

这里的区别是,我们在_search API中POST了一个JSON风格的请求主体来代替URL中的q=*参数。

在下一节中,我们将讨论这种JSON查询.

需要重点理解的是,一旦你得到了搜索结果,Elasticsearch就完成了请求,它不会维护维护任何形式的服务端资源或针对结果的打开游标.这与许多其他平台(如SQL)形成了鲜明对比,在其它平台中,您最初可能会先获取查询结果的部分子集,然后再通过某种有状态的服务端游标来获取结果集的剩余部分。

Query语言介绍

Elasticsearch提供了一种可用来执行查询的JSON风格语言(称为Query DSL).

该查询语言比较全面而且复杂,因此学习它的最佳方式是先从几个基本示例开始.

回到我们的最后一个例子,我们执行了这个查询:

GET /bank/_search
{
 "query": { "match_all": {} }
}

解析上述部分,query部分用于告知查询定义是什么,而match_all部分则是我们想运行的查询类型. match_all会搜索指定索引中的所有文档。除了query参数,我们还可以传递其它参数来影响查询结果.

在上面章节的例子中,我们传递了一个sort,这里,我们将传递一个size:

GET /bank/_search
{
 "query": { "match_all": {} },
 "size": 1
}

注意,如果未明确指定size值的话,那么其默认值将为10.

下面这个例子执行match_all查询,并返回文档11到20之间的文档:

GET /bank/_search
{
 "query": { "match_all": {} },
 "from": 10,
 "size": 10
}

from参数(从0开始)用于指定从哪个文档开始检索,size参数指定从from参数开始返回多少个文档。

在实现分页搜索结果时,此功能非常有用。 请注意,如果from不指定,则默认为0。

下面这个示例会执行match_all查询,并按帐户余额进行降序排序,并返回前10个(默认大小)文档。

GET /bank/_search
{
 "query": { "match_all": {} },
 "sort": { "balance": { "order": "desc" } }
}

执行搜索

现在我们已经看到了一些基本的搜索参数,让我们再深入查询DSL。我们先来看看返回的文档字段。 默认情况下,完整的JSON文档作为所有搜索的一部分返回。 这被称为源(搜索匹配中的_source字段)。 如果我们不希望返回整个源文档,我们可以只返回源内的几个字段。

下面这个示例显示如何从搜索中返回两个字段account_number和balance(在_source之内):

GET /bank/_search
{
 "query": { "match_all": {} },
 "_source": ["account_number", "balance"]
}

注意,上面的例子只是简单地减少了_source字段. 它仍然只会返回一个名为_source的字段,但在其中只包含字段account_number和balance。

如果您有SQL背景,则上述内容在概念上与SQL SELECT FROM字段列表有些相似。

现在我们来看看query部分.在这之前,我们已经看到了如何使用match_all来查询所有文档.

现在我们将介绍一种新的查询-match query, 可将其视为基本的字段搜索查询(即针对特定字段或字段集合进行的搜索).

下面这个例子会返回帐号为20的文档:

GET /bank/_search
{
 "query": { "match": { "account_number": 20 } }
}

下面这个示例会返回address中包含term为“mill”的所有帐户:

GET /bank/_search
{
 "query": { "match": { "address": "mill" } }
}

下面这个示例会返回address中包含术语"mill" 或"lane"的所有帐户:

GET /bank/_search
{
 "query": { "match": { "address": "mill lane" } }
}

这个例子是match(match_phrase)的一个变体,它会返回address中包含短语“mill lane”的所有账号:

GET /bank/_search
{
 "query": { "match_phrase": { "address": "mill lane" } }
}

接下来我们介绍bool query.bool query允许我们使用布尔逻辑将较小的查询组合成更大的查询。

下面这个示例由两个match查询所组成,它会返回address中包含“mill”和“lane”的所有帐户:

GET /bank/_search
{
 "query": {
 "bool": {
 "must": [
 { "match": { "address": "mill" } },
 { "match": { "address": "lane" } }
 ]
 }
 }
}

在上面的示例中, bool must 条件指明了所有查询条件必须为true时才算文档匹配.

相反,下面的示例也由两个match查询所组成,但只要adress中任意包一个 "mill"或"lane",就可视文档匹配:

GET /bank/_search
{
 "query": {
 "bool": {
 "should": [
 { "match": { "address": "mill" } },
 { "match": { "address": "lane" } }
 ]
 }
 }
}

在上面的示例中,bool should 条件指明了只要文档满足任意一个条件,就可以视其为匹配文档.

下面的示例也由两个match查询所组成,但它会返回address中既不包含"mill"也不包含"lane"的所有文档:

GET /bank/_search
{
 "query": {
 "bool": {
 "must_not": [
 { "match": { "address": "mill" } },
 { "match": { "address": "lane" } }
 ]
 }
 }
}

在上面的例子中,must_not 条件指明了任意一个查询条件均不为true时,就视文档为匹配.

我们可以在一个bool查询中同时结合must,should和must_not子句。此外,我们可以在这些bool子句中编写的布尔查询来模拟任何复杂的多级布尔逻辑。

下面这个例子会返回岁数为40,但不住在爱达荷州的所有帐户:

GET /bank/_search
{
 "query": {
 "bool": {
 "must": [
 { "match": { "age": "40" } }
 ],
 "must_not": [
 { "match": { "state": "ID" } }
 ]
 }
 }
}

执行过滤器

在上一节中,我们跳过了一个称为文档分数(document score,搜索结果中的_score字段)的相关细节.

得分是一个数字值,它代表的是文档与搜索词的匹配度。 分数越高,文档越相关,分数越低,文档就越不相关.但查询并非总是需要分数,尤其是用于过滤文档集时. Elasticsearch检测到这种情况后会自动对查询进行优化,以便不计算这些无用分数.

在上一节中我们介绍bool query时,还提到它可支持过滤条件,它允许使用查询来限制将被其他子句匹配的文档,而不改变计算得分的方式. 作为示例,我们将介绍一下range query,该查询允许我们通过范围值来过滤文档. 通常我们会使用数字或日期来过滤文档.

下面的示例会使用bool查询来返回余额介于20000到30000的帐户.

换句话说,我们想查询余额大于等于20000且小于等于30000的所有帐户.

GET /bank/_search
{
 "query": {
 "bool": {
 "must": { "match_all": {} },
 "filter": {
 "range": {
 "balance": {
 "gte": 20000,
 "lte": 30000
 }
 }
 }
 }
 }
}

解析一下上述查询, bool 查询包含了一个match_all查询(query部分) 和一个range查询(filter部分).

虽然我们也可以使用其它查询来替换query和filter部分,但在上面的示例中,range查询是非常有意义的,因为可通过范围来缩小文档匹配范围。

除了match_all, match, bool, 和range查询外,还有其它类型查询。 由于我们已经对其工作原理有了基本的了解,所以将这些知识应用于其他查询类型的学习和实验并不难。

执行聚合

聚合提供了对数据进行分组和统计的能力。 理解聚合的最简单方法是将其视为SQL中的GROUP BY和聚合函数。 在Elasticsearch中,您可以执行搜索来返回命中文档,还可以在一个响应中同时返回命中文档的聚合结果。 这是非常强大和高效的,因为您可以运行多个查询和多个聚合,并使用一个简洁的API来避免网络往返,从而一次性获得两个(或二者之一)操作的结果。

下面的例子首先会按state对帐户进行分组, 然后再按计数大小降序返回前10个洲(默认按降序排列):

GET /bank/_search
{
 "size": 0,
 "aggs": {
 "group_by_state": {
 "terms": {
 "field": "state.keyword"
 }
 }
 }
}

在SQL中,上面的聚合在概念上类似于:

SELECT state, COUNT(*) FROM bank GROUP BY state ORDER BY COUNT(*) DESC

其响应如下(部分显示):

{
 "took": 29,
 "timed_out": false,
 "_shards": {
 "total": 5,
 "successful": 5,
 "skipped": 0,
 "failed": 0
 },
 "hits": {
 "total": 1000,
 "max_score": 0,
 "hits": []
 },
 "aggregations": {
 "group_by_state": {
 "doc_count_error_upper_bound": 20,
 "sum_other_doc_count": 770,
 "buckets": [
 {
 "key": "ID",
 "doc_count": 27
 },
 {
 "key": "TX",
 "doc_count": 27
 },
 {
 "key": "AL",
 "doc_count": 25
 },
 {
 "key": "MD",
 "doc_count": 25
 },
 {
 "key": "TN",
 "doc_count": 23
 },
 {
 "key": "MA",
 "doc_count": 21
 },
 {
 "key": "NC",
 "doc_count": 21
 },
 {
 "key": "ND",
 "doc_count": 21
 },
 {
 "key": "ME",
 "doc_count": 20
 },
 {
 "key": "MO",
 "doc_count": 20
 }
 ]
 }
 }
}

我们可以看到ID(爱达荷州)有27个账户,其次是德克萨斯州的27个账户,其次是阿拉巴马州的25个账户,等等。请注意,我们将size设置为0来不显示命中文档,因为我们只想看到响应中的聚合结果。

在前面的聚合基础上,下面的示例会按state计算平均账户余额:

GET /bank/_search
{
 "size": 0,
 "aggs": {
 "group_by_state": {
 "terms": {
 "field": "state.keyword"
 },
 "aggs": {
 "average_balance": {
 "avg": {
 "field": "balance"
 }
 }
 }
 }
 }
}

注意我们是如何在group_by_state 聚合中嵌入average_balance聚合的.这是所有聚合的通用模式。 你可以在聚合中任意地嵌套聚合, 以便从数据中提取所要的聚合结果.

基于前面的聚合,下面我们将按平均余额降序来排序结果:

GET /bank/_search
{
 "size": 0,
 "aggs": {
 "group_by_state": {
 "terms": {
 "field": "state.keyword",
 "order": {
 "average_balance": "desc"
 }
 },
 "aggs": {
 "average_balance": {
 "avg": {
 "field": "balance"
 }
 }
 }
 }
 }
}


下面的例子演示了如何先按年龄段分组(20-29, 30-39, 40-49),再按性别进行分组,最终得到每个性别的年龄段平均账户余额:

GET /bank/_search
{
 "size": 0,
 "aggs": {
 "group_by_age": {
 "range": {
 "field": "age",
 "ranges": [
 {
 "from": 20,
 "to": 30
 },
 {
 "from": 30,
 "to": 40
 },
 {
 "from": 40,
 "to": 50
 }
 ]
 },
 "aggs": {
 "group_by_gender": {
 "terms": {
 "field": "gender.keyword"
 },
 "aggs": {
 "average_balance": {
 "avg": {
 "field": "balance"
 }
 }
 }
 }
 }
 }
 }
}

还有很多其它的聚合功能,在这里就不详细介绍了。

如果你想进一步地实验, 聚合参考手册则是很好的起点。

总结

Elasticsearch是一个既简单又复杂的产品。现在我们学习了其基础知识,并学会了如何使用REST APIs来进行相关操作.

希望本教程能够让您更好地理解Elasticsearch是什么,更重要的是:启发您进一步尝试其它的伟大功能!

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表