有表t,DDL如下:
drop table if exists t; create table if not exists t( id int primary key auto_increment, name varchar(32) not null, salary int not null, city varchar(16) not null )engine=innodb default charset=utf8mb4;
在對應的t_index索引中實現類似如下操作:
select max(salary), min(salary), avg(salary), sum(salary) from t group by city;
ES中的聚合分為4類:Metrics Aggregation、Bucket Aggregation、Pipeline Aggregation、Matrix Aggregation,都可同時操作多個索引,下面以僅操作單個索引講解。
一、Metrics Aggregation
Avg Aggregation、Max Aggregation、Min Aggregation、Sum Aggregation
get /t_index/_search
{
"size":0,
"aggs":{
"avg_id":{
"avg":{
"field":"id"
}
}
}
}
等同於select avg(id) from t;
aggs是關鍵字,是aggregations的縮寫,avg_id是我們給聚合結果起的名字,avg是關鍵字,表示取平均值,field是關鍵字,表示根據某個字段聚合,id表示根據id字段。
如果要執行select avg(id) from t where id < 10呢,只需添加query就好了。
GET /t_index/_search
{
"query" : {
"range": {
"id": {
"gte": 0,
"lt": 10
}
}
},
"size" : 0,
"aggs" : {
"avg_id" : {
"avg": {
"field" : "id"
}
}
}
}
field還可以換成script,表示根據腳本聚合。
get /t_index/_search
{
"size":0,
"aggs":{
"avg_id":{
"avg":{
"script":{
"lang":"painless",
"source":"doc.name.value.length()"
}
}
}
}
}
等同於select avg(char_length(name)) from t;
get /t_index/_search
{
"size":1,
"aggs":{
"max_id":{
"max": {
"field": "id"
}
}
}
}
等同於select max(id) from t;
同樣,也可以根據腳本聚合。
get /t_index/_search
{
"size":1,
"aggs":{
"min_name_length":{
"min": {
"script": {
"lang": "painless",
"source": "doc.name.value.length()"
}
}
}
}
}
等同於select min(char_length(name)) from t;
get /t_index/_search
{
"size":1,
"aggs":{
"sum_id":{
"sum": {
"field": "id"
}
}
}
}
等同於select sum(id) from t;
Value_count Aggregation
get /t_index/_search
{
"size":1,
"aggs":{
"types_count":{
"value_count":{
"field":"id"
}
}
}
}
等同於select count(id) from t;
Stats Aggregation
一次性返回count、min、max、avg、sum
get /t_index/_search
{
"size":0,
"aggs":{
"id_stats":{
"stats": {
"field": "id"
}
}
}
}
等同於select count(id), min(id), max(id), avg(id), sum(id) from t;
返回如下:
get /t_index/_search
{
"aggregations":{
"id_stats":{
"count":11524,
"min":1,
"max":3122,
"avg":1538.1006594932314,
"sum":17725072
}
}
}
Extended_stats Aggregation
增強型的Stats Aggregation,不僅會返回count、min、max、avg、sum,還會返回sum_of_squares、variance、std_deviation、std_deviation_bounds。
sum_of_squares表示平方和,variance表示方差,std_deviation表示標准差。
get /t_index/_search
{
"size":0,
"aggs":{
"id_extended_stats":{
"extended_stats":{
"field":"id"
}
}
}
}
返回如下:
{
"aggregations":{
"id_extended_stats":{
"count":11524,
"min":1,
"max":3122,
"avg":1538.1006594932314,
"sum":17725072,
"sum_of_squares":37053374752,
"variance":849568.7104507973,
"std_deviation":921.7205164532237,
"std_deviation_bounds":{
"upper":3381.541692399679,
"lower":-305.34037341321596
}
}
}
}
Cardinality Aggregation
get /t_index/_search
{
"size":0,
"aggs":{
"count_distinct_name":{
"cardinality":{
"field":"name"
}
}
}
}
等同於select count(distinct name) from t;
cardinality意思是基數,指的是一個集合去重后元素的個數。同selct count(distinct xxx)不同的是,cardinality aggregation的值只是一個近似值,不保證完全准確。cardinality aggregation提供一個precision_threshold參數,precision_threshold越大,聚合操作占用的內存越多,聚合結果也越准確。precision_threshold默認是3000,最大支持40000。
get /t_index/_search
{
"size":0,
"aggs":{
"count_distinct_name":{
"cardinality":{
"field":"name",
"precision_threshold": 5000
}
}
}
}
cardinality底層用的是HyperLogLog算法。這個算法很有意思,有空可以研究下。
在redis中也有對應實現,pfadd、pfcount、pfmerge這三個命令底層都用了HyperLogLog結構。pfadd往一個集合中放數據,pfadd返回這個集合的基數,pfmerge可以合並多個集合。
pfadd hll a b c d e f a b
pfcount hll,返回6
pfadd hll2 c d e g h
pfcount hll2,返回5
pfmerge hll3 hll hll2
pfcount hll3,返回8
二、Bucket Aggregation
Terms Aggregation
get /t_index/_search
{
"size":0,
"aggs":{
"id_group":{
"terms":{
"field":"id"
}
}
}
}
等同於select name, count(1) from t group by name order by count(1) desc limit 10;
默認取top10,然后把top10之后的文檔的總數量賦值給sum_other_doc_count。我們可以通過size參數來控制取top多少。
返回:
{
"aggregations":{
"genres":{
"doc_count_error_upper_bound":0,
"sum_other_doc_count":9,
"buckets":[
{
"key":'A',
"doc_count":6
},
{
"key":'B',
"doc_count":6
},
{
"key":'G',
"doc_count":5
},
{
"key":'D',
"doc_count":4
},
{
"key":'F',
"doc_count":3
},
{
"key":'E',
"doc_count":3
},
{
"key":'C',
"doc_count":3
},
{
"key":'H',
"doc_count":3
},
{
"key":'M',
"doc_count":2
},
{
"key":'J',
"doc_count":2
}
]
}
}
}
也可以根據script來分組。如下
get /t_index/_search
{
"size":0,
"aggs":{
"name_length_group":{
"terms":{
"script":"doc.name.value.length()"
}
}
}
}
等同於select char_length(name), count(1) from t group by char_length(name) order by count(1) desc limit 10;
還可以一次對多個字段進行分組,但是這里的多字段分組並不是group by id, name這種意思,而是在根據id分組,取其topN的同時,又根據name分組,取其topM。如下:
get /t_index/_search
{
"size":0,
"aggs":{
"id_group":{
"terms":{
"field":"id",
"size":5
}
},
"name_group":{
"terms":{
"field":"name",
"size":15
}
}
}
}
等同於
select id, count(1) from t group by id order by count(1) desc limit 5;賦值給id_group
select name, count(1) from t group by name order by count(1) desc limit 15;賦值給name_group
默認是根據doc_count倒序排列的,我們還可以用order參數來自定義排列順序,如根據doc_count正序,根據分組字段排序,甚至還可以根據其他聚合值排序,如下
get /t_index/_search
{
"size":0,
"aggs":{
"genres":{
"terms":{
"field":"name",
"size":10000,
"order":{
"_count":"asc"
}
}
}
}
}
等同於select name, count(1) from t group by name order by count(1) limit 10000;
_count表示根據doc_count排序,_key表示根據分組字段排序。
get /t_index/_search
{
"size":0,
"aggs":{
"genres":{
"terms":{
"field":"name",
"size":10000,
"order":{
"_key":"asc"
}
}
}
}
}
等同於select name, count(1) from t group by name order by name limit 10000;
get /t_index/_search
{
"size":0,
"aggs":{
"genres":{
"terms":{
"field":"name",
"order":{
"max_id":"desc"
}
},
"aggs":{
"max_id":{
"max":{
"field":"id"
}
}
}
}
}
}
用於排序的聚合也可以是stats aggregation或者extended_stats aggregation,如下:
get /t_index/_search
{
"size":0,
"aggs":{
"genres":{
"terms":{
"field":"name",
"order":{
"id_stats.min":"desc"
}
},
"aggs":{
"id_stats":{
"stats":{
"field":"id"
}
}
}
}
}
}
用min_doc_count參數實現having的效果:
get /t_index/_search
{
"size":0,
"aggs":{
"genres":{
"terms":{
"field":"name",
"shard_size":10000,
"min_doc_count":400,
"order":{
"_count":"asc"
}
}
}
}
}
等同於select name, count(1) from t group by name having count(1) >= 400 order by count(1) asc limit 10;
如果想在分組字段上加一些where條件,可以用include參數、exclude參數,如下
get /t_index/_search
{
"size":0,
"aggs":{
"names":{
"terms":{
"field":"name",
"include":"球蛋白",
"shard_size":1000
}
}
}
}
等同於select name, count(1) from t where name = '球蛋白' group by name order by count(1) desc limit 10;
get /t_index/_search
{
"size":0,
"aggs":{
"names":{
"terms":{
"field":"name",
"include":["球蛋白", "白蛋白"],
"shard_size":1000
}
}
}
}
等同於select name, count(1) from t where name in ('球蛋白', '白蛋白') group by name order by count(1) desc limit 10;
get /t_index/_search
{
"size":0,
"aggs":{
"names":{
"terms":{
"field":"name",
"include":".*蛋白.*",
"exclude":"組織.*",
"shard_size":1000
}
}
}
}
等同於select name, count(1) from t where name like '%蛋白%' and name not like '組織%' group by name order by count(1) desc limit 10;
底層原理:索引的各主分片或其副本分片會先在本地分組,然后協調節點會根據doc_count抽取前shard_size的文檔,匯總后,返回top size。
如果只有一個主分片,那么shard_size值默認等於size,否則,shard_size值默認是1.5 * size + 10。當shard_size值比分組字段的基數小時,top size的key和doc_count都不一定准確。當返回的doc_count_error_upper_bound不等於0時,就表示有的doc_count不准了。
get /t_index/_search
{
"size":0,
"aggs":{
"genres":{
"terms":{
"field":"id",
"size":10,
"shard_size":100000
}
}
}
}
為什么doc_count會不准呢?
假設我們有一個有3個主分片的索引,第一個主分片上有6個A、5個B、4個C、3個D、2個E、1個F,第二個主分片上也是有6個A、5個B、4個C、3個D、2個E、1個F,第三個分片上數據稍微有點不一樣,有6個A、5個B、4個C、3個D、2個F、1個E。現在取top5,那么協調節點會從第一個主分片上取前5名,即6個A、5個B、4個C、3個D、2個E,從第二個分片上取前5名,即6個A、5個B、4個C、3個D、2個E,從第三個分片上取前5名,即6個A、5個B、4個C、3個D、2個F,文檔匯總后,得出有18個A、15個B、12個C、9個D、4個E、2個F,返回前5名,即A 18個,B 15個,C 12個,D 9個,E 4個。很明顯,E的數量是不對的,因為在第三個分片上,E排不上前5,從而沒有被抽取到協調節點上,但是在前兩個分片上的數量就已經使其排到了前5。
為什么key會不准呢?
還是以一個有3個主分片的索引舉例,第一個主分片上有20個A、19個B、18個C、17個D、16個E、15個P,第二個主分片上有15個F、14個G、13個H、12個I、11個J、10個P,第三個主分片上有10個K、9個L、8個M、7個N、6個O、5個P。現在取top5,那么協調節點會從第一個主分片上取前5名,即20個A、19個B、18個C、17個D、16個E,從第二個分片上取前5名,即15個F、14個G、13個H、12個I、11個J,從第三個分片上取前5名,即10個K、9個L、8個M、7個N、6個O,文檔匯總后返回top5,即A 20個,B 19個,C 18個,D 7個,E 16個。很明顯,top5不對,因為P在三個分片上的總數量是30,理應排在第一名的,但是現在卻名落孫山。
如果要terms aggregation的結果准確,第一種方式是把所有文檔都routing到一個主分片上,第二種方式是聚合時shard_size設置大一點,超過分組字段值基數就沒毛病了,但這也意味着給es更大的壓力。
Composite Aggregation
composite意思是復合。
