Aggregation 在中文中也被称作聚合。简单地说,Elasticsearch 中的 aggregation 聚合将你的数据汇总为指标、统计数据或其他分析。聚合可帮助你回答以下问题:

  • 我的网站的平均加载时间是多少?
  • 根据交易量,谁是我最有价值的客户?
  • 什么会被认为是我网络上的大文件?
  • 每个产品类别有多少产品?

Elasticsearch 将聚合分为三类:

  • Metric 聚合:根据字段值计算指标,例如总和或平均值。
  • Bucket 聚合:根据字段值、范围或其他标准将文档分组到存储桶(也称为箱)中。
  • Pipeline 聚合:从其他聚合而不是文档或字段中获取输入,并计算新的聚合

在之前的文章 “Elasticsearch:aggregation 介绍” 我对它有一些介绍。在今天的文章中,我来对它做一个非常简单的介绍,以便大家对它有一个更加清楚明了的认识。

准备工作

我们首先需要参阅文章 “Elastic:开发者上手指南” 来安装自己的 Elasticsearch 及 Kibana:

  • 如何在 Linux,MacOS 及 Windows 上进行安装 Elasticsearch
  • Kibana:如何在 Linux,MacOS 及 Windows上安装 Elastic 栈中的 Kibana

你需要根据自己的平台选择合适的方式来安装。你可以选择最新的 Elastic Stack 8.2 来进行安装。

准备数据

接下来,我们先创建一个练习的数据。我们打开 Kibana,并导航至 Dev Tools:

我们使用 _bulk API 来导入数据:

在 Console 中,我们输入如下的数据并执行:

PUT bank/_bulk
{ "create":{ } }
{ "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" }
{ "create":{ } }
{ "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" }
{ "create":{ } }
{ "account_number":13,"balance":32838,"firstname":"Nanette","lastname":"Bates","age":28,"gender":"F","address":"789 Madison Street","employer":"Quility","email":"nanettebates@quility.com","city":"Nogal","state":"VA" }
{ "create":{ } }
{ "account_number":18,"balance":4180,"firstname":"Dale","lastname":"Adams","age":33,"gender":"M","address":"467 Hutchinson Court","employer":"Boink","email":"daleadams@boink.com","city":"Orick","state":"MD" }
{ "create":{ } }
{ "account_number":20,"balance":16418,"firstname":"Elinor","lastname":"Ratliff","age":36,"gender":"M","address":"282 Kings Place","employer":"Scentric","email":"elinorratliff@scentric.com","city":"Ribera","state":"WA" }
{ "create":{ } }
{ "account_number":25,"balance":40540,"firstname":"Virginia","lastname":"Ayala","age":39,"gender":"F","address":"171 Putnam Avenue","employer":"Filodyne","email":"virginiaayala@filodyne.com","city":"Nicholson","state":"PA" }
{ "create":{ } }
{ "account_number":32,"balance":48086,"firstname":"Dillard","lastname":"Mcpherson","age":34,"gender":"F","address":"702 Quentin Street","employer":"Quailcom","email":"dillardmcpherson@quailcom.com","city":"Veguita","state":"IN" }
{ "create":{ } }
{ "account_number":37,"balance":18612,"firstname":"Mcgee","lastname":"Mooney","age":39,"gender":"M","address":"826 Fillmore Place","employer":"Reversus","email":"mcgeemooney@reversus.com","city":"Tooleville","state":"OK" }
{ "create":{ } }
{ "account_number":44,"balance":34487,"firstname":"Aurelia","lastname":"Harding","age":37,"gender":"M","address":"502 Baycliff Terrace","employer":"Orbalix","email":"aureliaharding@orbalix.com","city":"Yardville","state":"DE" }
{ "create":{ } }
{ "account_number":49,"balance":29104,"firstname":"Fulton","lastname":"Holt","age":23,"gender":"F","address":"451 Humboldt Street","employer":"Anocha","email":"fultonholt@anocha.com","city":"Sunriver","state":"RI" }

当我们执行完上面的指令后,我们就可以看到一个被创建的索引 bank,并且它里面含有 10 个文档:

GET bank/_count
{"count" : 10,"_shards" : {"total" : 1,"successful" : 1,"skipped" : 0,"failed" : 0}
}

接下来,为了让大家对 aggregation 有一个认识,我在下面来展示如何实现 aggregation 操作的。

在做聚合之前,我们先来了解我们的数据:

GET bank/_mapping
{"bank" : {"mappings" : {"properties" : {"account_number" : {"type" : "long"},"address" : {"type" : "text","fields" : {"keyword" : {"type" : "keyword","ignore_above" : 256}}},"age" : {"type" : "long"},"balance" : {"type" : "long"},"city" : {"type" : "text","fields" : {"keyword" : {"type" : "keyword","ignore_above" : 256}}},"email" : {"type" : "text","fields" : {"keyword" : {"type" : "keyword","ignore_above" : 256}}},"employer" : {"type" : "text","fields" : {"keyword" : {"type" : "keyword","ignore_above" : 256}}},"firstname" : {"type" : "text","fields" : {"keyword" : {"type" : "keyword","ignore_above" : 256}}},"gender" : {"type" : "text","fields" : {"keyword" : {"type" : "keyword","ignore_above" : 256}}},"lastname" : {"type" : "text","fields" : {"keyword" : {"type" : "keyword","ignore_above" : 256}}},"state" : {"type" : "text","fields" : {"keyword" : {"type" : "keyword","ignore_above" : 256}}}}}}
}

从上面的展示中,我们可以看出来有的字段是 text 类型的,那么它不可以进行聚合,但是如果这个字段是 multi-field 的,并且它的一个字段是 keyword 的,那么它的 keyword 字段是可以进行聚合的。截止目前为止,除 text 和 annotated_text 类型的字段外,所有的其它类型的字段都支持聚合。

运行一个聚合

你可以通过指定搜索 API 的 aggs 参数将聚合作为搜索的一部分运行。 以下搜索在 city.keyword 上运行术语聚合:

GET bank/_search?filter_path=aggregations
{"size": 0,"aggs": {"cities_distribution": {"terms": {"field": "city.keyword","size": 10}}}
}

在通常的情况下,我们设置 size 为 0,这样我们不需要返回任何的文档。它仅返回聚合的结果。这样做的好处是节省相应的负载,同时也可以缓存我们的响应,以便下次执行同样的聚合时,返回速度更快。上面命令返回的结果为:

{"aggregations" : {"cities_distribution" : {"doc_count_error_upper_bound" : 0,"sum_other_doc_count" : 0,"buckets" : [{"key" : "Brogan","doc_count" : 1},{"key" : "Dante","doc_count" : 1},{"key" : "Nicholson","doc_count" : 1},{"key" : "Nogal","doc_count" : 1},{"key" : "Orick","doc_count" : 1},{"key" : "Ribera","doc_count" : 1},{"key" : "Sunriver","doc_count" : 1},{"key" : "Tooleville","doc_count" : 1},{"key" : "Veguita","doc_count" : 1},{"key" : "Yardville","doc_count" : 1}]}}
}

我们可以看到在各个城市的文档数。上面显示每个城市的文档数为 1。

改变聚合的范围

针对特别大的数据集来说,聚合是非常耗时的。我们可以通过查询来限定它的聚合文档的范围。我们可以这么来操作:

GET bank/_search?filter_path=aggregations
{"size": 0,"query": {"range": {"balance": {"gte": 4000,"lte": 10000}}}, "aggs": {"cities_distribution": {"terms": {"field": "city.keyword","size": 10}}}
}

在上面,我们通过设置一个查询的范围,限定 balance 在 4000 到 10000 之间的文档来进行聚合。这样聚合作用在一个小一点的文档集合中,在很多的情况下会运行更快。上面的命令返回的结果为:

{"aggregations" : {"cities_distribution" : {"doc_count_error_upper_bound" : 0,"sum_other_doc_count" : 0,"buckets" : [{"key" : "Dante","doc_count" : 1},{"key" : "Orick","doc_count" : 1}]}}
}

运行多个集合

我们有时甚至可以在同一个请求中执行多个聚合:

GET bank/_search?filter_path=aggregations
{"size": 0,"aggs": {"cities_distribution": {"terms": {"field": "city.keyword","size": 5}},"average_age": {"avg": {"field": "age"}}}
}

在上面,我们使用了两个聚合:一个求城市的文档分别及平均年龄。上面命令的结果为:

{"aggregations" : {"cities_distribution" : {"doc_count_error_upper_bound" : 0,"sum_other_doc_count" : 5,"buckets" : [{"key" : "Brogan","doc_count" : 1},{"key" : "Dante","doc_count" : 1},{"key" : "Nicholson","doc_count" : 1},{"key" : "Nogal","doc_count" : 1},{"key" : "Orick","doc_count" : 1}]},"average_age" : {"value" : 33.7}}
}

运行 sub-aggregations

桶聚合支持桶或指标子聚合。 例如,具有 avg 子聚合的 histogram 聚合计算每个文档桶的平均值。 嵌套子聚合没有级别或深度限制。

GET bank/_search?filter_path=aggregations
{"size": 0,"aggs": {"balance_distribution": {"histogram": {"field": "balance","interval": 5000},"aggs": {"average_age": {"avg": {"field": "age"}}}}}
}

上面的命令返回的结果为:

{"aggregations" : {"balance_distribution" : {"buckets" : [{"key" : 0.0,"doc_count" : 1,"average_age" : {"value" : 33.0}},{"key" : 5000.0,"doc_count" : 1,"average_age" : {"value" : 36.0}},{"key" : 10000.0,"doc_count" : 0,"average_age" : {"value" : null}},{"key" : 15000.0,"doc_count" : 2,"average_age" : {"value" : 37.5}},{"key" : 20000.0,"doc_count" : 0,"average_age" : {"value" : null}},{"key" : 25000.0,"doc_count" : 1,"average_age" : {"value" : 23.0}},{"key" : 30000.0,"doc_count" : 2,"average_age" : {"value" : 32.5}},{"key" : 35000.0,"doc_count" : 1,"average_age" : {"value" : 32.0}},{"key" : 40000.0,"doc_count" : 1,"average_age" : {"value" : 39.0}},{"key" : 45000.0,"doc_count" : 1,"average_age" : {"value" : 34.0}}]}}
}

添加定制 metadata

使用 meta 元对象将自定义元数据与聚合相关联:

GET bank/_search?filter_path=aggregations
{"size": 0,"aggs": {"state_dist": {"terms": {"field": "state.keyword","size": 5},"meta": {"my-metadata-field": "foo"}}}
}

在返回的结果中,我们可以看到 meta 对象:

{"aggregations" : {"state_dist" : {"meta" : {"my-metadata-field" : "foo"},"doc_count_error_upper_bound" : 0,"sum_other_doc_count" : 5,"buckets" : [{"key" : "DE","doc_count" : 1},{"key" : "IL","doc_count" : 1},{"key" : "IN","doc_count" : 1},{"key" : "MD","doc_count" : 1},{"key" : "OK","doc_count" : 1}]}}
}

返回聚合的类型

默认情况下,聚合结果包括聚合的名称,但不包括其类型。 要返回聚合类型,请使用 typed_keys 查询参数。

GET bank/_search?typed_keys&filter_path=aggregations
{"size": 0,"aggs": {"agg_dist": {"histogram": {"field": "age","interval": 10}}}
}

上面的命令返回的结果为:

{"aggregations" : {"histogram#agg_dist" : {"buckets" : [{"key" : 20.0,"doc_count" : 2},{"key" : 30.0,"doc_count" : 8}]}}
}

上面的结果显示是一个 histogram 的聚合。

使用 scripts 来进行聚合

当一个字段与你需要的聚合不完全匹配时,你应该在运行时字段(runtime field)上进行聚合:

GET bank/_search?filter_path=aggregations
{"size": 0,"runtime_mappings": {"name": {"type": "keyword","script": {"source": "emit(doc['firstname.keyword'].value + ' ' + doc['lastname.keyword'].value)"}}}, "aggs": {"name": {"terms": {"field": "name","size": 5}}}
}

上面的命令返回的结果为:

{"aggregations" : {"name" : {"doc_count_error_upper_bound" : 0,"sum_other_doc_count" : 5,"buckets" : [{"key" : "Amber Duke","doc_count" : 1},{"key" : "Aurelia Harding","doc_count" : 1},{"key" : "Dale Adams","doc_count" : 1},{"key" : "Dillard Mcpherson","doc_count" : 1},{"key" : "Elinor Ratliff","doc_count" : 1}]}}
}

在上面,我们把 firstname 和 lastname 一起加在一起,并对它进行统计。脚本动态计算字段值,这给聚合增加了一点开销。 除了计算所花费的时间之外,某些聚合(如 terms 和 filters)不能将它们的某些优化与运行时字段一起使用。 总的来说,使用运行时字段的性能成本因聚合而异。

上面的聚合,我们也可以直接使用 scripts 来实现。这个针对早期的 Elasticsearch 版本:

GET bank/_search?filter_path=aggregations
{"size": 0,"aggs": {"name": {"terms": {"script": {"source": "doc['firstname.keyword'].value + ' ' + doc['lastname.keyword'].value"},"size": 5}}}
}

上面的搜索的结果为:

{"aggregations": {"name": {"doc_count_error_upper_bound": 0,"sum_other_doc_count": 5,"buckets": [{"key": "Amber Duke","doc_count": 1},{"key": "Aurelia Harding","doc_count": 1},{"key": "Dale Adams","doc_count": 1},{"key": "Dillard Mcpherson","doc_count": 1},{"key": "Elinor Ratliff","doc_count": 1}]}}
}

从结果上看,它和我们之前的 runtime field 的结果是一样的。

我们甚至可以使用如下的简略形式。比如我们想统计每个姓氏的情况。我们可以使用如下的格式:

GET bank/_search?filter_path=aggregations
{"size": 0,"aggs": {"name": {"terms": {"field": "lastname.keyword","script": "'lastname ' + _value", "size": 5}}}
}

在这里,我们使用 _value 来访问 lastname.keyword 里的值。我们使用 lastname 和这个值进行相加,从而得出新的字段:

{"aggregations": {"name": {"doc_count_error_upper_bound": 0,"sum_other_doc_count": 5,"buckets": [{"key": "lastname Adams","doc_count": 1},{"key": "lastname Ayala","doc_count": 1},{"key": "lastname Bates","doc_count": 1},{"key": "lastname Bond","doc_count": 1},{"key": "lastname Duke","doc_count": 1}]}}
}

我们甚至可以对数值字段使用 _value 来对其进行统计,比如:

POST bank/_search?filter_path=aggregations
{"size": 0,"aggs": {"NAME": {"histogram": {"field": "age","script": "_value*2","interval": 5}}}
}

在上面,我们把 age 这个字段的值乘以2,并进行 histogram 统计:

{"aggregations": {"NAME": {"buckets": [{"key": 45,"doc_count": 1},{"key": 50,"doc_count": 0},{"key": 55,"doc_count": 1},{"key": 60,"doc_count": 1},{"key": 65,"doc_count": 2},{"key": 70,"doc_count": 3},{"key": 75,"doc_count": 2}]}}
}

聚合缓存

为了更快的响应,Elasticsearch 将频繁运行的聚合结果缓存在分片请求缓存中。 要获得缓存的结果,请为每次搜索使用相同的首选项字符串。 如果你不需要搜索命中,请将 size 设置为 0 以避免填充缓存。

Elasticsearch 将具有相同首选项字符串的搜索路由到相同的分片。 如果分片的数据在搜索之间没有变化,则分片返回缓存的聚合结果。

Long 类型值的限制

运行聚合时,Elasticsearch 使用双精度值来保存和表示数字数据。 因此,大于 2^23 的长数字的聚合是近似的。

更多阅读,请参阅 “Elastic:开发者上手指南” 中的 “Aggregations” 章节。

Elasticsearch:Aggregation 简介相关推荐

  1. Elasticsearch Aggregation 多个字段分组统计 Java API实现

    2019独角兽企业重金招聘Python工程师标准>>> 现有索引数据: index:school type:student ----------------------------- ...

  2. 适用于Java开发人员的Elasticsearch:简介

    本文是我们学院课程的一部分,该课程的标题为Java开发人员的Elasticsearch教程 . 在本课程中,我们提供了一系列教程,以便您可以开发自己的基于Elasticsearch的应用程序. 我们涵 ...

  3. Elasticsearch(ES)简介及基本使用

    ES 简介 ES是一个使用java语言编写的并且基于Lucene编写的搜索引擎, 他提供了分布式的全文搜索服务, 还提供了一个RESTful风格的web接口, 官方还对多种语言提供了相应的API Lu ...

  4. Elasticsearch API简介

    Elasticsearch提供了丰富的API. API主要有: Indices APIs:负责索引Index的创建(create).删除(delete).获取(get).索引存在(exist)等操作. ...

  5. Elasticsearch入门简介

    最近因为一个项目涉及到了搜索相关的需求,elasticsearch可以说是目前最流行,最受欢迎的企业搜索引擎,所以在技术选型上自然的选择ES作为搜索引擎的实现,由于自己也是第一次接触elasticse ...

  6. ElasticSearch入门简介、安装ES(安装Kibana和IK分词器)使用 Postman连接ES进行测、ESRestAPI(操作索引CRUD操作文档CRUD)、练习

    要理解倒排索引 要能够使用DSL命令操作索引库 要能够使用DSL命令操作文档 要能够使用RestAPI操作索引库.文档 一.ES简介 理解倒排索引的概念和作用 理解es和MySql的区别和使用场景 1 ...

  7. elasticsearch aggregation的入门案例

    1. 数据的准备 我们的数据结构为: https://github.com/elastic/elasticsearch/blob/master/docs/src/test/resources/acco ...

  8. elasticsearch的简介_以及实现原理---全文检索引擎ElasticSearch工作笔记001

    可以去百度查一下 上面的elastic的官网. 我们要知道mysql 是专攻于crud的操作,而在海量数据中进行快速的查询他就不太合适了, 在海量数据中进行检索和存储,需要用elasticsearch ...

  9. Elasticsearch aggregation order by scripted_metric

    ES 使用order by script_metric 会有问题,官方文档中有order by 只能针对numbric,不能对pipeline,scripted_mertic排序 如下 1 无排序使用 ...

最新文章

  1. Elixir 1.2带来多项功能增强和性能提升
  2. oppo n1t android 版本,OPPO N1的手机系统是什么?OPPO N1能升级安卓4.3吗?
  3. MariaDB三种方法安装及多实例实现
  4. C# 利用系统剪贴板 保存 自定义对象
  5. 一二三系列之CodeChef分块——Chef and Churu,Chef and Problems,Children Trips
  6. java速成(11/30)抽象类 abstract
  7. 12款高质量的免费 HTML 网页模板下载
  8. python表格数据对比_python入门之对比两份excel表格数据
  9. iOS常用RGB颜色的色值一览表
  10. 这个游戏的名字叫:哒哒哒哒哒哒
  11. “自我炒作”雄安新区 遭监管层严打
  12. 黑色渐变遮罩html,巧用CSS遮罩
  13. linux磁盘存储和文件管理进阶
  14. PyQt5 文件保存对话框
  15. 杭电1070--喝牛奶
  16. 折半查找判定树的画法(较简单易懂!)
  17. gtsam 学习十一(ISAM2 实践)
  18. 嵌入式软件测试的10条秘诀
  19. div右上角的数字提示
  20. ESP-12S学习(1)--环境、编译、烧录

热门文章

  1. 27岁女生零基础转行学软件测试,来的及吗,我陷入了迷茫......
  2. 高频丙类谐振功率放大器【Multisim】【高频电子线路】
  3. 两直线垂直,斜率乘积为-1的证明
  4. 数据挖掘中常用的数据清洗方法有哪些?
  5. 移动AI智能超算便携工作站
  6. 如何加快 Synology Drive 的同步过程?(外网速度极慢)
  7. 符合w3c 的 strict标准,用 rel 替换_blank打开新窗口
  8. [易水寒]大一实训笔记 第二篇
  9. Windows下解决依赖动态库问题:bat脚本实现自动复制dll文件
  10. Matlab/SimulinkAutosar应用开发