es第四篇:Aggregation


有表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意思是復合。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM