es实现桶聚合

news2024/12/28 5:57:24

目录

聚合

聚合的分类

DSL实现桶聚合

 dsl语句

 结果

聚合结果排序 

限定聚合范围 

总结

聚合必须的三要素:

聚合可配置的属性

 DSL实现metric聚合

例如:我们需要获取每个品牌的用户评分的min,max,avg等值

只求socre的max

利用RestHighLevelClient实现聚合

业务需求

dsl语句 

java代码

结果

​编辑


聚合

聚合是根据查询后的结果来聚合的,如果没有写query查询条件,就是对索引库的所有文档进行聚合

聚合的分类

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

1.桶(Bucket)聚合:用来对文档进行分组,相当于mysql的group by

  • TermAggregation:按照文档字段值进行分组,注意:这个文档字段不可分词
  • Date Histogram:按照日期阶梯分组,例如一周为一组,或者一个月为一组

2.度量(Metric)聚合:用来计算一些值,比如:最大值,最小值,平均值 

3.管道(pipeline)聚合:其他聚合的结果为基础做聚合

参与聚合的字段类型必须是:

  • keyword
  • 数值
  • 日期
  • 布尔 

DSL实现桶聚合

现在,我们要统计所有数据中的酒店品牌有几种,其实就是按照品牌对数据分组。此时可以根据酒店品牌的名称做聚合,也就是Bucket聚合。

 dsl语句

GET /hotel/_search
{
    "size":0, //返回命中文档的详细信息的数量,(默认执行match_all),这里设置为0就是不返回文档的详细信息
    "aggs":{ //聚合查询关键字
        "brandSum":{ //桶名字
            "trems":{ //聚合的类型,这里使用brand字段聚合,所以使用terms
                "field":"brand", 
                "size":20 //返回最多的桶数量,如果设置为1,就只返回一个桶的信息
            }
        }
    }
}

 结果

可以看见hits数组里的值为空,因为我们设置了size=0,不返回文档的详细信息

brandSum就是这个聚合的名字,buckets桶数组最大的大小为20,默认通过桶里的文档数降序排序

聚合结果排序 

默认情况下,Bucket聚合会统计Bucket内的文档数量,记为_count,并且按照_count降序排序。

我们可以指定order属性,自定义聚合的排序方式:

GET /hotel/_search
{
    "size":0,
    "aggs":{
        "brandSum":{
            "terms":{
                "field":"brand",
                "size":20,
                "order":{ #自定义排序规则
                    "_count":asc #使用桶内的文档数进行升序排序
                }
            }
        }
    }
}

 

限定聚合范围 

默认情况下,Bucket聚合是对索引库的所有文档做聚合,但真实场景下,用户会输入搜索条件,因此聚合必须是对搜索结果聚合。那么聚合必须添加限定条件。

我们可以限定要聚合的文档范围,只要添加query条件即可:

只聚合价格大于500的文档

 

总结

聚合必须的三要素:

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

聚合可配置的属性

  • size:指定聚合结果(即桶的最大数量)的最大数量 
  • order:指定聚合结果排序的方式
  • field:指定聚合的字段

 DSL实现metric聚合

例如:我们需要获取每个品牌的用户评分的min,max,avg等值

GET /hotel/_search
{
    "aggs":{
        "brandSum":{
            "terms":{
                "field":"brand",
                "size":20
            },
         "aggs":{ //brandSum聚合下的子聚合
                "scoreStats":{//子聚合名字
                    "stats":{ //聚合的类型·。stats会把max,min,avg,sum,count都算出来
                        "field":"score"
                    }
                }
            }
        }
    }
}

只求socre的max

 

利用RestHighLevelClient实现聚合

@SpringBootTest
public class TestAggregation {
    @Autowired
    private RestHighLevelClient restHighLevelClient;
    @Test
    public void test01() throws IOException {
        //构建 查询对象
        SearchRequest request = new SearchRequest("hotel");
        //设置dsl语句
        request.source().size(0);
        request.source().aggregation(AggregationBuilders
                .terms("brandSum")//指定聚合的名字为brandSum,且聚合的类型是terms
                .field("brand")//指定聚合的字段是brand
                .size(20));
        //发送请求
        SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT);

        //解析响应数据
        Aggregations aggregations = response.getAggregations();
        Terms brandSum= aggregations.get("brandSum");
        List<? extends Terms.Bucket> buckets = brandSum.getBuckets();
        for (Terms.Bucket bucket : buckets) {
            String key = bucket.getKeyAsString();
            System.out.println(key);
        }
    }
}

业务需求

需求:搜索页面的品牌、城市等信息不应该是在页面写死,而是通过聚合索引库中的酒店数据得来的:

分析:

目前,页面的城市列表、星级列表、品牌列表都是写死的,并不会随着搜索结果的变化而变化。但是用户搜索条件改变时,搜索结果会跟着变化。

例如:用户搜索“东方明珠”,那搜索的酒店肯定是在上海东方明珠附近,因此,城市只能是上海,此时城市列表中就不应该显示北京、深圳、杭州这些信息了。

也就是说,搜索结果中包含哪些城市,页面就应该列出哪些城市;搜索结果中包含哪些品牌,页面就应该列出哪些品牌。

如何得知搜索结果中包含哪些品牌?如何得知搜索结果中包含哪些城市?

使用聚合功能,利用Bucket聚合,对搜索结果中的文档基于品牌分组、基于城市分组,就能得知包含哪些品牌、哪些城市了。

因为是对搜索结果聚合,因此聚合是限定范围的聚合,也就是说聚合的限定条件跟搜索文档的条件一致。

查看浏览器可以发现,前端其实已经发出了这样的一个请求:

请求参数与搜索文档的参数完全一致

返回值类型就是页面要展示的最终结果:

结果是一个Map结构:

  • key是字符串,城市、星级、品牌、价格
  • value是集合,例如多个城市的名称

dsl语句 

结果会有三个桶聚合的结果,可以发现因为查询条件match全文匹配了上海,所以citySum聚合桶内只有一种城市就是上海,这种情况是正确的 ,因为聚合是根据查询后的结果来聚合的,如果没有query查询条件,就是对索引库的所有文档进行聚合

这里有三种聚合,都是独立的 ,city聚合,brand聚合,starName聚合

 

java代码

前端封装类

package com.hhh.hotel.pojo;

import lombok.Data;

@Data
public class RequestParams {
    private String key;
    private Integer page;
    private Integer size;
    private String sortBy;
    // 下面是新增的过滤条件参数
    private String city;
    private String brand;
    private String starName;
    private Integer minPrice;
    private Integer maxPrice;
    //坐标
    private String location;
}

controller层

  @RestController
@RequestMapping("/hotel")
public class HotelController {
    @Autowired
    private HotelService hotelService;
    @PostMapping("/list")
    public PageResult getPageResult(@RequestBody RequestParams params){
        return hotelService.getPageResult(params);
    }

    /**
     * 获取传入条件过滤出 星级,城市,品牌
     */
    @PostMapping("/filters")
    public Map<String, List<String>> getFilters(@RequestBody RequestParams params){
        return hotelService.getFilters(params);
    }
}

service服务层 

前端进行搜索时,会发送两个请求,一个请求时/hotel/list获取匹配到的hotel信息,还有一个请求时/hotel/filters获取根据查询条件得到的结果去聚合过滤出的星级,城市和品牌 


@Service
public class HotelServiceImpl extends ServiceImpl<HotelMapper, Hotel>
    implements HotelService {
    @Autowired
    private RestHighLevelClient restHighLevelClient;
    @Override
    public PageResult getPageResult(RequestParams params) {
        //1.构建 查询请求对象
        SearchRequest request = new SearchRequest("hotel");
        //2.编写dsl语句
       /* //2.1如果key为空,即搜索的内容为空,就全文查询
        if(StringUtils.isBlank(params.getKey())){
            request.source().query(QueryBuilders.matchAllQuery());
        }else {
            request.source().query(QueryBuilders.matchQuery("all",params.getKey()));
        }*/
        assembleBasicQuery(params,request);

        //构建高亮显示
        request.source().highlighter(new HighlightBuilder().field("name").requireFieldMatch(false));

        //3.构建分页信息
        Integer pageNum=params.getPage()==null?1:params.getPage();//默认是第一页
        Integer pageSize=params.getSize()==null?5:params.getSize();//默认每页大小为5

        request.source().from((pageNum-1)*pageSize).size(pageSize);

        //TODO:维护距离排序
        if(StringUtils.isNotBlank(params.getLocation())) {
            GeoDistanceSortBuilder geoDistanceSortBuilder = new GeoDistanceSortBuilder("location", new GeoPoint(params.getLocation()))
                    .order(SortOrder.ASC) // 升序排序
                    .unit(DistanceUnit.KILOMETERS); // 单位为千米
            request.source().sort(geoDistanceSortBuilder);
        }
        if(params.getSortBy().equals("price")){
            request.source().sort("price",SortOrder.ASC);
        } else if (params.getSortBy().equals("score")) {
            request.source().sort("score",SortOrder.DESC);
        }

        //4.发送请求
        SearchResponse response = null;
        try {
            response = restHighLevelClient.search(request, RequestOptions.DEFAULT);
        } catch (IOException e) {
            throw new RuntimeException("查询异常");
        }
        //5.解析数据
        Boolean isEnableKm=false;
        if(params.getLocation()!=null){
            isEnableKm=true;//location字段不为null,才设置为true,然后获取排序值
        }
        return ParseResponse(response,isEnableKm);
    }
    /**
     * 获取传入条件过滤出 星级,城市,品牌
     */
    @Override
    public Map<String, List<String>> getFilters(RequestParams params) {
        SearchRequest request = new SearchRequest("hotel");
        request.source().size(0);
        //设置query DSL语句
        assembleBasicQuery(params,request);
        //构建桶聚合
        request.source().aggregation(AggregationBuilders
                .terms("brandSum")
                .field("brand")
                .size(20));
        request.source().aggregation(AggregationBuilders
                .terms("citySum")
                .field("city")
                .size(20));
        request.source().aggregation(AggregationBuilders
                .terms("starNameSum")
                .field("starName")
                .size(20));
        //发起请求
        SearchResponse response = null;
        try {
            response = restHighLevelClient.search(request, RequestOptions.DEFAULT);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        //解析返回数据
        List<String>brands=parseAggreData(response,"brandSum");
        List<String>citys=parseAggreData(response,"citySum");
        List<String>startNames=parseAggreData(response,"starNameSum");
        Map<String, List<String>> info = new HashMap<>();
        info.put("brand",brands);
        info.put("city",citys);
        info.put("starName",startNames);
        return info;
    }

    /**
     * 解析桶数据
     * @return
     */
    private List<String> parseAggreData(SearchResponse response, String sum) {
        Aggregations aggregations = response.getAggregations();
        Terms aggregation = aggregations.get(sum);
        if(aggregation==null||CollectionUtils.isEmpty(aggregation.getBuckets())){
            return null;
        }
        ArrayList<String> list = new ArrayList<>();
        for (Terms.Bucket bucket : aggregation.getBuckets()) {
            String key = bucket.getKeyAsString();
            list.add(key);
        }
        return list;
    }

    /**
     * 组装dsl查询语句
     * @param params 前端封装类
     * @param request 查询请求对象
     */
    private void assembleBasicQuery(RequestParams params,SearchRequest request){
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        //1.判断key是否为空
        if(StringUtils.isBlank(params.getKey())){
            boolQuery.must(QueryBuilders.matchAllQuery());
        }else{
            boolQuery.must(QueryBuilders.matchQuery("all",params.getKey()));
        }
        //品牌名不为空,对品牌过滤
        if(StringUtils.isNotBlank(params.getBrand())){
            boolQuery.filter(QueryBuilders.termQuery("brand",params.getBrand()));
        }
        //城市
        if(StringUtils.isNotBlank(params.getCity())){
            boolQuery.filter(QueryBuilders.termQuery("city",params.getCity()));
        }
        //星级
        if(StringUtils.isNotBlank(params.getStarName())){
            boolQuery.filter(QueryBuilders.termQuery("startName",params.getStarName()));
        }
        //价格范围
        if(params.getMaxPrice()!=null&&params.getMinPrice()!=null){
            boolQuery.filter(QueryBuilders.rangeQuery("price").gte(params.getMinPrice()).lte(params.getMaxPrice()));
        }
        //定义算分函数
        //设置排名,根据算分设置level排名
        FieldValueFactorFunctionBuilder functionBuilder = ScoreFunctionBuilders.fieldValueFactorFunction("value").modifier(FieldValueFactorFunction.Modifier.NONE).factor(1.5F).missing(1);


        FunctionScoreQueryBuilder.FilterFunctionBuilder[] functions = new FunctionScoreQueryBuilder.FilterFunctionBuilder[] {
                new FunctionScoreQueryBuilder.FilterFunctionBuilder(
                        QueryBuilders.termQuery("isAD",true),
                        //ScoreFunctionBuilders.weightFactorFunction(10)  // 权重因子,乘以基础得分
                        functionBuilder
                )
        };
        FunctionScoreQueryBuilder functionScoreQuery = QueryBuilders.functionScoreQuery(boolQuery, functions).boostMode(CombineFunction.SUM);
        request.source().query(functionScoreQuery);
    }

    /**
     * 解析es响应的数据
     */
    private PageResult ParseResponse(SearchResponse response,Boolean isEnableKm) {
        SearchHits hits = response.getHits();
        //1.获取总命中文档数
        long size = hits.getTotalHits().value;
        SearchHit[] hits1 = hits.getHits();
        ArrayList<HotelDoc> docs = new ArrayList<>();
        if(ArrayUtils.isNotEmpty(hits1)){
            for (SearchHit searchHit : hits1) {
                String jsonData = searchHit.getSourceAsString();
                HotelDoc hotelDoc = JSON.parseObject(jsonData, HotelDoc.class);

                if(isEnableKm) {
                    //获取排序后的距离
                    Object[] sortValues = searchHit.getSortValues();
                    if (ArrayUtils.isNotEmpty(sortValues)) {
                        hotelDoc.setDistance(sortValues[0]);
                    }
                }
                //获取高亮
                Map<String, HighlightField> fieldMap = searchHit.getHighlightFields();
                if(!CollectionUtils.isEmpty(fieldMap)){
                    HighlightField highlightField = fieldMap.get("name");
                    String highName = highlightField.getFragments()[0].string();
                    //替换hotel实体类的name属性
                    hotelDoc.setName(highName);
                }

                docs.add(hotelDoc);
            }
        }
        return new PageResult(size,docs);
    }
}




结果

输入上海进行搜索时,会根据hotel/list获取全文检索匹配的文档信息,然后根据/hotel/filters获取过滤出的city,brand,price,但是根据上海匹配出来的文档,经过city字段进行聚合时,citySum桶内只有一个数据就是上海,只有一个数据时,直接在前端不显示其他城市的选择即可,因为没必要

然后brandSum桶内有所有品牌的名字,和 starSum桶内所有的星级都会显示出来(因为桶内的数量大于1)

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

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

相关文章

【Multisim14.0正弦波>方波>三角波】2022-6-8

缘由有没有人会做啊Multisim14.0-其他-CSDN问答参考方波、三角波、正弦波信号产生 - 豆丁网

arcgis中dem转模型导入3dmax

文末分享素材 效果 1、准备数据 (1)DEM (2)DOM 2、打开arcscene软件 3、加载DEM、DOM数据 4、设置DOM的高度为DEM

【虚幻引擎UE】UE5 音频共振特效制作

UE5 音频共振特效制作 一、基础准备1.插件准备2.音源准备 二、创建共感NRT解析器和设置1.解析器选择依据2. 创建解析器3. 创建解析器设置&#xff08;和2匹配&#xff09;4.共感NRT解析器设置参数调整5.为共感NRT解析器关联要解析的音频和相应设置 三、蓝图控制1.创建Actor及静…

Openlayers高级交互(8/20):选取feature,平移feature

本示例介绍如何在vue+openlayers中使用Translate,选取feature,平移feature。选择的时候需要按住shift。Translate 功能通常是指在地图上平移某个矢量对象的位置。在 OpenLayers 中,可以通过修改矢量对象的几何位置来实现这一功能。 效果图 配置方式 1)查看基础设置:http…

AnaTraf | 全面掌握网络健康状态:全流量的分布式网络性能监测系统

AnaTraf 网络性能监控系统NPM | 全流量回溯分析 | 网络故障排除工具AnaTraf网络流量分析仪是一款基于全流量&#xff0c;能够实时监控网络流量和历史流量回溯分析的网络性能监控与诊断系统&#xff08;NPMD&#xff09;。通过对网络各个关键节点的监测&#xff0c;收集网络性能…

麒麟v10 arm64 部署 kubesphere 3.4 修改记录

arm64环境&#xff0c;默认安装 kubesphere 3.4 &#xff0c;需要修改几个地方的镜像&#xff0c;并且会出现日志无法显示 1 fluentbit:v1.9.4 报错 <jemalloc>: Unsupported system page size Error in GnuTLS initialization: ASN1 parser: Element was not found. &…

2.3 机器学习--朴素贝叶斯(分类)

本章我们来学习朴素贝叶斯算法&#xff0c;贝叶斯分类是一类分类算法的总称&#xff0c;这类算法均以贝叶斯定理为基础&#xff0c;故统称为贝叶斯分类。而朴素贝叶斯分类是贝叶斯分类中最简单&#xff0c;也是常见的一种分类方法。 我们常常遇到这样的场景。与友人聊天时&…

合合信息亮相2024中国模式识别与计算机视觉大会,用AI构建图像内容安全防线

近日&#xff0c;第七届中国模式识别与计算机视觉大会&#xff08;简称“PRCV 2024”&#xff09;在乌鲁木齐举办。大会由中国自动化学会&#xff08;CAA&#xff09;、中国图象图形学学会&#xff08;CSIG&#xff09;、中国人工智能学会&#xff08;CAAI&#xff09;和中国计…

分享几个办公类常用的AI工具

办公类 WPS AI讯飞智文iSlideProcessOn亿图脑图ChatPPT WPS AI 金山办公推出的协同办公 AI 应用&#xff0c;具有文本生成、多轮对话、润色改写等多种功能&#xff0c;可以辅助用户进行文档编辑、表格处理、演示文稿制作等办公操作。 https://ai.wps.cn/ 讯飞智文 科大讯飞推…

我谈Canny算子

在Canny算子的论文中&#xff0c;提出了好的边缘检测算子应满足三点&#xff1a;①检测错误率低——尽可能多地查找出图像中的实际边缘&#xff0c;边缘的误检率&#xff08;将边缘识别为非边缘&#xff09;低&#xff0c;且避免噪声产生虚假边缘&#xff08;将非边缘识别为边缘…

高效文本编辑与导航:Vim中的三种基本模式及粘滞位的深度解析

✨✨ 欢迎大家来访Srlua的博文&#xff08;づ&#xffe3;3&#xffe3;&#xff09;づ╭❤&#xff5e;✨✨ &#x1f31f;&#x1f31f; 欢迎各位亲爱的读者&#xff0c;感谢你们抽出宝贵的时间来阅读我的文章。 我是Srlua小谢&#xff0c;在这里我会分享我的知识和经验。&am…

有色行业测温取样机器人 - SNK施努卡

SNK施努卡有色行业熔炼车间机器人测温取样 在有色行业&#xff0c;测温取样机器人专门设计用于自动化处理高温熔体的温度监测和样品采集任务。这类机器人在铜、铝、锌等金属冶炼过程中扮演着关键角色&#xff0c;以提高生产效率、确保产品质量并增强工作安全性。 主要工作项 …

ABAP ALV

目录 一、基本概念 1、ALV概览 2、基本概念 二、属性更改 1、FIELDCAT 2、宏 3、LAYOUT 4、升序降序SORT 5、FILTER 三、交互 1、实现自己的按钮 一、基本概念 1、ALV概览 ALV&#xff1a;SAP List View,是SAP提供的一个强大的数据报表显示工具 ALV实际上是一个…

前端零基础入门到上班:【Day3】从零开始构建网页骨架HTML

HTML 基础入门&#xff1a;从零开始构建网页骨架 目录 1. 什么是 HTML&#xff1f;HTML 的核心作用 2. HTML 基本结构2.1 DOCTYPE 声明2.2 <html> 标签2.3 <head> 标签2.4 <body> 标签 3. HTML 常用标签详解3.1 标题标签3.2 段落和文本标签3.3 链接标签3.4 图…

CRLF、UTF-8这些编辑器右下角的选项的意思

经常使用编辑器的小伙伴应该经常能看到右下角会有这么两个选项&#xff0c;下图是VScode中的示例&#xff0c;那么这两个到底是啥作用呢&#xff1f; 目录 字符编码ASCII 字符集GBK 字符集Unicode 字符集UTF-8 编码 换行 字符编码 此部分参考博文 在计算机中&#xff0c;所有…

网络搜索引擎Shodan(1)

声明&#xff1a;学习视频来自b站up主 泷羽sec&#xff0c;如涉及侵权马上删除文章 感谢泷羽sec 团队的教学 视频地址&#xff1a;shodan(1)_哔哩哔哩_bilibili 本文主要讲解网络搜索引擎Shodan的一些用法&#xff08;host和search这两个命令&#xff09;。 Shodan 是一个网络…

合合信息亮相PRCV大会,探讨生成式AI时代的内容安全与系统构建加速

一、前言 在人工智能技术的飞速发展下&#xff0c;生成式AI已经成为推动社会进步的重要力量。然而&#xff0c;随着技术的不断进步&#xff0c;内容安全问题也日益凸显。如何确保在享受AI带来的便利的同时&#xff0c;保障信息的真实性和安全性&#xff0c;已经成为整个行业待解…

高速自爆穿梭无人机技术详解

高速自爆穿梭无人机技术是一种结合了高速飞行与自爆式攻击能力的先进无人机技术。以下是对该技术的详细解析&#xff1a; 一、技术特点 1. 高速飞行&#xff1a; 高速自爆穿梭无人机通常具备极高的飞行速度&#xff0c;如部分型号的速度可达到174公里/小时&#xff0c;甚至更…

Stack和Queue(3)

Stack和Queue&#xff08;3&#xff09; priority_queue的模拟实现 priority_queue.h #include <vector>namespace soobin {template<class T, class Container vector<T>>class priority_queue{public://强制生成默认构造priority_queue() default;temp…

html+css+js实现Notification 通知

实现效果&#xff1a; 代码实现&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Notif…