基于 elasticsearch 构建的业务中最常用的聚合查询就是 terms aggregation,它基于 term 粒度的词或数字值进行聚合,如果聚合字段类型是 text,会对一个一个的词根进行聚合,通常不会在 text 类型的字段上使用聚合,terms 聚合类似关系数据库中的 group by 查询。
GET product/_search
{
"track_total_hits": true,
"size": 0,
"aggregations": {
"store_id_agg_udf_name": {
"terms": {
"field": "store_id",
"size": 5
}
}
}
}
假设有商品索引 product
,根据商品的商家门店 store_id
字段聚合查询 5 条记录如上。若只返回聚合的结果,非每个记录的 source
内容,size
指定 0
,如上。返回结果如下:
{
"took" : 11,
"timed_out" : false,
"_shards" : {
"total" : 3,
"successful" : 3,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 160955,
"relation" : "gte"
},
"max_score" : null,
"hits" : [ ]
},
"aggregations" : {
"store_id_agg_udf_name" : {
"doc_count_error_upper_bound" : 257,
"sum_other_doc_count" : 156356,
"buckets" : [
{
"key" : 148272,
"doc_count" : 3038
},
{
"key" : 4669,
"doc_count" : 453
},
{
"key" : 661,
"doc_count" : 378
},
{
"key" : 24688,
"doc_count" : 366
},
{
"key" : 20296,
"doc_count" : 364
}
]
}
}
}
语法
terms 聚合的语法结构如下,核心关键字是 terms
:
{
"aggs": { // 等同 aggregations
"profit_terms": { // 自定义的聚合名称
"terms": { // terms 聚合 关键字
"field": "index_field_name", // 索引中要聚合的字段名
"size": 30, // 获取的聚合字段个数
"shard_size": 100, // 分片获取的聚合字段个数
"min_doc_count": 1, // 各个分片聚合后的最小聚合数
"shard_min_doc_count": 0 // 每个分片聚合的最小聚合数
}
}
}
}
terms aggs 支持很多参数,以及子聚合等高级用法,参照下面的参数讲解。
参数
size
默认情况下,terms 聚合查询返回对应文档数最多的前 10 个聚合结果,我们可以通过 size
来调整返回的聚合结果数,最大值受到 search.max_buckets
配置参数的限制。
如果只包含不到 1000 左右不同的唯一 term
时,我们可以通过增加 size
值来返回所有的数据;若有更多的不同 term 值时,可以改用 composite aggregation
(这个复合聚合代价大,可以用在离线计算上而非在线实时服务上)。
size
值越大使用更多的内存进行计算,并将整个聚合推向接近极限,所以针对 size
值的调整要与实际需求相结合权衡考虑。
shard_size
shard_size
顾名思义是针对每个分片的获取文档的条数,我们知道 es 是分布式的系统,数据在物理层面分不同节点,在逻辑层面也会分片,为了获得更准确的结果,agg 从每个分片中获取的 top size 数要比实际要获取的 size 大,默认情况下 shard_size = size * 1.5 + 10
。
此外,shard_size
的设置不能小于 size
,因为这样没有意义,elasticsearch 针对这种情况,会把 shard_size
纠正为 size
大小。
从上面 size 和 shard_size 的参数定义可以看出,es 的 terms 聚合查询的数据就有不确定性,结果可能和实际结果有一定的偏差与错误性。
es 的聚合查询其实是 map-reduce 的过程,由于 elasticsearch 存在分片的情况,即文档分布不可控,每个分片的聚合统计后的 top 获取后再 reduce 有可能和真实的结果有出入,所以我们要提高 shard_size 数来让其更接近真实值;但同时也要考虑 shard_size 的增大也会带来相应的代价,需要按需设置。
min_doc_count
聚合的字段可能存在一些频率很低的词条,如果这些词条数目比例很大,那么就会造成很多不必要的计算。
min_doc_count
指定各个分片聚合后,再筛选的文档聚合个数最小值,默认为 1。
shard_min_doc_count
shard_min_doc_count
指定每个分片筛选的文档个数最小值,默认为 0。
collect_mode
collect_mode
参数指定聚合收集(collect)数据时的模式,支持 breadth_first
和 depth_first
两种,即广度优先和深度优先。
execution_hint
执行提示,类似于 MySQL 数据库的 hint 功能。
Term Aggregation 聚合通常基于如下两种实现方式:
- 通过直接使用字段值来聚合每个桶的数据(map);
只有当很少的文档匹配查询时,才应该考虑映射。否则,基于序号的执行模式会快得多。默认情况下,map 只在脚本上运行聚合时使用,因为它们没有序号。 - 通过使用字段的全局序号并为每个全局序号分配一个 bucket(global_ordinals);
keyword 类型的字段默认使用 global_ordinals 机制,它使用全局序号动态分配 bucket,因此内存使用与属于聚合范围的文档的值的数量是线性的。
默认情况下,ES 会自动选择,但也可以通过参数 execution_hint
进行人工干预,可选值:global_ordinals
、map
。
missing
missing
参数定义应如何处理聚合字段缺省值的文档,默认情况下,缺省值的文档将被忽略,但它们也可以指定为具体值。
GET product/_search
{
"aggs": {
"tags": {
"terms": {
"field": "tags",
"missing": "N/A"
}
}
}
}
上述示例中,tags
字段值不存在的文档,作为 N/A
值的桶进行统计。
在多个索引上进行聚合操作时,聚合字段的类型可能在所有索引中都不相同。某些类型彼此兼容(如 integer 和 long 或 float 和 double),但当类型是十进制数和非十进制数的混合时,terms 聚合会将非十进制数提升为十进制数。这可能会导致桶值的精度损失。
order 参数
terms 聚合通过 order 参数指定聚合的排序方式,若指定多个,会按照指定顺序依次排序,默认情况下,按照聚合文档数 _count
降序返回。
排序方式一种时,用大括号的对象方式书写;多个时,用中括号开头的数组方式定义。
按照 term 值排序
terms 聚合查询中 _key
关键字表示要聚合 term 的具体值,如根据关键字或数字的升序或降序排列返回。升序示例如下:
GET post/_search
{
"aggs": {
"tags_agg": {
"terms": {
"field": "tags",
"order": {
"_key": "asc"
}
}
}
}
}
若降序,将上述示例中的 asc
改为 desc
即可。
按照聚合文档数排序
terms 聚合查询处理提供 _key
的关键字,还提供了 _count
,它表示 term 对应的文档数,类似于关系型数据库中 group by
聚合语句中 count
函数返回的值。
GET product/_search
{
"aggs": {
"store_id_agg": {
"terms": {
"field": "store_id",
"order": {
"_count": "desc"
}
}
}
}
}
按照子聚合(sub aggregation)统计排序
子聚合(sub aggregation)是聚合内部嵌套的聚合,可以用内置的 max
、min
、avg
、sum
等聚合方法,也可以通过脚本(script
)的统计。
子聚合同样也因为基于 shard 的聚合,所以也有一定的不准确性。
子聚合只有在两种情况下是准确的,只按照一个字段的最大值或最小值。
GET product/_search
{
"from": 0,
"size": 0,
"track_total_hits": true,
"aggregations": {
"store_id_agg": {
"terms": {
"field": "store_id",
"size": 200,
"shard_size": 500,
"min_doc_count": 1,
"shard_min_doc_count": 0,
"show_term_doc_count_error": true,
"execution_hint": "map",
"order": [
{
"order_score": "desc"
},
{
"star_score": "desc"
}
],
"collect_mode": "breadth_first"
},
"aggregations": {
"star_score": {
"max": {
"field": "star_score"
}
},
"order_score": {
"sum": {
"script": {
"source": "(doc['star_score'].value/5.0+doc['bid'].value/5.0+1.0)*doc['sold_cnt'].value"
}
}
}
}
}
}
}
如上示例,先通过脚本的分数取累积和之后降序,star_score 表示门店的5星评分,由于同一个门店的五星评分值是相同的,可以如上形式,当第一个排序分相同时,会查看第二个排序项,但这个已经存在不准确性了,需要根据业务特点选择使用。