介绍
聚合(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;
@Test
void test() {
System.out.println("client =" + client);
}
@Test
void 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获取buckets
List<? extends Terms.Bucket> buckets = brandTerms.getBuckets();
// 4.3遍历获取每一个bucket
for (Terms.Bucket bucket : buckets) {
System.out.println("price:" + bucket.getKeyAsString());
System.out.println("count:" + bucket.getDocCount());
}
}
@BeforeEach
void setup() {
client = new RestHighLevelClient(RestClient.builder(
HttpHost.create("http://192.168.1.97:9200")
));
}
@AfterEach
void tearDown() throws IOException {
if (client != null) {
client.close();
}
}
}