当前位置: 首页 > news >正文

ElasticSearch聚合查询Restful语法和JavaApi详解(基于ES7.6)

本文收录于github和gitee ,里面有我完整的Java系列文章,学习或面试都可以看看

(一)概述

在前面关于ES的一系列文章中,已经介绍了ES的概念、常用操作、JavaAPI以及实际的一个小demo,但是在真实的应用场景中,还有可能会有更高阶的一些用法,今天主要介绍两种相对来说会更难一些的操作,聚合查询。该文档基于ElasticSearch7.6,将介绍restful查询语法以及JavaApi。

阅读本文需要你有ElasticSearch的基础​。

(二)前期数据准备

这里准备了包含姓名、年龄、教室、性别和成绩五个字段的数据

PUT /test4
{
    "mappings" : {
      "properties" : {
        "name" : {
          "type" : "text"
        },
        "age":{
          "type": "integer"
        },
        "classroom":{
          "type": "keyword"
        },
        "gender":{
          "type": "keyword"
        },
        "grade":{
          "type": "integer"
        }
      }
    }
}
PUT /test4/_bulk
{"index": {"_id": 1}}
{"name":"张三","age":18,"classroom":"1","gender":"男","grade":80}
{"index": {"_id": 2}}
{"name":"李四","age":20,"classroom":"2","gender":"男","grade":60}
{"index": {"_id": 3}}
{"name":"王五","age":20,"classroom":"2","gender":"女","grade":70}
{"index": {"_id": 4}}
{"name":"赵六","age":19,"classroom":"1","gender":"女","grade":90}
{"index": {"_id": 5}}
{"name":"毛七","age":20,"classroom":"1","gender":"男","grade":90}

(三)聚合查询

ES中的聚合操作提供了强大的分组及数理计算的能力,ES中聚合从大体上可以分为四种方式:

1、Metrics Aggregation 提供了诸如Max,Min,Avg的数值计算能力

2、Bucket Aggregation 提供了分桶的能力,简单来讲就是将一类相同的数据聚合到一起

3、Pipeline Aggregation 管道聚合,对其他聚合进行二次聚合

4、Matrix Aggregation 对多个字段进行操作并返回矩阵结果

ES官网提供了全部聚合查询文档,这篇文章将介绍常用的几种聚合查询的语法以及JavaApi:

https://www.elastic.co/guide/en/elasticsearch/client/java-rest/7.6/java-rest-high-aggregation-builders.html#_metrics_aggregations

(四)Metrics Aggregation

4.1 AVG

avg用于计算聚合文档中提取的数值的平均值,restful查询语法如下:

POST /test4/_search
{
  "aggs": {
    "avg_grade": {
      "avg": {
        "field": "grade"
      }
    }
  }
}

查询得到的结果如下:

接着是JavaApi,核心在于使用AggregationBuilders的avg方法,第七行代码对应于上面的操作。

@Test
public void testAvg() throws Exception {
    //封装了获取RestHighLevelClient的方法
    RestHighLevelClient client=ElasticSearchClient.getClient();
    SearchRequest request = new SearchRequest("test4");
    SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
    searchSourceBuilder.aggregation(AggregationBuilders.avg("agg_grade").field("grade")).size(0);
    request.source(searchSourceBuilder);
    SearchResponse search = client.search(request, RequestOptions.DEFAULT);
    //注意这里要把Aggregation类型转化为ParsedAvg类型
    ParsedAvg aggregation = search.getAggregations().get("agg_grade");
    System.out.println(aggregation.getValue()); //返回78.0
}

接下来就直接贴代码了

4.2 Min

获取聚合数据的最小值:

POST /test4/_search
{
  "aggs": {
    "min_grade": {
      "min": {
        "field": "grade"
      }
    }
  }
}
@Test
public void testMin() throws Exception {
    //封装了获取RestHighLevelClient的方法
    RestHighLevelClient client=ElasticSearchClient.getClient();
    SearchRequest request = new SearchRequest("test4");
    SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
    searchSourceBuilder.aggregation(AggregationBuilders.min("min_grade").field("grade")).size(0);
    request.source(searchSourceBuilder);
    SearchResponse search = client.search(request, RequestOptions.DEFAULT);
    ParsedMin aggregation = search.getAggregations().get("min_grade");
    System.out.println(aggregation.getValue());
}

4.3 Max

获取聚合数据的最大值:

POST /test4/_search
{
  "aggs": {
    "max_grade": {
      "max": {
        "field": "grade"
      }
    }
  }
}
@Test
public void testMax() throws Exception {
    //封装了获取RestHighLevelClient的方法
    RestHighLevelClient client=ElasticSearchClient.getClient();
    SearchRequest request = new SearchRequest("test4");
    SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
    searchSourceBuilder.aggregation(AggregationBuilders.max("max_grade").field("grade")).size(0);
    request.source(searchSourceBuilder);
    SearchResponse search = client.search(request, RequestOptions.DEFAULT);
    ParsedMax aggregation = search.getAggregations().get("max_grade");
    System.out.println(aggregation.getValue());
}

4.4 Sum

获取聚合数据的和:

POST /test4/_search
{
  "aggs": {
    "sum_grade": {
      "sum": {
        "field": "grade"
      }
    }
  }
}
@Test
public void testSum() throws Exception {
    //封装了获取RestHighLevelClient的方法
    RestHighLevelClient client=ElasticSearchClient.getClient();
    SearchRequest request = new SearchRequest("test4");
    SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
    searchSourceBuilder.aggregation(AggregationBuilders.sum("sum_grade").field("grade")).size(0);
    request.source(searchSourceBuilder);
    SearchResponse search = client.search(request, RequestOptions.DEFAULT);
    ParsedSum aggregation = search.getAggregations().get("sum_grade");
    System.out.println(aggregation.getValue());
}

4.5 Stats

stats集成了上面的所有计算操作。

POST /test4/_search
{
  "aggs": {
    "stats_grade": {
      "stats": {
        "field": "grade"
      }
    }
  }
}
@Test
public void testStats() throws Exception {
    //封装了获取RestHighLevelClient的方法
    RestHighLevelClient client=ElasticSearchClient.getClient();
    SearchRequest request = new SearchRequest("test4");
    SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
    searchSourceBuilder.aggregation(AggregationBuilders.stats("sum_grade").field("grade")).size(0);
    request.source(searchSourceBuilder);
    SearchResponse search = client.search(request, RequestOptions.DEFAULT);
    ParsedStats aggregation = search.getAggregations().get("sum_grade");
    System.out.println(aggregation.getMax());
    System.out.println(aggregation.getAvg());
    System.out.println(aggregation.getCount());
    System.out.println(aggregation.getMin());
    System.out.println(aggregation.getSum());
}

(五)Bucket Aggregation

桶聚合是按照某个字段将同类型的数据聚合为一类,最常用对桶聚合就是terms聚合了。

5.1 terms

terms查询类似于group by,返回查询字段分组后的值以及数量,比如我对classroom字段terms查询

POST /test4/_search
{
  "aggs": {
    "classroom_term": {
      "terms": {
        "field": "classroom"
      }
    }
  }
}

返回值就是classroom的分组后的值以及每个组的数量:classroom是1的有3条记录,classroom是2的有2条记录

"aggregations" : {
    "classroom_term" : {
      "doc_count_error_upper_bound" : 0,
      "sum_other_doc_count" : 0,
      "buckets" : [
        {
          "key" : "1",
          "doc_count" : 3
        },
        {
          "key" : "2",
          "doc_count" : 2
        }
      ]
    }

我们也可以对多个字段进行terms分组,比如我现在对classroom和gender两个字段进行分组:

POST /test4/_search
{
  "aggs": {
    "classroom_term": {
      "terms": {
        "field": "classroom"
      },
      "aggs": {
        "gender": {
          "terms": {
            "field": "gender"
          }
        }
      }
    }
  }
}

最后对返回值就是classroom和gender分组后的值和数量:

classroom是1,gender是男有两条;

classroom是1,gender是女有一条;

classroom是2,gender是男有一条;

classroom是2,gender是女有一条;

"aggregations" : {
    "classroom_term" : {
      "doc_count_error_upper_bound" : 0,
      "sum_other_doc_count" : 0,
      "buckets" : [
        {
          "key" : "1",
          "doc_count" : 3,
          "gender" : {
            "doc_count_error_upper_bound" : 0,
            "sum_other_doc_count" : 0,
            "buckets" : [
              {
                "key" : "男",
                "doc_count" : 2
              },
              {
                "key" : "女",
                "doc_count" : 1
              }
            ]
          }
        },
        {
          "key" : "2",
          "doc_count" : 2,
          "gender" : {
            "doc_count_error_upper_bound" : 0,
            "sum_other_doc_count" : 0,
            "buckets" : [
              {
                "key" : "女",
                "doc_count" : 1
              },
              {
                "key" : "男",
                "doc_count" : 1
              }
            ]
          }
        }
      ]
    }

对应的JavaApi使用如下:

@Test
public void testTerms() throws Exception {
    //封装了获取RestHighLevelClient的方法
    RestHighLevelClient client=ElasticSearchClient.getClient();
    SearchRequest request = new SearchRequest("test4");
    SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
    searchSourceBuilder.aggregation(AggregationBuilders.terms("classroom_term").field("classroom")
                        .subAggregation(AggregationBuilders.terms("gender").field("gender")));
    request.source(searchSourceBuilder);
    SearchResponse search = client.search(request, RequestOptions.DEFAULT);
    //获取数据时首先对classroom分桶,再对gender分桶
    Terms classroomTerm = search.getAggregations().get("classroom_term");
    for(Terms.Bucket classroomBucket:classroomTerm.getBuckets()){
        Terms genderTerm=classroomBucket.getAggregations().get("gender");
        for (Terms.Bucket genderBucket:genderTerm.getBuckets()){
            System.out.println("classRoom:"+classroomBucket.getKeyAsString()+"gender:"+genderBucket.getKeyAsString()+"count:"+genderBucket.getDocCount());
        }
    }
}

这里比较难理解对是获取数据时的处理,聚合查询时有个桶的概念,在获取数据时需要遍历获取桶,以上面的代码为例,先获取到classroom的桶,再遍历classroom的桶获取gender的桶,从桶中获取到具体的内容。看下图:

5.2 range

range查询可以统计出每个数据区间内的数量:比如我要统计分数为*~70,70~85,80~*的数据,就可以通过下面的方式:

POST /test4/_search
{
  "aggs": {
    "grade_range": {
      "range": {
        "field": "grade",
        "ranges": [
          {"to":70},
          {"from":70,"to":85},
          {"from":85}
        ]
      }
    }
  }
}

JavaAPI如下:

@Test
public void testRange() throws Exception {
    //封装了获取RestHighLevelClient的方法
    RestHighLevelClient client=ElasticSearchClient.getClient();
    SearchRequest request = new SearchRequest("test4");
    SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
    searchSourceBuilder.aggregation(AggregationBuilders.range("grade_range").field("grade")
                                    .addUnboundedTo(70).addRange(70,85).addUnboundedFrom(85));
    request.source(searchSourceBuilder);
    SearchResponse search = client.search(request, RequestOptions.DEFAULT);
    //获取数据时首先对classroom分桶,再对gender分桶
    Range gradeRange = search.getAggregations().get("grade_range");
    for(Range.Bucket gradeBucket:gradeRange.getBuckets()){
        System.out.println("key:"+gradeBucket.getKey()+"count:"+gradeBucket.getDocCount());
    }
}

(六)总结

至此,关于ES的聚合查询一些常用方法就讲解完毕了,ES提供的其他更多方法可以直接在官方文档中看,讲解的十分详细。我是鱼仔,我们下期再见!

相关文章:

  • 《偷影子的人》
  • 模板方法设计模式理论与应用
  • 跟我一起数据挖掘(13)——矩阵分解
  • 用了MybatisPlus后,我很久没有手写sql了
  • 两张图让你快速读懂JVM字节码指令
  • Android Bitmap面面观
  • 用几张图深度剖析Java运行时数据区
  • HDOJ(HDU) 2519 新生晚会(组合公式)
  • 从JVM角度思考--如何预估线上环境机器资源大小
  • 1到3年的Java开发工程师应该如何准备面试
  • 写了两年代码之后再来看看Spring中的Bean
  • 【Python之旅】第二篇(四):字典
  • 使用Optional更优雅地处理非空判断
  • 你能保证你的代码没有异常吗?
  • OC之NSString/NSMutableString
  • css属性的继承、初识值、计算值、当前值、应用值
  • C语言笔记(第一章:C语言编程)
  • IDEA常用插件整理
  • iOS筛选菜单、分段选择器、导航栏、悬浮窗、转场动画、启动视频等源码
  • Mac转Windows的拯救指南
  • mockjs让前端开发独立于后端
  • mysql innodb 索引使用指南
  • PHP的Ev教程三(Periodic watcher)
  • php的插入排序,通过双层for循环
  • Python_网络编程
  • 短视频宝贝=慢?阿里巴巴工程师这样秒开短视频
  • 关于for循环的简单归纳
  • 观察者模式实现非直接耦合
  • 来,膜拜下android roadmap,强大的执行力
  • 力扣(LeetCode)21
  • 排序算法学习笔记
  • 前端_面试
  • 如何胜任知名企业的商业数据分析师?
  • 提醒我喝水chrome插件开发指南
  • 小程序开发中的那些坑
  • FaaS 的简单实践
  • #我与Java虚拟机的故事#连载05:Java虚拟机的修炼之道
  • %3cscript放入php,跟bWAPP学WEB安全(PHP代码)--XSS跨站脚本攻击
  • (16)UiBot:智能化软件机器人(以头歌抓取课程数据为例)
  • (30)数组元素和与数字和的绝对差
  • (Repost) Getting Genode with TrustZone on the i.MX
  • (附源码)spring boot建达集团公司平台 毕业设计 141538
  • (附源码)小程序 交通违法举报系统 毕业设计 242045
  • (汇总)os模块以及shutil模块对文件的操作
  • (接口自动化)Python3操作MySQL数据库
  • (七)理解angular中的module和injector,即依赖注入
  • (收藏)Git和Repo扫盲——如何取得Android源代码
  • (一)Neo4j下载安装以及初次使用
  • (原创)boost.property_tree解析xml的帮助类以及中文解析问题的解决
  • (转)C语言家族扩展收藏 (转)C语言家族扩展
  • (转)Groupon前传:从10个月的失败作品修改,1个月找到成功
  • (转)scrum常见工具列表
  • ******IT公司面试题汇总+优秀技术博客汇总
  • .NET Core WebAPI中使用Log4net 日志级别分类并记录到数据库
  • .Net 代码性能 - (1)