介绍
聚合(aggregations
)可以让我们极其方便的实现对数据的统计、分析、运算。
例如:
- 什么品牌的手机最受欢迎?
- 这些手机的平均价格、最高价格、最低价格?
- 这些手机每月的销售情况如何?
- 实现这些统计功能的比数据库的sql要方便的多,而且查询速度非常快,可以实现近实时搜索效果。
- 官方文档:https://www.elastic.co/guide/en/elasticsearch/reference/7.12/search-aggregations.html
- 聚合常见的有三类:
- 桶(
Bucket
)聚合:用来对文档做分组
-
TermAggregation
:按照文档字段值分组,例如按照品牌值分组、按照国家分组Date Histogram
:按照日期阶梯分组,例如一周为一组,或者一月为一组
- 度量(
Metric
)聚合:用以计算一些值,比如:最大值、最小值、平均值等
-
Avg
:求平均值Max
:求最大值Min
:求最小值Stats
:同时求max
、min
、avg
、sum
等
- 管道(
pipeline
)聚合:其它聚合的结果为基础做进一步运算
- 注意:参加聚合的字段必须是keyword、日期、数值、布尔类型的字段
DSL聚合
Bucket聚合
例如我们要统计所有商品中共有哪些商品分类,其实就是以分类(category)字段对数据分组。category值一样的放在同一组,属于Bucket
聚合中的Term
聚合。
基本语法如下:
示例:
GET /items/_search
{"size": 0, "aggs": {"category_agg": {"terms": {"field": "category","size": 20}},# 如果需要多个字段, 继续添加"brand_agg": {"terms": {"field": "brand","size": 10}}}
}
结果:
带条件聚合
默认情况下,Bucket聚合是对索引库的所有文档做聚合,可以限定要聚合的文档范围,添加query条件即可
例如,我想知道价格高于3000元的手机品牌有哪些:
- 搜索查询条件:
-
- 价格高于3000
- 必须是手机
- 聚合目标:统计的是品牌,肯定是对brand字段做term聚合
语法如下:
聚合结果:
{"took" : 2,"timed_out" : false,"hits" : {"total" : {"value" : 13,"relation" : "eq"},"max_score" : null,"hits" : [ ]},"aggregations" : {"brand_agg" : {"doc_count_error_upper_bound" : 0,"sum_other_doc_count" : 0,"buckets" : [{"key" : "华为","doc_count" : 7},{"key" : "Apple","doc_count" : 5},{"key" : "小米","doc_count" : 1}]}}
}
可以看到,结果中只剩下3个品牌了。
Metric聚合
除了对数据分组(Bucket)以外,我们还可以对每个Bucket内的数据进一步做数据计算和统计
例如: 我想知道手机有哪些品牌,每个品牌的价格最小值、最大值、平均值。
这就要用到Metric
聚合了,例如stat
聚合,就可以同时获取min
、max
、avg
等结果。
语法如下:
query
部分就不说了,我们重点解读聚合部分语法。- 可以看到我们在
brand_agg
聚合的内部,我们新加了一个aggs
参数。这个聚合就是brand_agg
的子聚合,会对brand_agg
形成的每个桶中的文档分别统计。
stats_meric
:聚合名称stats
:聚合类型,stats是metric
聚合的一种field
:聚合字段,这里选择price
,统计价格
- 由于stats是对brand_agg形成的每个品牌桶内文档分别做统计,因此每个品牌都会统计出自己的价格最小、最大、平均值。
- 结果如下:
- 另外,我们还可以让聚合按照每个品牌的价格平均值排序:
总结
aggs代表聚合,与query同级,此时query的作用是?
- 限定聚合的的文档范围
聚合必须的三要素:
- 聚合名称
- 聚合类型
- 聚合字段
聚合可配置属性有:
- size:指定聚合结果数量
- order:指定聚合结果排序方式
- field:指定聚合字段
RestClient聚合
可以看到在DSL中,aggs
聚合条件与query
条件是同一级别,都属于查询JSON参数。因此依然是利用request.source()
方法来设置。
不过聚合条件的要利用AggregationBuilders
这个工具类来构造。DSL与JavaAPI的语法对比如下:
我们以品牌聚合为例:
聚合结果与搜索文档同一级别,因此需要单独获取和解析。具体解析语法如下:
完整代码如下:
public class ElasticSearchTest {private RestHighLevelClient client;@Testvoid test() {System.out.println("client =" + client);}@Testvoid testAgg() throws IOException {//1.创建request对象SearchRequest request = new SearchRequest("items");//2.组织DSL条件//2.1分页参数, 设置0不返回文档数据request.source().size(0);//2.2.聚合条件request.source().aggregation(AggregationBuilders.terms("brandAgg").field("price").size(10));//3.发送请求SearchResponse response = client.search(request, RequestOptions.DEFAULT);//4.解析结果System.out.println("response = " + response);Aggregations aggregations = response.getAggregations();// 4.1根据聚合名称获取对应的聚合Terms brandTerms = aggregations.get("brandAgg");// 4.2获取bucketsList<? extends Terms.Bucket> buckets = brandTerms.getBuckets();// 4.3遍历获取每一个bucketfor (Terms.Bucket bucket : buckets) {System.out.println("price:" + bucket.getKeyAsString());System.out.println("count:" + bucket.getDocCount());}}@BeforeEachvoid setup() {client = new RestHighLevelClient(RestClient.builder(HttpHost.create("http://192.168.1.97:9200")));}@AfterEachvoid tearDown() throws IOException {if (client != null) {client.close();}}
}