Elasticsearch官档翻译——1.5 探索数据

探索数据

样本数据

现在我们已经知道一些基础的东西了,接下来我们用更真实的数据来试试。我已经准备了一份虚假银行客户账户的样本数据,每个文档结构如下:

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

译者批注:创建索引时有短暂的状态为yellow或red是正常现象,因为创建索引的时候需要分配分片,如果集群繁忙创建会有一定延迟。

响应内容说明我们成功的将1000条文档索引到了银行索引中(在_doc这个type下)


搜索API

现在我们尝试一些简单的搜索,有两个基础的方法执行搜索:一种是将通过 REST request URI发送搜索参数,另一种是通过REST request body发送搜索参数。请求体这种方法能让你的请求更具有表现力并且也需要你使用更可读的JSON格式。这里我们用一个示例演示URI 的请求方式,但是本教程剩余的部分,我们将只使用请求体这种方式
译者批注:请求体灵活性安全性更强,更适合生产环境;测试数据或简单的排查数据可以使用URI的方式,更快捷

Rest API的搜索方式使用_search结尾的方式访问,下面的例子将返回银行索引下的全部文档:

curl 'localhost:9200/bank/_search?q=*&pretty'

我们来进行第一次分析这个搜索请求。我们搜索(_search结尾)银行索引,并且参数 q=*告诉Elasticsearch匹配全部文档,之前出现过的这个pretty参数,仅仅是告诉Elasticsearch返回格式化过的JSON。
响应如下:

{
  "took" : 63,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "failed" : 0 
},
  "hits" : {
    "total" : 1000,
    "max_score" : 1.0,
    "hits" : [ {
      "_index" : "bank",
      "_type" : "account",
      "_id" : "1",
      "_score" : 1.0, "_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"}
    }, {
      "_index" : "bank",
      "_type" : "account",
      "_id" : "6",
      "_score" : 1.0, "_source" : {"account_number":6,"balance":5686,"firstname":"Hattie","lastname":"Bond","age":36,"gender":"M","address":"671 Bristol Street","employer":"Netagy","email":"hattiebond@netagy.com","city":"Dante","state":"TN"}
    }, {
      "_index" : "bank",
      "_type" : "account",

至于响应,我们看到以下部分:

  • took - Elasticsearch搜索花销的时间,毫秒单位
  • timed_out - 请求是否超时
  • _shards - 告诉我们搜索了多少个分片,以及搜索成功/失败的分片个数
  • hit - 搜索结果(默认返回10条)
  • hits.total - 搜索匹配到的文档总数
  • hit.hits - 搜索结果实际的数组
  • _scoremax _score - 先无视这两个参数

下面使用请求体的方式和上面的搜索一样。

curl -XPOST 'localhost:9200/bank/_search?pretty' -d '
{
"query": { "match_all": {} }
}'

不同的是,我们的_search API使用 POST 请求和 JSON 风格的请求体替代 URI中的q=*
我们将会在下一章节讨论JSON风格的查询。

响应如下:

{
  "took" : 26,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "failed" : 0
  },
  "hits" : {
    "total" : 1000,
    "max_score" : 1.0,
    "hits" : [ {
      "_index" : "bank",
      "_type" : "account",
      "_id" : "1",
      "_score" : 1.0, "_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"}
    }, {
      "_index" : "bank",
      "_type" : "account",
      "_id" : "6",
      "_score" : 1.0, "_source" : {"account_number":6,"balance":5686,"firstname":"Hattie","lastname":"Bond","age":36,"gender":"M","address":"671 Bristol Street","employer":"Netagy","email":"hattiebond@netagy.com","city":"Dante","state":"TN"}
    }, {
      "_index" : "bank",
      "_type" : "account",
      "_id" : "13",

一定要明白,一旦你获得了搜索结果,这个请求在Elasticsearch中将完全执行完毕并且不会保存任何形式的服务端资源,并且你的搜索结果中也不会有游标等。这一点和其他使用SQL的存储引擎形成了鲜明的对比,在这些引擎中,可能你先获取查询结果的子集,然后如果你使用了带状态的服务端游标,需要接着返回服务器获取剩下结果。


查询语句介绍

Elasticsearch提供了基于JSON风格的特定查询语法,供你执行查询操作。在 Query DSL
中有参考。查询语法十分详细可能第一次看会被吓到,不过最好的学习方法是从几个基本的例子开始:

回到上一个例子,我们执行了如下查询:

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

分析上面的语句,query部分告诉我们查询定义了什么,match_all是我们想用的查询类型。match_all就是搜索指定索引下的全部文档。

除了query参数我们还可以传递其他的参数影响搜索结果。例如下面的match_all查询只返回第一条文档:

curl -XPOST 'localhost:9200/bank/_search?pretty' -d '
{
"query": { "match_all": {} },
"size": 1
}'

注意size如果不指定,默认返回10条文档。

下面的例子中,match_all查询返回第11到20条文档:

curl -XPOST 'localhost:9200/bank/_search?pretty' -d '
{
"query": { "match_all": {} },
"from": 10,
"size": 10
}'

from(默认是0)参数从第几个文档开始,size参数用来指定从from参数指定的文档号开始返回几条文档。这个特性在实现分页搜索的时候很有用,注意from不指定的时候默认是0。

下面这个例子执行match_all查询,并且按照账户余额进行降序排序,并返回10(默认是大小)条文档。

curl -XPOST 'localhost:9200/bank/_search?pretty' -d '
{
"query": { "match_all": {} },
"sort": { "balance": { "order": "desc" } }
}'


执行搜索

现在我们已经见过一些基本的搜索了,下面我们来发掘下更多的查询DSL。我们先来看一下搜索结果的字段,默认情况下所有查询都会返回完整的JSON文档,这个(完整的JSON文档)被称作 源(搜索命中结果的_source字段),如果我们不想返回整个文档,我们可以只从其中请求几个字段。

下面的例子告诉我们搜索结果如何只返回account_numberbalance两个字段。

curl -XPOST 'localhost:9200/bank/_search?pretty' -d '
{
"query": { "match_all": {} },
"_source": ["account_number", "balance"]
}'

注意上面的例子我们只是减少了_source字段,返回结果仍然有_source但是里面只包含account_numberbalance两个字段。

如果你有使用SQL语句的背景,上面的例子有点类似SQL中的 SELECT FROM中的字段列表的概念。

下面我们继续查询部分。之前我们已经知道如何使用match_all获取索引全部文档,那么现在来介绍一种新查询,叫match query,可以说是最基本的基于字段的搜索(针对特定字段或字段集的搜索)。

下面的例子返回账户号为20的文档:

curl -XPOST 'localhost:9200/bank/_search?pretty' -d '
{
"query": { "match": { "account_number": 20 } }
}'

下面的例子返回地址中包含mill的全部文档。

curl -XPOST 'localhost:9200/bank/_search?pretty' -d '
{
"query": { "match": { "address": "mill" } }
}'

下面的例子返回地址中包含milllane的全部文档。

curl -XPOST 'localhost:9200/bank/_search?pretty' -d '
{
"query": { "match": { "address": "mill lane" } }
}'

译者批注:后面的查询语句都省略开头的command,大家只需要关注query体即可

下面的例子是match查询的变种(match_phrase),返回地址字段包含词组mill lane的全部文档:

{
  "query": { "match_phrase": { "address": "mill lane" } }
}

下面来介绍bool(ean) query译者批注:布尔查询)。布尔查询允许我们通过布尔逻辑将小的查询组合成大的查询。

下面的例子组合了两个 match 查询,返回地址字段包含milllane的全部文档。

{
  "query": {
    "bool": {
      "must": [
        { "match": { "address": "mill" } },
        { "match": { "address": "lane" } }
      ]
    }
  }
}

上面的例子中,bool must部分指定了所有查询条件都必须返回true,文档才算命中。

相反的,下面的例子组合了两个match 查询,返回地址中包含milllane的全部文档。

{
  "query": {
    "bool": {
      "should": [
        { "match": { "address": "mill" } },
        { "match": { "address": "lane" } }
      ]
    }
  }
}

上面的例子,bool should部分指定了一组查询任意一个条件是true才能命中文档。

下面的例子组合了两个match查询,返回文档的地址字段中既不包括mill 也不包括lane

{
  "query": {
    "bool": {
      "must_not": [
        { "match": { "address": "mill" } },
        { "match": { "address": "lane" } }
      ]
    }
  }
}

上面的例子中,bool must_not部分指定了一组查询任意一个条件都不为true才能命中文档(译者批注:简单说就是全部条件都是false)。

我们还可以同时组合mustshouldmust_not这几个逻辑到一个布尔查询中,进一步说,我们还能组合多个布尔查询到其他任何布尔查询中,从而模拟复杂的多级布尔逻辑

译者批注:类似SQL语句中AND OR 多层嵌套,只不过JSON表达比较麻烦,建议逻辑不要过于复杂,否则当你排查业务逻辑问题时看到N层query dsl会崩溃。。。

下面的例子返回年龄是40岁但是不住在ID州的账户:

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

执行过滤

之前的章节我们跳过了一个小细节,叫文档得分(搜索结果中的_score字段)。得分是一个数值类型的值,他能表示文档同我们指定的搜索条件的匹配度。得分越高,文档相关度越高,繁殖文档相关度越低。

但是查询也并不总是需要产出得分,特别是当他们只用于"过滤"文档集合的时候,Elasticsearch检测到这些情况并自动优化了查询执行过程避免了无用的评分计算。

前一章我们介绍的布尔查询也支持filter短语,可以用来限制被其他短语匹配到,并且不改变原有的得分计算(译者批注:说白了就是不影响评分)。让我们用一个例子介绍下 range query,可以让我们按照范围过滤。这个查询通常用在数值或日期类型的过滤。

下面的例子使用布尔查询,过滤 余额 在2000-3000 闭区间内的所有文档,换句话说,我们要找到余额大于等于2000且小于等于3000的账户。

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

分析上述例子,布尔查询包括一个match_all查询(查询部分)还有一个范围查询(过滤部分)。我们可以用其他任意查询替换查询和过滤部分。例子中范围查询命中的文档最有意义,没有其他方式命中的文档比它更相关
译者批注:意思就是过滤命中的相关度是最高的,毕竟属于精准匹配

除了match_allmatchboolrange几种查询方式,还有许多查询类型可用,在这里不一一深究,鉴于我们已经对查询有了基本的理解,所以将这些知识点运用到学习和实验其他类型的查询中应该不会太难。


执行聚合

聚合提供能从数据中分组和提取统计的功能。最简单的理解方法就是认为他们和SQL中的 GROUP BY和SQL中的聚合方法大致相同。在Elasticsearch中,你可以在执行一次搜索的结果中返回命中文档并同时在命中结果集外返回聚合结果。这个功能从某种意义上说十分有效,你可以通过简洁的API一次性返回查询结果和聚合结果,从而避免了多次网络IO。

我们以统计账户信息中各个州有多少账户开始,并按照州的字母顺序倒序排序(默认就是这样),返回10条聚合结果。

{
  "size": 0,
  "aggs": {
    "group_by_state": {
      "terms": {
        "field": "state"
      }
    }
  }
}

SQL语句中和下面类似:

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

响应如下:

{
  "took" : 26,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "failed" : 0
  },
"hits" : {
    "total" : 1000,
    "max_score" : 0.0,
    "hits" : [ ]
  },
  "aggregations" : {
    "group_by_state" : {
      "buckets" : [ {
        "key" : "al",
        "doc_count" : 21
      }, {
        "key" : "tx",
        "doc_count" : 17
      }, {
        "key" : "id",
        "doc_count" : 15
      }, {
        "key" : "ma",
        "doc_count" : 15
      }, {
        "key" : "md",
        "doc_count" : 15
      }, {
        "key" : "pa",
        "doc_count" : 15
      }, {
        "key" : "dc",
        "doc_count" : 14
      }, {
        "key" : "me",
        "doc_count" : 14
      }, {
        "key" : "mo",
        "doc_count" : 14
      }, {
        "key" : "nd",
        "doc_count" : 14
      } ]
    }
  }
}

我们能看到,al州有21个账户,其次是tx有17个,其次是id有15个,等等。

注意我们设置size为0,因为我们只想关注响应中的聚合结果。

基于之前的聚合例子,下面的例子还计算了每个州账户的平均余额。

{
  "size": 0,
  "aggs": {
    "group_by_state": {
      "terms": {
        "field": "state"
      },
      "aggs": {
        "average_balance": {
          "avg": {
            "field": "balance"
          }
        }
      }
    }
  }
}

注意我们是如何在group_by_state聚合体中嵌套average_balance聚合体的,这是一个公共的聚合模式,你可以嵌套任意聚合体改变聚合结果,从而实现你的需求。

基于上面的聚合例子,我们再根据平均余额进行倒序排序:

{
  "size": 0,
  "aggs": {
    "group_by_state": {
      "terms": {
        "field": "state",
        "order": {
          "average_balance": "desc"
        }
      },
      "aggs": {
        "average_balance": {
          "avg": {
            "field": "balance"
          }
        }
      }
    }
  }
}

下面的例子展示了如何根据年龄段(20-29, 30-39, 和 40-49)聚合,其次是性别,最后计算它们的平均余额,分别每个年龄段每个性别一组结果。

{
  "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"
          },
          "aggs": {
            "average_balance": {
              "avg": {
                "field": "balance"
              }
            }
          }
        }
      }
    }
  }
}

还有很多聚合方法在这里不做深究,如果你想进一步研究,聚合参考指导是个不错的开始。


总结

Elasticsearch既简单又复杂,到目前为止我们已经基本了解了它是什么,他的一些内部机制,以及如何使用REST API来操作它。希望这个教程能够让你更好地理解Elasticsearch,更重要的是,能够启发你去实验ES中更多地特性。

上一节:Elasticsearch官档翻译——1.4 修改数据

下一节:Elasticsearch官档翻译——2 设置

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343

推荐阅读更多精彩内容

  • 介绍查询语言 Elasticsearch提供一种JSON风格的特定领域语言,利用它你可以执行查询。这杯称为查询DS...
    山天大畜阅读 3,715评论 0 1
  • 博客原文一博客原文二 翻译作品,水平有限,如有错误,烦请留言指正。原文请见 官网英文文档 起步 Elasticse...
    rabbitGYK阅读 3,228评论 0 68
  • 基础概念 Elasticsearch有几个核心概念,从一开始理解这些概念会对整个学习过程有莫大的帮助。 接近实时(...
    山天大畜阅读 2,103评论 0 4
  • 探索你的数据 样本数据集 现在我们对于基本的东西已经有了一些感觉,现在让我们尝试使用一些更加贴近现实的数据集。我已...
    Jason__Ding阅读 404评论 0 0
  • 我想爱情的模样应该是甜蜜、幸福的。 我知道相处中难免会触碰到对方的雷区或不小心扎到彼此;但我想要的爱情是争吵和矛盾...
    红历历阅读 144评论 0 0