首页 > 编程笔记 > Java笔记 阅读:1

Elasticsearch聚合查询详解(附带实例)

在实际项目中,Elasticsearch 用得多的还是聚合查询、分组查询以及结果高亮显示。

什么是聚合

聚合(Aggregation)是 Elasticsearch 非常重要的功能,它允许用户在数据上生成复杂的分析统计。它很像 SQL 中的 GROUP BY,但是比 GROUP BY 的功能更强大。

在解释什么是聚合之前,首先需要了解两个重要概念:
概括起来,聚合就是由一个或者多个桶、一个或者多个指标组合而成的数据查询统计功能。一个聚合查询可以只有一个桶或者一个指标,或者每样一个。在桶中甚至可以有多个嵌套的桶。比如,我们可以将文档按照其所属国家/地区进行分桶,再按照年龄分桶,最后对每个桶计算其平均薪资(一个指标)。

Elasticsearch实现统计查询

Elasticsearch 提供了 AggregationBuilders 类用于统计信息,比如最小值(min)、最大值(max)、总和(sum)、数量(count)、平均值(vag)等。

具体使用流程如下:
  1. 使用 QueryBuilders 创建数据查询条件。
  2. 使用 AggregationBuilders 创建统计指标。
  3. 构建 NativeSearchQuery,合并数据查询条件和统计指标。
  4. 执行 search 并输出统计结果。

下面通过示例演示 AggregationBuilders 的数据统计功能:
@Test
public void testAggregationQuery() {
    QueryBuilder queryBuilder= QueryBuilders.boolQuery()
            .must(QueryBuilders.rangeQuery("price").gte(50));
    // 1. 数量
    ValueCountAggregationBuilder countAggregationBuilder=  AggregationBuilders.count("count_id").field("id");
    // 2. 求和
    SumAggregationBuilder sumAggregationBuilder= AggregationBuilders.sum("sum_price").field("price");
    // 3. 求最小值
    MinAggregationBuilder minAggregationBuilder=AggregationBuilders.min("min_price").field("price");
    // 4. 求最大值
    MaxAggregationBuilder maxAggregationBuilder=AggregationBuilders.max("max_price").field("price");
    // 5. 求平均值
    AvgAggregationBuilder avgAggregationBuilder=AggregationBuilders.avg("avg_price").field("price");
    // 全部统计指标
       StatsAggregationBuilder statsAggregationBuilder=AggregationBuilders.stats("stats_price").field("price");
       NativeSearchQuery nativeSearchQuery=new NativeSearchQueryBuilder()
            .withQuery(queryBuilder)
            .addAggregation(countAggregationBuilder)
            .addAggregation(sumAggregationBuilder)
            .addAggregation(minAggregationBuilder)
            .addAggregation(maxAggregationBuilder)
            .addAggregation(avgAggregationBuilder)
            .addAggregation(statsAggregationBuilder).build();

    SearchHits<Book> bookSearchHits=elasticsearchRestTemplate.search(nativeSearchQuery,Book.class,IndexCoordinates.of(Book_INDEX));
    Map<String, Aggregation> aggregationMap = bookSearchHits.getAggregations().getAsMap();
    ValueCount count = (ValueCount)aggregationMap.get("count_id");
    Avg avg = (Avg)aggregationMap.get("avg_price");
    Sum sum=(Sum) aggregationMap.get("sum_price");
    Min min=(Min) aggregationMap.get("min_price");
    Max max=(Max) aggregationMap.get("max_price");
    Stats stats=(Stats) aggregationMap.get("stats_price");

    System.out.println("count:"+count.getValue());
    System.out.println("sum:"+sum.getValue());
    System.out.println("min:"+min.getValue());
    System.out.println("max:"+max.getValue());
    System.out.println("avg:"+avg.getValue());
    System.out.println("sum:"+stats.getSumAsString()+"  "+
            "min:"+stats.getMinAsString()+" "+
            "max:"+stats.getMaxAsString()+" "+
            "avg:"+stats.getAvgAsString()+" "+
            "count:"+stats.getCount());
};
单击 Run Test 或在方法上右击,选择 Run 'testAggregationQuery',运行单元测试方法,验证 StringQuery 查询的效果,运行结果如下图所示:


图 1 Aggregation 统计查询的运行结果

通过输出发现,category 字段匹配的关键字被加上高亮的样式。我们可以通过 getHighlightField 获取并处理高亮显示的字段。

Elasticsearch实现聚合查询

聚合是一些桶和指标的组合。一个聚合可以只有一个桶或者一个指标,或者每样一个。在桶中甚至可以有多个嵌套的桶。

例如,我们可以按照类别(Category)对所有的书进行分桶,然后对每个桶计算其平均价格(一个指标)。

下面通过示例验证 AggregationBuilders 的分组聚合统计功能。
@Test
public void testAggregationQuery2() {
    Query query = new NativeSearchQueryBuilder()
            .addAggregation(AggregationBuilders.terms("category").field("category.keyword"))
            .build();

    SearchHits<Book> searchHits = elasticsearchRestTemplate.search(query, Book.class);
    searchHits.getAggregations().asList().forEach(aggregation -> {
        ((Terms) aggregation).getBuckets()
                .forEach(bucket -> System.out.println("分组:"+bucket.getKeyAsString()+":"+ bucket.getDocCount()));
    });
};
单击 Run Test 或在方法上右击,选择 Run 'testAggregationQuery2',运行单元测试方法,验证分组查询功能,运行结果如下图所示:


图 2 Aggregation 聚合查询的运行结果

通过输出发现,查询结果按照 category 字段分组聚合,然后统计每一组的数量。其实与数据库的分组比较类似,都是通过某个字段分组,然后进行统计。

上面是简单计算每个分组的数量,我们也可以先聚合,再计算每个 Buckets 的最大值、最小值等统计数据。对上面的示例做一些修改:
@Test
public void testAggregationQuery3() {
    QueryBuilder queryBuilder= QueryBuilders.boolQuery()
            .must(QueryBuilders.rangeQuery("price").gte(5));

    TermsAggregationBuilder aggregationBuilder=AggregationBuilders.terms("category").field("category.keyword");

    AggregationBuilder minAggregation=AggregationBuilders.min("min_price").field("price");
    AggregationBuilder maxAggregation=AggregationBuilders.max("max_price").field("price");
    AggregationBuilder avgAggregation=AggregationBuilders.avg("avg_price").field("price");

    aggregationBuilder.subAggregation(minAggregation)
            .subAggregation(maxAggregation)
            .subAggregation(avgAggregation);
    NativeSearchQuery nativeSearchQuery=new NativeSearchQueryBuilder().withQuery(queryBuilder)
            .addAggregation(aggregationBuilder).build();

    SearchHits<Book> searchHits = elasticsearchRestTemplate.search(nativeSearchQuery, Book.class);
    Map<String, Aggregation> aggregationMap = searchHits.getAggregations().getAsMap();
    Aggregation categorys = aggregationMap.get("category");
    for (Terms.Bucket bucket: ((Terms) categorys).getBuckets()){
        Aggregations aggregations=bucket.getAggregations();
        System.out.print("bucket:"+bucket.getKeyAsString()+" ");

        Min min= aggregations.get("min_price");
        Max max= aggregations.get("max_price");
        Avg avg= aggregations.get("avg_price");
        System.out.println("min:"+min.getValue()+" "+"max:"+max.getValue()+" "+"avg:"+avg.getValue());
    }
};
在上面的示例中,通过 AggregationBuilders 的 mix、max、avg 等方法构建聚合查询的数据统计信息。

单击 Run Test 或在方法上右击,选择 Run 'testAggregationQuery3',运行单元测试方法,验证聚合统计的功能,运行结果如下图所示:


图 3 Aggregation聚合统计查询的运行结果

通过输出发现,查询结果按照 category 字段分组聚合,然后统计每个分组价格的最大值、最小值和平均值。

相关文章