聚合作用范围
Elasticsearch 聚合分析的默认作用范围是 query 的查询结果集,此外还支持以下方式改变聚合的作用范围:
- filter
- post filter
- global
1. filter
用于对具体的某个聚合做条件过滤,如下示例:
curl -X POST "localhost:9200/employees/_search?size=0&pretty" -H 'Content-Type: application/json' -d'{"aggs" : {"older_person" : {"filter" : {"range" : {"age" : {"from" : 35}}},"aggs" : {"jobs" : {"terms" : { "field" : "job.keyword" }}}},"all_jobs": {"terms" : { "field" : "job.keyword" }}}}'
通过 filter 对 older_person 这个聚合进行条件过滤,只会对符合过滤条件的文档做聚合,而和它平级的 all_jobs 则不受 filter 的条件过滤,会对所有文档做聚合。
返回结果:
{"aggregations" : {"older_person" : {"doc_count" : 2,"jobs" : {"doc_count_error_upper_bound" : 0,"sum_other_doc_count" : 0,"buckets" : [{"key" : "Dev Manager","doc_count" : 1},{"key" : "Java Programmer","doc_count" : 1}]}},"all_jobs" : {"doc_count_error_upper_bound" : 0,"sum_other_doc_count" : 0,"buckets" : [{"key" : "Java Programmer","doc_count" : 7},{"key" : "Javascript Programmer","doc_count" : 4},{"key" : "QA","doc_count" : 3},{"key" : "DBA","doc_count" : 2},{"key" : "Web Designer","doc_count" : 2},{"key" : "Dev Manager","doc_count" : 1},{"key" : "Product Manager","doc_count" : 1}]}}}
2. post_filter
在计算了聚合之后,在搜索请求的最后,将 post_filter 应用于搜索结果。也就是说,聚合还是对所有文档进行聚合分析,返回的 aggregations 中包含完整聚合结果,但在搜索时会对搜索结果使用 post_filter 过滤器,即返回的 hits 中只包含 job 字段值为 Dev Manager 的文档。
curl -X POST "localhost:9200/employees/_search?pretty" -H 'Content-Type: application/json' -d'{"aggs" : {"jobs" : {"terms" : { "field" : "job.keyword" }}},"post_filter" : {"match" : {"job.keyword" : "Dev Manager"}}}'
返回结果:
{"hits" : {"total" : {"value" : 1,"relation" : "eq"},"max_score" : 1.0,"hits" : [{"_index" : "employees","_type" : "_doc","_id" : "2","_score" : 1.0,"_source" : {"name" : "Underwood","age" : 41,"job" : "Dev Manager","gender" : "male","salary" : 50000}}]},"aggregations" : {"jobs" : {"doc_count_error_upper_bound" : 0,"sum_other_doc_count" : 0,"buckets" : [{"key" : "Java Programmer","doc_count" : 7},{"key" : "Javascript Programmer","doc_count" : 4},{"key" : "QA","doc_count" : 3},{"key" : "DBA","doc_count" : 2},{"key" : "Web Designer","doc_count" : 2},{"key" : "Dev Manager","doc_count" : 1},{"key" : "Product Manager","doc_count" : 1}]}}}
3. global
curl -X POST "localhost:9200/sales/_search?size=0&pretty" -H 'Content-Type: application/json' -d'{"query" : {"match" : { "type" : "t-shirt" }},"aggs" : {"all_products" : {"global" : {},"aggs" : {"avg_price" : { "avg" : { "field" : "price" } }}},"t_shirts": { "avg" : { "field" : "price" } }}}'
在上面这个查询中,在 all_products 这个聚合中使用了 global,表示 all_products 聚合不受上面 query 条件的影响,会对所有文档聚合取平均值,而 t_shirts 聚合只会对 type 为 t-shirt 的文档进行聚合取平均值。
返回结果如下:
{"aggregations": {"all_products": {"doc_count": 7,"avg_price": {"value": 140.71428571428572}},"t_shirts": {"value": 128.33333333333334}}}
聚合排序
在聚合中可以通过 order 参数指定按照某个字段进行排序,聚合提供了 _count 和 _key 元字段,表示聚合后的文档数量和聚合后桶的 key 值,默认情况下按照 _count 降序排列。
如下示例按照 _count 升序排列,在数量相同时按 _key 降序排序
curl -XPOST "http://localhost:9200/employees/_search?size=0&pretty" -H 'Content-Type: application/json' -d'{"query": {"range" : {"age" : {"gte" : 20}}},"aggs" : {"jobs" : {"terms" : {"field" :"job.keyword","order" :[{"_count" : "asc"},{"_key" : "desc"}]}}}}'
此外,还可以按照子聚合的值进行排序,如下示例:
curl -XPOST "http://localhost:9200/employees/_search?size=0" -H 'Content-Type: application/json' -d'{"aggs": {"jobs": {"terms": {"field":"job.keyword","order":[{ "avg_salary":"desc" }]},"aggs": {"avg_salary": {"avg": {"field":"salary"}}}}}}'
如果子聚合有多值,如 stats 等指标聚合,可通过 . 进行指定具体值,示例如下:
curl -XPOST "http://localhost:9200/employees/_search?size=0" -H 'Content-Type: application/json' -d'{"aggs": {"jobs": {"terms": {"field":"job.keyword","order":[{ "stats_salary.min":"desc" }]},"aggs": {"stats_salary": {"stats": {"field":"salary"}}}}}}'
聚合原理
Elasticsearch 的聚合会先通过协调节点将聚合请求分发到多个分片上执行,每个分片返回该分片上的聚合结果给到协调节点,协调节点再对各个分片返回的结果进行统计并返回聚合结果。
这种聚合统计的方式对于 min 这种指标聚合是不会有精确度问题的,但对于 terms 聚合就不一样了。因为数据是分散在多个分片上的,协调节点无法获取数据全貌。
针对这个问题,如果数据量不大,可以设置 Primary Shard 为 1 提高准确性;如果在多分片上,我们可以通过提升 shard_size参数减少 terms 不准的影响。shard_size 的原理是从 Shard 上额外多获取数据来提升整体的准确率,但返回给客户端的数量还是以 size 为准。
在使用 terms 聚合时,在返回结果中还有两个特殊的字段:
"doc_count_error_upper_bound" : 0,"sum_other_doc_count" : 0,
- doc_count_error_upper_bound:被遗漏的 term 分桶包含的文档,有可能的最大值
- sum_other_doc_count:除了返回结果 bucket 的 terms 之外,其他 terms 的文档总数,其实就是文档总数-返回的文档总数。
假设有如下聚合统计:
从图中可以看出,实际上 D 的总数量要大于 C,但却没有被统计出来。
本次统计返回的 doc_count_error_upper_bound 的值为 7,因为在第一个分片上取前三个桶时,第三个桶的数量为 4,所在假设它被遗漏的文档数也为 4,虽然实际上第一个分片中 D 的文档数为 3,但这个值返回的是 “可能最大” 而不是 “实际最大”。第二个分片同理,所以这个值为 7。
sum_other_doc_count 的值为所有分片上的总文档数(29)减去返回的文档数(22)即为 7。
