ES的聚合项目:有相关基于ElasticSearch5.6v Java API 的各种用法
源码地址: GitHub
聚合概念
聚合(Aggregations)也拥有一种可组合(Composable)的语法:独立的功能单元可以被混合在一起来满足你的需求。这意味着需要学习的基本概念虽然不多,但是它们的组合方式是几近无穷的。
为了掌握聚合,你只需要了解两个主要概念:
Buckets(桶):
满足某个条件的文档集合。
Metrics(指标):
为某个桶中的文档计算得到的统计信息。
就是这样!每个聚合只是简单地由一个或者多个桶,零个或者多个指标组合而成。可以将它粗略地转换为SQL:
SELECT COUNT(color)
FROM table
GROUP BY color
以上的COUNT(color)就相当于指标。GROUP BY color则相当于桶。
桶和SQL中的组(Grouping)拥有相似的概念,而指标则与COUNT(),SUM(),MAX()等相似。
桶(Buckets)
一个桶就是满足特定条件的一个文档集合:
- 一名员工要么属于男性桶,或者女性桶。
- 城市Albany属于New York州这个桶。
- 日期2014-10-28属于十月份这个桶。
指标(Metrics)
桶能够让我们对文档进行有意义的划分,但是最终我们还是需要对每个桶中的文档进行某种指标计算。分桶是达到最终目的的手段:提供了对文档进行划分的方法,从而让你能够计算需要的指标。
多数指标仅仅是简单的数学运算(比如,min,mean,max以及sum),它们使用文档中的值进行计算。在实际应用中,指标能够让你计算例如平均薪资,最高出售价格,或者百分之95的查询延迟。
将两者结合起来
一个聚合就是一些桶和指标的组合。一个聚合可以只有一个桶,或者一个指标,或者每样一个。在桶中甚至可以有多个嵌套的桶。比如,我们可以将文档按照其所属国家进行分桶,然后对每个桶计算其平均薪资(一个指标)。
因为桶是可以嵌套的,我们能够实现一个更加复杂的聚合操作:
- 将文档按照国家进行分桶。(桶)
- 然后将每个国家的桶再按照性别分桶。(桶)
- 然后将每个性别的桶按照年龄区间进行分桶。(桶)
- 最后,为每个年龄区间计算平均薪资。(指标)
此时,就能够得到每个<国家,性别,年龄>组合的平均薪资信息了。它可以通过一个请求,一次数据遍历来完成!
聚合的测试数据(Aggregation Test-Drive)
让我们从一个例子开始。我们会建立一个也许对汽车交易商有所用处的聚合。数据是关于汽车交易的:汽车型号,制造商,销售价格,销售时间以及一些其他的相关数据。
首先,通过批量索引(Bulk-Index)来添加一些数据:
PUT http://node1:9200/cars
{
"mappings":{
"transactions":{
"properties":{
"price":{
"type":"integer"
},
"color":{
"type":"keyword"
},
"make":{
"type":"keyword"
},
"sold":{
"type":"date"
}
}
}
}
}
或者===========================================================
{
"mappings":{
"transactions":{
"properties":{
"price":{
"type":"integer"
},
"color":{
"type":"text",
"fielddata":true
},
"make":{
"type":"text",
"fielddata":true
},
"sold":{
"type":"date"
}
}
}
}
}
POST http://node1:9200/cars/transactions/_bulk
{ "index": {}}
{ "price" : 10000, "color" : "red", "make" : "honda", "sold" : "2014-10-28" }
{ "index": {}}
{ "price" : 20000, "color" : "red", "make" : "honda", "sold" : "2014-11-05" }
{ "index": {}}
{ "price" : 30000, "color" : "green", "make" : "ford", "sold" : "2014-05-18" }
{ "index": {}}
{ "price" : 15000, "color" : "blue", "make" : "toyota", "sold" : "2014-07-02" }
{ "index": {}}
{ "price" : 12000, "color" : "green", "make" : "toyota", "sold" : "2014-08-19" }
{ "index": {}}
{ "price" : 20000, "color" : "red", "make" : "honda", "sold" : "2014-11-05" }
{ "index": {}}
{ "price" : 80000, "color" : "red", "make" : "bmw", "sold" : "2014-01-01" }
{ "index": {}}
{ "price" : 25000, "color" : "blue", "make" : "ford", "sold" : "2014-02-12" }
因为我们并不关心搜索结果,所以size:0 。聚合工作在顶层的aggs参数下(当然你也可以使用更长的aggregations)。 然后给这个聚合起了一个名字:colors。 最后,我们定义了一个terms类型的桶,它针对color字段。
聚合是以搜索结果为上下文而执行的,这意味着它是搜索请求中的另一个顶层参数(Top-level Parameter)。聚合可以和查询同时使用,这一点我们在后续的范围聚合(Scoping Aggregations)中介绍。
接下来我们为聚合起一个名字。命名规则是有你决定的; 聚合的响应会被该名字标记,因此在应用中你就能够根据名字来得到聚合结果,并对它们进行操作了。
然后,我们开始定义聚合本身。比如,我们定义了一个terms类型的桶。terms桶会动态地为每一个它遇到的不重复的词条创建一个新的桶。因为我们针对的是color字段,那么terms桶会动态地为每种颜色创建一个新桶。
如果执行出现报错,搜了一下应该是5.x后对排序,聚合这些操作用单独的数据结构(fielddata)缓存到内存里了,需要单独开启,简单来说就是在聚合前执行如下操作
让我们执行该聚合来看看其结果:
{
"took": 27,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"failed": 0
},
"hits": {
"total": 7,
"max_score": 0,
"hits": []
},
"aggregations": {
"colors": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": "red",
"doc_count": 4
},
{
"key": "green",
"doc_count": 2
},
{
"key": "blue",
"doc_count": 1
}
]
}
}
}
因为我们使用的size为0,所以没有搜索结果被返回。 每个桶中的key对应的是在color字段中找到的不重复的词条。它同时也包含了一个doc_count,用来表示包含了该词条的文档数量。
响应包含了一个桶列表,每个桶都对应着一个不重复的颜色(比如,红色或者绿色)。每个桶也包含了“掉入”该桶中的文档数量。比如,有4辆红色的车。
前面的例子是完全实时(Real-Time)的:如果文档是可搜索的,那么它们就能够被聚合。这意味着你能够将拿到的聚合结果置入到一个图形库中来生成实时的仪表板(Dashboard)。一旦你卖出了一台银色汽车,在图形上关于银色汽车的统计数据就会被动态地更新。
添加一个指标(Metric)
从前面的例子中,我们可以知道每个桶中的文档数量。但是,通常我们的应用会需要基于那些文档的更加复杂的指标(Metric)。比如,每个桶中的汽车的平均价格是多少?
为了得到该信息,我们得告诉ES需要为哪些字段计算哪些指标。这需要将指标嵌套到桶中。指标会基于桶中的文档的值来计算相应的统计信息。
让我们添加一个计算平均值的指标:
GET /cars/transactions/_search
{
"size": 0,
"aggs" : {
"colors" : {
"terms" : {
"field" : "color"
},
"aggs":{
"avg_price":{
"avg":{
"field":"price"
}
}
}
}
}
}
我们添加了一个新的aggs层级来包含该指标。然后给该指标起了一个名字:avg_price。最后定义了该指标作用的字段为price。
正如你所看到的,我们向前面的例子中添加了一个新的aggs层级。这个新的聚合层级能够让我们将avg指标嵌套在terms桶中。这意味着我们能为每种颜色都计算一个平均值。
同样的,我们需要给指标起一个名(avg_price)来让我们能够在将来得到其值。最后,我们指定了指标本身(avg)以及该指标作用的字段(price):
{
"took": 29,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"failed": 0
},
"hits": {
"total": 7,
"max_score": 0,
"hits": []
},
"aggregations": {
"colors": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": "red",
"doc_count": 4,
"avg_price": {
"value": 32500
}
},
{
"key": "green",
"doc_count": 2,
"avg_price": {
"value": 21000
}
},
{
"key": "blue",
"doc_count": 1,
"avg_price": {
"value": 15000
}
}
]
}
}
}
桶定义及指标定义都是通过aggs来进行的 但是定义关键字不同,一个是terms桶定义 ,一个是avg等指标定义符
现在,在响应中多了一个avg_price元素。
尽管得到的响应只是稍稍有些变化,但是获得的数据增加的了许多。之前我们只知道有4辆红色汽车。现在我们知道了红色汽车的平均价格是32500刀。这些数据你可以直接插入到报表中。
桶中的桶(Buckets inside Buckets)
桶中桶,桶必须有自己的名字,桶中的指标也必须有自己的名字。
当你开始使用不同的嵌套模式时,聚合强大的能力才会显现出来。在前面的例子中,我们已经知道了如何将一个指标嵌套进一个桶的,它的功能已经十分强大了。
但是真正激动人心的分析功能来源于嵌套在其它桶中的桶。现在,让我们来看看如何找到每种颜色的汽车的制造商分布信息:
POST http://node1:9200/cars/transactions/_search
{
"aggs":
{
"colors": {
"terms": {
"field": "color"
},
"aggs":
{
"avg_price": {
"avg": {
"field": "price"
}
},
"make": {
"terms": {
"field": "make"
}
}
}
}
},
"size": 0
}
此时发生了一些有意思的事情。首先,你会注意到前面的avg_price指标完全没有变化。一个聚合的每个层级都能够拥有多个指标或者桶。avg_price指标告诉了我们每种汽车颜色的平均价格。为每种颜色创建的桶和指标是各自独立的。
这个性质对你的应用而言是很重要的,因为你经常需要收集一些互相关联却又完全不同的指标。聚合能够让你对数据遍历一次就得到所有需要的信息。
另外一件重要的事情是添加了新聚合make,它是一个terms类型的桶(嵌套在名为colors的terms桶中)。这意味着我们会根据数据集创建不重复的(color, make)组合。
每个桶中添加桶 或者 计算所在桶的集合函数 必须在与桶名称下一级声明 aggs(同级有所在桶的terms声明) ,然后再aggs下级定义 桶的名称 或者 函数的名称, 也可以在aggs 下级同时进行桶中桶的定义 和 桶中函数的定义。如上面例子:
在colors桶中添加桶make:首先在colors 中声明aggs ,然后在aggs 声明新的桶的名称make,make下级利用terms定义该桶按什么分。
让我们来看看得到的响应:
{
"took": 26,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"failed": 0
},
"hits": {
"total": 7,
"max_score": 0,
"hits": []
},
"aggregations": {
"colors": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": "red",
"doc_count": 4,
"avg_price": {
"value": 32500
},
"make": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": "honda",
"doc_count": 3
},
{
"key": "bmw",
"doc_count": 1
}
]
}
},
{
"key": "green",
"doc_count": 2,
"avg_price": {
"value": 21000
},
"make": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": "ford",
"doc_count": 1
},
{
"key": "toyota",
"doc_count": 1
}
]
}
},
{
"key": "blue",
"doc_count": 1,
"avg_price": {
"value": 15000
},
"make": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": "toyota",
"doc_count": 1
}
]
}
}
]
}
}
}
该响应告诉了我们如下信息:
有4辆红色汽车。
红色汽车的平均价格是32500美刀。
红色汽车中的3辆是Honda,1辆是BMW。
最后的一个修改(One Final Modification)
为每个制造商添加两个指标来计算最低和最高价格:
POST http://node1:9200/cars/transactions/_search
{
"aggs": {
"colors": {
"terms": {
"field": "color"
},
"aggs": {
"avg_price": { "avg": { "field": "price" }
},
"make" : {
"terms" : {
"field" : "make"
},
"aggs" : {
"min_price" : { "min": { "field": "price"} },
"max_price" : { "max": { "field": "price"} }
}
}
}
}
},
"size": 0
}
我们需要添加另一个aggs层级来进行对min和max的嵌套。
得到的响应如下:
{
"took": 90,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"failed": 0
},
"hits": {
"total": 7,
"max_score": 0,
"hits": []
},
"aggregations": {
"colors": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": "red",
"doc_count": 4,
"avg_price": {
"value": 32500
},
"make": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": "honda",
"doc_count": 3,
"max_price": {
"value": 20000
},
"min_price": {
"value": 10000
}
},
{
"key": "bmw",
"doc_count": 1,
"max_price": {
"value": 80000
},
"min_price": {
"value": 80000
}
}
]
}
},
{
"key": "green",
"doc_count": 2,
"avg_price": {
"value": 21000
},
"make": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": "ford",
"doc_count": 1,
"max_price": {
"value": 30000
},
"min_price": {
"value": 30000
}
},
{
"key": "toyota",
"doc_count": 1,
"max_price": {
"value": 12000
},
"min_price": {
"value": 12000
}
}
]
}
},
{
"key": "blue",
"doc_count": 1,
"avg_price": {
"value": 15000
},
"make": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": "toyota",
"doc_count": 1,
"max_price": {
"value": 15000
},
"min_price": {
"value": 15000
}
}
]
}
}
]
}
}
}
在每个make桶下,多了min和max的指标。
此时,我们可以得到如下信息:
有4辆红色汽车。
红色汽车的平均价格是32500美刀。
红色汽车中的3辆是Honda,1辆是BMW。
红色Honda汽车中,最便宜的价格为10000美刀。
最贵的红色Honda汽车为20000美刀。
直接求值:
POST http://node1:9200/cars/transactions/_search
{
"aggs": {
"colors": {
"terms": {
"field": "color"
},
"aggs": {
"avg_price": { "avg": { "field": "price" }},
"min_price" : { "min": { "field": "price"}} ,
"max_price" : { "max": { "field": "price"}}
}
}
},
"size": 0
}
==========================================================================================
{
"took": 62,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"failed": 0
},
"hits": {
"total": 7,
"max_score": 0,
"hits": []
},
"aggregations": {
"colors": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": "red",
"doc_count": 4,
"max_price": {
"value": 80000
},
"min_price": {
"value": 10000
},
"avg_price": {
"value": 32500
}
},
{
"key": "green",
"doc_count": 2,
"max_price": {
"value": 30000
},
"min_price": {
"value": 12000
},
"avg_price": {
"value": 21000
}
},
{
"key": "blue",
"doc_count": 1,
"max_price": {
"value": 15000
},
"min_price": {
"value": 15000
},
"avg_price": {
"value": 15000
}
}
]
}
}
}
创建条形图(Building Bar Charts)
柱状图桶(Histogram Bucket)非常有用。柱状图在本质上就是条形图,如果你创建过一份报告或者分析面板(Analytics Dashboard),毫无疑问其中会有一些条形图。柱状图通过指定一个间隔(Interval)来工作。如果我们使用柱状图来表示销售价格,你或许会指定一个值为20000的间隔。因此每20000美刀会创建一个桶。然后文档会被分配到桶中。
对于我们的仪表板,我们想要知道每个价格区间中有多少辆车。同时我们也想知道该价格桶中产生了多少收入。这是通过将该间隔中所有的车的售价累加而计算得到的。
为了达到这一目的,我们使用了一个histogram(柱状图)类型的聚合然后在其中嵌套了一个sum指标:
POST http://node1:9200/cars/transactions/_search
{
"aggs": {
"price": {
"histogram": {
"field": "price",
"interval":20000
},
"aggs": {
"revenue":{
"sum":{
"field":"price"
}
}
}
}
},
"size": 0
}
正如你能看到的那样,我们的查询是围绕着价格聚合而建立的,该聚合包含了一个柱状图桶。该桶需要一个数值字段以及一个间隔值来进行计算。间隔用来定义每个桶有“多宽”。间隔为20000意味着我们能够拥有区间[0-19999, 20000-39999, 等]。
接下来,我们在柱状图中定义了一个嵌套的指标。它是一个sum类型的指标,会将该区间中的文档的price字段进行累加。这就得到了每个价格区间中的收入,因此我们就能够从中看出是普通车还是豪华车赚的更多。
以下是得到的响应:
{
"took": 32,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"failed": 0
},
"hits": {
"total": 7,
"max_score": 0,
"hits": []
},
"aggregations": {
"price": {
"buckets": [
{
"key": 0,
"doc_count": 3,
"revenue": {
"value": 37000
}
},
{
"key": 20000,
"doc_count": 3,
"revenue": {
"value": 70000
}
},
{
"key": 40000,
"doc_count": 0,
"revenue": {
"value": 0
}
},
{
"key": 60000,
"doc_count": 0,
"revenue": {
"value": 0
}
},
{
"key": 80000,
"doc_count": 1,
"revenue": {
"value": 80000
}
}
]
}
}
}
从图形上,你可以将前面的数据表示如下:
当然,你可以使用任何生成类别和统计信息的聚合来创建条形图,并不仅限于使用histogram桶。让我们创建一个受欢迎的汽车制造商的条形图,其中包含了它们的平均价格和标准误差(Standard Error)。需要使用的而是terms桶以及一个extended_stats指标:
POST http://node1:9200/cars/transactions/_search
{
"aggs": {
"makes": {
"terms": {
"field": "make",
"size": 10
},
"aggs": {
"stats": {
"extended_stats": {
"field": "price"
}
}
}
}
},
"size":0
}
它会返回一个制造商列表(根据受欢迎程度排序)以及针对每个制造商的一些列统计信息。其中,我们对stats.avg,stats.count以及stats.std_deviation感兴趣。有了这一信息,我们能够计算出标准误差:
std_err = std_deviation / count
{
"took": 35,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"failed": 0
},
"hits": {
"total": 7,
"max_score": 0,
"hits": []
},
"aggregations": {
"makes": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": "honda",
"doc_count": 3,
"stats": {
"count": 3,
"min": 10000,
"max": 20000,
"avg": 16666.666666666668,
"sum": 50000,
"sum_of_squares": 900000000,
"variance": 22222222.22222221,
"std_deviation": 4714.045207910315,
"std_deviation_bounds": {
"upper": 26094.757082487296,
"lower": 7238.5762508460375
}
}
},
{
"key": "toyota",
"doc_count": 2,
"stats": {
"count": 2,
"min": 12000,
"max": 15000,
"avg": 13500,
"sum": 27000,
"sum_of_squares": 369000000,
"variance": 2250000,
"std_deviation": 1500,
"std_deviation_bounds": {
"upper": 16500,
"lower": 10500
}
}
},
{
"key": "bmw",
"doc_count": 1,
"stats": {
"count": 1,
"min": 80000,
"max": 80000,
"avg": 80000,
"sum": 80000,
"sum_of_squares": 6400000000,
"variance": 0,
"std_deviation": 0,
"std_deviation_bounds": {
"upper": 80000,
"lower": 80000
}
}
},
{
"key": "ford",
"doc_count": 1,
"stats": {
"count": 1,
"min": 30000,
"max": 30000,
"avg": 30000,
"sum": 30000,
"sum_of_squares": 900000000,
"variance": 0,
"std_deviation": 0,
"std_deviation_bounds": {
"upper": 30000,
"lower": 30000
}
}
}
]
}
}
}
时间数据处理(Looking at Time)
如果在ES中,搜索是最常见的行为,那么创建日期柱状图(Date Histogram)肯定是第二常见的。
- 今年的每个月销售了多少辆车?
- 过去的12小时中,这只股票的价格是多少?
- 上周每个小时我们的网站的平均延迟是多少?
常规的histogram通常使用条形图来表示,而date_histogram倾向于被装换为线图(Line Graph)来表达时间序列(Time Series)。很多公司使用ES就是为了对时间序列数据进行分析。
date_histogram的工作方式和常规的histogram类似。常规的histogram是基于数值字段来创建数值区间的桶,而date_histogram则是基于时间区间来创建桶。因此每个桶是按照某个特定的日历时间定义的(比如,1个月或者是2.5天)。
常规Histogram能够和日期一起使用吗?
从技术上而言,是可以的。常规的histogram桶可以和日期一起使用。但是,它不懂日期相关的信息(Not calendar-aware)。而对于date_histogram,你可以将间隔(Interval)指定为1个月,它知道2月份比12月份要短。date_histogram还能够和时区一同工作,因此你可以根据用户的时区来对图形进行定制,而不是根据服务器。
常规的histogram会将日期理解为数值,这意味着你必须将间隔以毫秒的形式指定。同时聚合也不理解日历间隔,所以它对于日期几乎是没法使用的。
第一个例子中,我们会创建一个简单的线图(Line Chart)来回答这个问题:每个月销售了多少辆车?
GET /cars/transactions/_search?search_type=count
{
"aggs": {
"sales": {
"date_histogram": {
"field": "sold",
"interval": "month",
"format": "yyyy-MM-dd"
}
}
}
}
在查询中有一个聚合,它为每个月创建了一个桶。它能够告诉我们每个月销售了多少辆车。同时指定了一个额外的格式参数让桶拥有更"美观"的键值。在内部,日期被简单地表示成数值。然而这会让UI设计师生气,因此使用格式参数可以让日期以更常见的格式进行表示。 5.x以下 空桶不会显示 5.x以上默认空桶会显示。
{
"took": 326,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"failed": 0
},
"hits": {
"total": 7,
"max_score": 0,
"hits": []
},
"aggregations": {
"sales": {
"buckets": [
{
"key_as_string": "2014-01-01",
"key": 1388534400000,
"doc_count": 1
},
{
"key_as_string": "2014-02-01",
"key": 1391212800000,
"doc_count": 0
},
{
"key_as_string": "2014-03-01",
"key": 1393632000000,
"doc_count": 0
},
{
"key_as_string": "2014-04-01",
"key": 1396310400000,
"doc_count": 0
},
{
"key_as_string": "2014-05-01",
"key": 1398902400000,
"doc_count": 1
},
{
"key_as_string": "2014-06-01",
"key": 1401580800000,
"doc_count": 0
},
{
"key_as_string": "2014-07-01",
"key": 1404172800000,
"doc_count": 1
},
{
"key_as_string": "2014-08-01",
"key": 1406851200000,
"doc_count": 1
},
{
"key_as_string": "2014-09-01",
"key": 1409529600000,
"doc_count": 0
},
{
"key_as_string": "2014-10-01",
"key": 1412121600000,
"doc_count": 1
},
{
"key_as_string": "2014-11-01",
"key": 1414800000000,
"doc_count": 2
}
]
}
}
}
聚合完整地被表达出来了。你能看到其中有用来表示月份的桶,每个桶中的文档数量,以及漂亮的key_as_string。
返回空桶
5.x之前不会返回空桶, 5.x之后会空桶和非空桶同时返回
因此本质上,我们需要返回所有的桶,哪怕其中不含有任何文档。我们可以设置两个额外的参数来实现这一行为:
{
"aggs": {
"sales": {
"date_histogram": {
"field": "sold",
"interval": "month",
"format": "yyyy-MM-dd" ,
"min_doc_count":0,
"extended_bounds":{
"min":"2014-01-01",
"max":"2014-12-31"
}
}
}
},
"size":0
}
以上的min_doc_count参数会强制返回空桶,extended_bounds参数会强制返回一整年的数据。
这两个参数会强制返回该年中的所有月份,无论它们的文档数量是多少。min_doc_count的意思很容易懂:它强制返回哪怕为空的桶。
extended_bounds参数需要一些解释。min_doc_count会强制返回空桶,但是默认ES只会返回在你的数据中的最小值和最大值之间的桶。
因此如果你的数据分布在四月到七月,你得到的桶只会表示四月到七月中的几个月(可能为空,如果使用了min_doc_count=0)。为了得到一整年的桶,我们需要告诉ES需要得到的桶的范围。extended_bounds参数就是用来告诉ES这一范围的。一旦你添加了这两个设置,得到的响应就很容易被图形生成库处理而最终得到下图:
另外的例子
我们已经看到过很多次,为了实现更复杂的行为,桶可以嵌套在桶中。为了说明这一点,我们会创建一个用来显示每个季度,所有制造商的总销售额的聚合。同时,我们也会在每个季度为每个制造商单独计算其总销售额,因此我们能够知道哪种汽车创造的收益最多:
POST http://node1:9200/cars/transactions/_search
{
"aggs": {
"sales": {
"date_histogram": {
"field": "sold",
"interval": "quarter",
"format": "yyyy-MM-dd",
"min_doc_count" : 0,
"extended_bounds" : {
"min" : "2014-01-01",
"max" : "2014-12-31"
}
},
"aggs": {
"per_make_sum": {
"terms": {
"field": "make"
},
"aggs": {
"sum_price": {
"sum": { "field": "price" }
}
}
},
"total_sum": {
"sum": { "field": "price" }
}
}
}
},
"size":0
}
可以发现,interval参数被设成了quarter。季度
得到的响应如下(删除了很多):
{
"took": 32,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"failed": 0
},
"hits": {
"total": 7,
"max_score": 0,
"hits": []
},
"aggregations": {
"sales": {
"buckets": [
{
"key_as_string": "2014-01-01",
"key": 1388534400000,
"doc_count": 1,
"per_make_sum": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": "bmw",
"doc_count": 1,
"sum_price": {
"value": 80000
}
}
]
},
"total_sum": {
"value": 80000
}
},
{
"key_as_string": "2014-04-01",
"key": 1396310400000,
"doc_count": 1,
"per_make_sum": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": "ford",
"doc_count": 1,
"sum_price": {
"value": 30000
}
}
]
},
"total_sum": {
"value": 30000
}
},
{
"key_as_string": "2014-07-01",
"key": 1404172800000,
"doc_count": 2,
"per_make_sum": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": "toyota",
"doc_count": 2,
"sum_price": {
"value": 27000
}
}
]
},
"total_sum": {
"value": 27000
}
},
{
"key_as_string": "2014-10-01",
"key": 1412121600000,
"doc_count": 3,
"per_make_sum": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": "honda",
"doc_count": 3,
"sum_price": {
"value": 50000
}
}
]
},
"total_sum": {
"value": 50000
}
}
]
}
}
}
我们可以将该响应放入到一个图形中,使用一个线图(Line Chart)来表达总销售额,一个条形图来显示每个制造商的销售额(每个季度),如下所示:
无限的可能性
显然它们都是简单的例子,但是在对聚合进行绘图时,是存在无限的可能性的。比如,下图是Kibana中的一个用来进行实时分析的仪表板,它使用了很多聚合:
因为聚合的实时性,类似这样的仪表板是很容易进行查询,操作和交互的。这让它们非常适合非技术人员和分析人员对数据进行分析,而不需要他们创建一个Hadoop任务。
为了创建类似Kibana的强大仪表板,你需要掌握一些高级概念,比如作用域(Scoping),过滤(Filtering)和聚合排序(Sorting Aggregations)。
ES聚合剩余内容
[Elasticsearch] 聚合作用域(Scoping Aggregations)
[Elasticsearch] 过滤查询以及聚合(Filtering Queries and Aggregations)