【ElasticSearch】数据聚合语法与Java实现

news2024/11/17 3:34:04

文章目录

  • 1、聚合的分类
  • 2、DSL实现bucket聚合
  • 3、DSL实现Metrics 聚合
  • 4、RestClient实现聚合
  • 5、需求:返回过滤条件的信息
  • 6、带过滤条件的聚合

1、聚合的分类

聚合(aggregations)可以实现对文档数据的统计、分析、运算。(类比MySQL的聚合函数)ES聚合常见的有三类:

  • 桶(Bucket)聚合:用来对文档做分组
常用:
TermAggregation:按照文档字段值分组
Date Histogram:按照日期阶梯分组,例如一周为一组,或者一月为一组

在这里插入图片描述

  • 度量(Metric)聚合:用以计算一些值,比如:最大值、最小值、平均值等
常用:
Avg:求平均值
Max:求最大值
Min:求最小值
Stats:同时求max、min、avg、sum等

  • 管道(pipeline)聚合:基于其它聚合的结果为基础做聚合

根据这些分类的介绍,可以得出,参与聚合的字段类型必须是:

keyword
数值
日期
布尔

2、DSL实现bucket聚合

DSL写bucket聚合,主要有三要素:

  • 聚合名称
  • 聚合类型
  • 聚合字段

语法

统计所有数据中的酒店品牌有几种,此时可以根据酒店品牌的名称做聚合

GET /hotel/_search
{
  "size": 0,  // 设置size为0,结果中不包含文档,只包含聚合结果
  "aggs": { // 定义聚合
    "brandAgg": { //给聚合起个名字
      "terms": { // 聚合的类型,按照品牌值聚合,所以选择term
        "field": "brand", // 参与聚合的字段
        "size": 20 // 希望获取的聚合结果数量
      }
    }
  }
}

在这里插入图片描述

Bucket聚合-聚合结果排序

默认情况下,Bucket聚合会统计Bucket内的文档数量,记为_count,并且按照_count降序排序。想修改排序方式就加order字段:

GET /hotel/_search
{
  "size": 0, 
  "aggs": {
    "brandAgg": {
      "terms": {
        "field": "brand",
        "order": {
          "_count": "asc" // 按照_count升序排列
        },
        "size": 20
      }
    }
  }
}

在这里插入图片描述

Bucket聚合-限定聚合范围

默认情况下,Bucket聚合是对索引库的所有文档做聚合,想修改这个范围可以加query条件,query与aggs同级:

GET /hotel/_search
{
  "query": {
    "range": {
      "price": {
        "lte": 200 // 只对200元以下的文档聚合
      }
    }
  }, 
  "size": 0, 
  "aggs": {
    "brandAgg": {
      "terms": {
        "field": "brand",
        "size": 20
      }
    }
  }
}

在这里插入图片描述
小结:

在这里插入图片描述

3、DSL实现Metrics 聚合

例:获取每个品牌的用户评分的min、max、avg等值.

首先确定用Metrics的stats聚合,其次是对每个品牌来求min、max,则要先按品牌bucket聚合:

GET /hotel/_search
{
  "size": 0, 
  "aggs": {
    "brandAgg": { 
      "terms": { 
        "field": "brand", 
        "size": 20
      },
      "aggs": { // 是上面brands聚合的子聚合,也就是分组后对每组分别计算,聚合的嵌套!
        "score_stats": { // 聚合名称
          "stats": { // 聚合类型,这里stats可以计算min、max、avg等
            "field": "score" // 聚合字段,这里是score
          }
        }
      }
    }
  }
}

在这里插入图片描述

4、RestClient实现聚合

对比DSL语句,看JavaRestClient发送聚合请求:

在这里插入图片描述
UT里试试:

在这里插入图片描述

再进行响应结果处理:

在这里插入图片描述
测试代码:

@Test
void testBucket() throws IOException {
    SearchRequest request = new SearchRequest("hotel");
    request.source().size(0);
    request.source().aggregation(AggregationBuilders
            .terms("brandAgg")
            .field("brand")
            .size(20));
    SearchResponse response = client.search(request, RequestOptions.DEFAULT);
    Aggregations aggregations = response.getAggregations();
    Terms brandTerms = aggregations.get("brandAgg");
    List<? extends Terms.Bucket> buckets = brandTerms.getBuckets();
    for (Terms.Bucket bucket : buckets) {
        String brandName = bucket.getKeyAsString();
        System.out.println(brandName);
    }

}

在这里插入图片描述

5、需求:返回过滤条件的信息

搜索页面的品牌、城市等信息不应该是在前端页面写死,而是通过聚合索引库中的酒店数据得来的:
在这里插入图片描述
看到这个格式,最先想到的数据格式应该是Map

/**
 * 查询城市、星级、品牌的聚合结果  
 * * @return 聚合结果,格式:{"城市": ["上海", "北京"], "品牌": ["如家", "希尔顿"]} 
*/
Map<String, List<String>> filters();

接下来开始写Service层实现:

public interface IHotelService extends IService<Hotel> {

    Map<String, List<String>> filters();

}

实现这个接口:

@Service
public class HotelService extends ServiceImpl<HotelMapper, Hotel> implements IHotelService {

    @Resource
    RestHighLevelClient client;

    @Override
    public Map<String, List<String>> filters() {
        try {
            SearchRequest request = new SearchRequest("hotel");
            //聚合
            buildAggregation(request);
            SearchResponse response = client.search(request,RequestOptions.DEFAULT);
            //获取总的聚合结果
            Aggregations aggregations = response.getAggregations();
            //获取品牌聚合信息
            List<String> brandFilterList = getAggByName(aggregations, "brandAgg");
            Map<String,List<String>> filterMap = new HashMap<>();
            filterMap.put("品牌",brandFilterList);
            //获取城市聚合信息
            List<String> cityFilterList = getAggByName(aggregations,"cityAgg");
            filterMap.put("城市",cityFilterList);
            //获取星级聚合信息
            List<String> startFilterList = getAggByName(aggregations,"starNameAgg");
            filterMap.put("星级",startFilterList);
            return filterMap;
        } catch (IOException e) {
            throw new RuntimeException();
        }
    }

    /**
     * 聚合DSL拼接
     * @param request
     */
    private void buildAggregation(SearchRequest request) {
        request.source().size(0);
        request.source().aggregation(AggregationBuilders
                .terms("brandAgg")
                .field("brand")
                .size(100)
        );
        request.source().aggregation(AggregationBuilders
                .terms("cityAgg")
                .field("city")
                .size(100)
        );
        request.source().aggregation(AggregationBuilders
                .terms("starNameAgg")
                .field("starName")
                .size(100)
        );
    }

    /**
     * 聚合结果解析
     * @param aggregations 聚合结果
     * @param name AggName
     * @return
     */
    private List<String> getAggByName(Aggregations aggregations,String name) {
        Terms brandTerms = aggregations.get(name);
        List<? extends Terms.Bucket> buckets = brandTerms.getBuckets();
        List<String> filterList = buckets.stream()
                .map(t -> {
                    String brandKey = t.getKeyAsString();
                    return brandKey;
                }).collect(Collectors.toList());
        return filterList;

    }
}

上面的client这个Bean,在启动类中进行处理:

@SpringBootApplication
public class HotelDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(HotelDemoApplication.class, args);
    }

    @Bean
    public RestHighLevelClient client(){
        return new RestHighLevelClient(RestClient.builder(
                HttpHost.create("http://10.4.130.220:9200")
        ));
    }

}


单元测试看下效果:

@SpringBootTest
class HotelDemoApplicationTests {

    @Resource
    IHotelService hotelService;

    @Test
    void testFilter(){
        Map<String, List<String>> filters = hotelService.filters();
        System.out.println(filters);
    }

}

跑下单元测试:

在这里插入图片描述

6、带过滤条件的聚合

还是上面的需求,看下项目中前端页面的传参:

在这里插入图片描述
这里获取筛选信息的接口有传参,是因为,如果筛选项是对索引库全部数据进行聚合,就会出现这个场景;

在这里插入图片描述
即,我已经搜了上海虹桥了,但底下的城市过滤项还有其他城市,因此要对聚合加过滤条件。

  • 接参dto类:
@Data
public class RequestParam {

    private String key;

    private Integer page;  //pageNum

    private Integer size;  //pageSize

    private String sortBy;

    private String brand;

    private String starName;

    private String city;

    private Integer minPrice;

    private Integer maxPrice;

    private String location;
}
  • 定义接口
@RestController
@RequestMapping("/hotel")
public class HotelSearchController {

    @Resource
    IHotelService hotelService;

    @PostMapping("/filters")
    public Map<String, List<String>> getFiltersOption(@RequestBody RequestParam requestParam){
        return hotelService.filters(requestParam);
    }
}
  • Service接口
public interface IHotelService extends IService<Hotel> {
    Map<String, List<String>> filters(RequestParam requestParam);
}
  • 接口实现,新加形参和过滤条件,先过滤再聚合
@Service
public class HotelService extends ServiceImpl<HotelMapper, Hotel> implements IHotelService {

    @Resource
    RestHighLevelClient client;

	//过滤条件DSL构建
    private void buildBasicQuery(RequestParam requestParam, SearchRequest request) {
        //BoolQuery原始查询条件,原始算分
        BoolQueryBuilder booleanQuery = QueryBuilders.boolQuery();
        //关键字
        String key = requestParam.getKey();
        if (!isEmpty(key)) {
            booleanQuery.must(QueryBuilders.matchQuery("all", key));
        } else {
            booleanQuery.must(QueryBuilders.matchAllQuery());
        }
        //城市
        if (!isEmpty(requestParam.getCity())) {
            booleanQuery.filter(QueryBuilders.termQuery("city", requestParam.getCity()));
        }
        //品牌
        if (!isEmpty(requestParam.getBrand())) {
            booleanQuery.filter(QueryBuilders.termQuery("brand", requestParam.getBrand()));
        }
        //星级
        if (!isEmpty(requestParam.getStarName())) {
            booleanQuery.filter(QueryBuilders.termQuery("startName", requestParam.getStarName()));
        }
        //价格
        if (requestParam.getMaxPrice() != null && requestParam.getMinPrice() != null) {
            booleanQuery.filter(QueryBuilders.rangeQuery("price")
                    .lte(requestParam.getMaxPrice())
                    .gte(requestParam.getMinPrice()));
        }

        //function score算分控制
        FunctionScoreQueryBuilder functionScoreQuery = QueryBuilders.functionScoreQuery(
                booleanQuery,  //第一个参数传入booleanQuery为原始查询,对应原始的相关性算分
                new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{  //第二个形参,function score数组,里面有个function score元素
                        new FunctionScoreQueryBuilder.FilterFunctionBuilder(  //function score元素对象,第一个参数传入筛选字段
                                QueryBuilders.termQuery("isAD", true),   //不再用酒店品牌筛选,而是isAD字段
                                ScoreFunctionBuilders.weightFactorFunction(10)  //算分函数,用默认的乘法,权重为10
                        )
                });
        request.source().query(functionScoreQuery);


    }

    @Override
    public Map<String, List<String>> filters(RequestParam requestParam) {
        try {
            SearchRequest request = new SearchRequest("hotel");
            //复合查询过滤
            buildBasicQuery(requestParam,request);
            //对过滤后的数据聚合
            buildAggregation(request);
            SearchResponse response = client.search(request,RequestOptions.DEFAULT);
            //获取总的聚合结果
            Aggregations aggregations = response.getAggregations();
            //获取品牌聚合信息
            List<String> brandFilterList = getAggByName(aggregations, "brandAgg");
            Map<String,List<String>> filterMap = new HashMap<>();
            filterMap.put("品牌",brandFilterList);
            //获取城市聚合信息
            List<String> cityFilterList = getAggByName(aggregations,"cityAgg");
            filterMap.put("城市",cityFilterList);
            //获取星级聚合信息
            List<String> startFilterList = getAggByName(aggregations,"starNameAgg");
            filterMap.put("星级",startFilterList);
            return filterMap;
        } catch (IOException e) {
            throw new RuntimeException();
        }
    }

    /**
     * 聚合DSL拼接
     * @param request
     */
    private void buildAggregation(SearchRequest request) {
        request.source().size(0);
        request.source().aggregation(AggregationBuilders
                .terms("brandAgg")
                .field("brand")
                .size(100)
        );
        request.source().aggregation(AggregationBuilders
                .terms("cityAgg")
                .field("city")
                .size(100)
        );
        request.source().aggregation(AggregationBuilders
                .terms("starNameAgg")
                .field("starName")
                .size(100)
        );
    }

    /**
     * 聚合结果解析
     * @param aggregations 聚合结果
     * @param name AggName
     * @return
     */
    private List<String> getAggByName(Aggregations aggregations,String name) {
        Terms brandTerms = aggregations.get(name);
        List<? extends Terms.Bucket> buckets = brandTerms.getBuckets();
        List<String> filterList = buckets.stream()
                .map(t -> {
                    String brandKey = t.getKeyAsString();
                    return brandKey;
                }).collect(Collectors.toList());
        return filterList;

    }
}


看下效果:

在这里插入图片描述

最后,代码有点长,以上这个需求的底层DSL其实就是这个:

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/734609.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

如何记牢托福口语考试的关键词?

一般情况下&#xff0c;托福独立口语一类问题是自由回答间题(Free-choice Response)&#xff0c;如&#xff1a;If you could have any job in the world, what would it be? Use details to support your. response;另一类是选择类问题(Paired-choice Response)&#xff0c;如…

BERT论文解读及实现(一)

BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding 1 论文解读 1.1 模型概览 There are two steps in our framework: pre-training and fine-tuning. bert由预训练模型微调模型组成。 ① pre-training, the model is trained on unlabele…

前端Vue入门-day01

(创作不易&#xff0c;感谢有你&#xff0c;你的支持&#xff0c;就是我前行的最大动力&#xff0c;如果看完对你有帮助&#xff0c;请留下您的足迹&#xff09; 目录 Vue 快速上手 Vue 概念 创建实例 插值表达式 响应式特性 开发者工具 Vue 指令 v-show v-if …

【Spring Boot】第一个Spring Boot项目:helloworld

第一个Spring Boot项目&#xff1a;helloworld 本节从简单的helloworld程序开始介绍创建Spring Boot项目的方法和流程&#xff0c;以及Spring Boot项目结构&#xff0c;最后介绍项目中非常重要的pom.xml文件。 1.创建Spring Boot项目 有两种方式来构建Spring Boot项目的基础…

【1++的C++初阶】之string

&#x1f44d;作者主页&#xff1a;进击的1 &#x1f929; 专栏链接&#xff1a;【1的C初阶】 文章目录 一&#xff0c;浅谈string类二&#xff0c;string 类常用接口2.1 string的构造2.2 string类对象的容量操作2.3 string类对象的访问及遍历操作2.4 string类对象的修改操作2.…

Python 有趣的模块之pynupt——通过pynput控制鼠标和键盘

Python 有趣的模块之pynupt ——通过pynput控制鼠标和键盘 文章目录 Python 有趣的模块之pynupt ——通过pynput控制鼠标和键盘1️⃣简介2️⃣鼠标控制与移动3️⃣键盘控制与输入4️⃣结语&#x1f4e2; 1️⃣简介 &#x1f680;&#x1f680;&#x1f680;学会控制鼠标和键盘是…

Mongodb连接数据库

1.初始化 npm init 2.安装mongoose npm i mongoose 3.导入mongoose const mongooserequire("mongoose") 4.连接mongodb服务 mongoose.connect("mongodb://127.0.0.1:27017/user") 说明&#xff1a;mongodb是协议,user是数据库&#xff0c;如果没有会自动创…

经OPA运放后,读取电压出错

问题&#xff1a; 在焊接完两块板子上传程序测试时&#xff0c;程序上传完成&#xff0c;有一块板子在使用OPA读取电压时&#xff0c;在未插入电阻情况下&#xff0c;电压读取是正确的&#xff0c;在插入50K电压后&#xff0c;电压值应该是之前的两倍&#xff0c;但是电压变化…

unittest单元测试2

目录 unittest框架解析 构建测试套件 用例的执行顺序 unittest断言 HTML报告生成 异常捕捉与错误截图 数据驱动 &#x1f381;更多干货 完整版文档下载方式&#xff1a; unittest框架解析 unittest 是python 的单元测试框架&#xff0c;unittest 单元测试提供了创建测…

怎么把CAJ转换成PDF文件格式?分享这两个方法!

随着互联网的发展&#xff0c;中国知网(CNKI)已成为许多学术研究人员和学生们获取文献资料的重要来源。在CNKI上&#xff0c;常见的文件格式是CAJ&#xff08;China Academic Journals&#xff09;。然而&#xff0c;由于个人喜好或特定需求&#xff0c;我们有时会希望将这些CA…

PDF文档转化为HTML网页格式怎么操作?分享这三个方法给大家!

PDF文档作为一种常见的文档格式&#xff0c;广泛应用于各个领域。然而&#xff0c;如果您想将PDF文档直接发布到网站上&#xff0c;或是想在网页上进行展示&#xff0c;您可能需要将PDF转化为HTML格式。在此&#xff0c;我为大家介绍三种将PDF转化为HTML格式的方法。 方法一&am…

mysql语句练习题,创建表create ,枚举中文字符集设置,修改(update)

作业&#xff1a; 1.创建表&#xff1a; 创建员工表employee&#xff0c;字段如下&#xff1a; id&#xff08;员工编号&#xff09;&#xff0c;name&#xff08;员工名字&#xff09;&#xff0c;gender&#xff08;员工性别&#xff09;&#xff0c;salary&#xff08;员工薪…

d3dx9_43.dll丢失怎么解决(分享三个解决方法)

d3dx9_43.dll是一个Microsoft DirectX的动态链接库文件&#xff0c;它包含了一系列用于图形、音频和输入的功能和接口。它是DirectX 9的一部分&#xff0c;用于提供游戏和其他图形应用程序所需的图形和声音效果。如果计算机中d3dx9_43.dll丢失&#xff0c;会造成很多游戏无法打…

opencv图片根据规则改变颜色

解析 1. 读入图片 2.通道分离 3.像素值在【100&#xff0c;200】之间&#xff0c;赋值128。大于200赋值255&#xff0c;小于100赋值0。 源码 import cv2 img_raw_path"past/unet-test_result0-0-1-0.png" img_rawcv2.imread(img_raw_path) (r,g,b)cv2.split(img_…

运动控制介绍

运动控制介绍 1 介绍1.1 概述1.2 运动控制的基本架构1.3 常见的控制功能1.4 运动控制研究的问题分类位置变化问题周期式旋转速度变化问题 1.5 知识体系1.6 路径规划 和 轨迹规划区别与联系1.7 运动控制系统 2 《运动控制系统》[班华 李长友 主编] 摘要1 绪论1.1 运动控制研究的…

信息系统项目管理师(第四版)教材精读思维导图-第二章信息技术发展

请参阅我的另一篇文章&#xff0c;综合介绍软考高项&#xff1a; 信息系统项目管理师&#xff08;软考高项&#xff09;备考总结_计算机技术与软件专业技术_铭记北宸的博客-CSDN博客 思维导图源文件下载&#xff1a; https://download.csdn.net/download/hanjingjava/88023847 …

SpringBoot 如何使用 EmbeddedDatabaseBuilder 进行数据库集成测试

SpringBoot 如何使用 EmbeddedDatabaseBuilder 进行数据库集成测试 在开发 SpringBoot 应用程序时&#xff0c;我们通常需要与数据库进行交互。为了确保我们的应用程序在生产环境中可以正常工作&#xff0c;我们需要进行数据库集成测试&#xff0c;以测试我们的应用程序是否能…

非线性规划快速入门和练习题集

目录 定义 标准形式 练习题1 练习题2 练习题3 定义 当目标函数或者约束条件中含有非1次项的时候,会出现非线性函数的规划。 标准形式 ​​​​​​​ ​​​​​​​ ​​​​​​​ ​​​​​​​ 其中f(x)是标准函数&#xff0c;A,b,Ae…

Basic——C++类型转换(转型操作符详解)

C转型操作符 1.C语言类型转换存在的隐患2.static_cast3.const_cast介绍测试案例 4.dynamic_cast5.reinterpret_cast 1.C语言类型转换存在的隐患 数据丢失&#xff1a;当将一个较大的数据类型转换为较小的数据类型时&#xff0c;可能会导致数据丢失。例如&#xff0c;将一个浮点…