es实现桶聚合

目录

聚合

聚合的分类

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 {@Autowiredprivate RestHighLevelClient restHighLevelClient;@Testpublic 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 {@Autowiredprivate 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 {@Autowiredprivate RestHighLevelClient restHighLevelClient;@Overridepublic 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();//默认每页大小为5request.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);}/*** 获取传入条件过滤出 星级,城市,品牌*/@Overridepublic 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.rhkb.cn/news/456847.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

BIO,NIO,直接内存,零拷贝

前置知识 什么是Socket&#xff1f; Socket是应用层与TCP/IP协议族通信的中间软件抽象层&#xff0c;它是一组接口&#xff0c;一般由操作系统提供。在设计模式中&#xff0c;Socket其实就是一个门面模式&#xff0c;它把复杂的TCP/IP协议处理和通信缓存管理等等都隐藏在Sock…

vue3使用i18n做国际化多语言,实现常量跟随语言切换翻译

因为我有一个常量的配置文件在项目中&#xff0c;而且有中文内容&#xff0c;我想在切换语言的时候&#xff0c;跟着这个翻译也实时切换&#xff0c;就可以使用computed计算属性实现。 把name改成下面的样子&#xff1a; name: computed(() > t(pad.regularMode)), 就可以…

分享一款录屏、直播软件

光音录屏 光音录屏 是新一代的录屏工具&#xff0c;跟传统录屏工具相比&#xff0c;它不仅可以录制屏幕&#xff0c;还可以同时录制「人像 屏幕」&#xff0c;此外它还提供了美颜、虚拟背景、绿幕抠像、图片、文本编辑、字幕、白板等更多高级功能。你可以将录制好的视频&…

ue5实现数字滚动增长

方法1 https://www.bilibili.com/video/BV1h14y197D1/?spm_id_from333.999.0.0 b站教程 重写loop节点 方法二 写在eventtick里

ffmpeg视频滤镜: 色温- colortemperature

滤镜简述 colortemperature 官网链接 》 FFmpeg Filters Documentation 这个滤镜可以调节图片的色温&#xff0c;色温值越大显得越冷&#xff0c;可以参考一下下图&#xff1a; 咱们装修的时候可能会用到&#xff0c;比如选择灯还有地板的颜色的时候&#xff0c;选暖色调还是…

多厂商的实现不同vlan间通信

Cisco单臂路由 Cisco路由器配置 -交换机配置 -pc配置 华三的单臂路由 -路由器配置 -华三的接口默认是打开的 -pc配置及ping的结果 -注意不要忘记配置默认网关 Cisco-SVI -交换机的配置 -创建vlan -> 设置物理接口对应的Acess或Trunk -> 进入vlan接口&#xff0c;打开接…

【纯血鸿蒙】HarmonyOS和OpenHarmony 的区别

一、开源鸿蒙&#xff08;Open Harmony&#xff09; 鸿蒙系统愿来的设计初衷&#xff0c;就是让所有设备都可以运行一个系统&#xff0c;但是每个设备的运算能力和功能都不同&#xff0c;所以内核的设计上&#xff0c;采用了微内核的设计&#xff0c;除了最基础的功能放在内核…

mfc之tab标签控件的使用--附TabSheet源码

TabSheet源码 TabSheet.h #if !defined(AFX_TABSHEET_H__42EE262D_D15F_46D5_8F26_28FD049E99F4__INCLUDED_) #define AFX_TABSHEET_H__42EE262D_D15F_46D5_8F26_28FD049E99F4__INCLUDED_#if _MSC_VER > 1000 #pragma once #endif // _MSC_VER > 1000 // TabSheet.h : …

C++面向对象编程学习

C面向对象编程学习 前言一、C面向对象编程二、知识点学习1. 定义一个类1.1 使用struct定义1.2 使用class定义1.3 struct和class的区别 2. 类的定义方式2.1 单文件定义&#xff08;Inline Definition&#xff09;2.2 分离定义&#xff08;Separate Definition&#xff09;2.3 头…

[bug] vllm 0.6.1 RuntimeError: operator torchvision::nms does not exist

[bug] vllm 0.6.1 RuntimeError: operator torchvision::nms does not exist 环境 python 3.10 torch 2.4.0cu118 torchvision 0.19.0cu118 vllm 0.6.1.post2cu118问题详情 if torch._C._d…

【华为路由】OSPF多区域配置

网络拓扑 设备接口地址 设备 端口 IP地址 RTA Loopback 0 1.1.1.1/32 G0/0/0 10.1.1.1/24 RTB Loopback 0 2.2.2.2/32 G0/0/0 10.1.1.2/24 G0/0/1 10.1.2.1/24 RTC Loopback 0 3.3.3.3/32 G0/0/0 10.1.2.2/24 G0/0/1 10.1.3.1/24 RTD Loopback 0 4.4.4…

【Jenkins】解决在Jenkins Agent节点容器内无法访问物理机的docker和docker compose的问题

解决在Jenkins Agent节点容器内无法访问物理机的docker和docker compose的问题 1. 确定物理机docker和docker compose已经安装2. 编写Jenkins Agent结点docker-compose.yaml配置文件3. 修改docker运行时文件权限4. 启动容器并验证 最近接触到一个发布产物是一个 docker镜像的项…

【K8s】Kubernetes 证书管理工具 Cert-Manager

本文内容均来自个人笔记并重新梳理&#xff0c;如有错误欢迎指正&#xff01; 如果对您有帮助&#xff0c;烦请点赞、关注、转发、订阅专栏&#xff01; 专栏订阅入口 | 精选文章 | Kubernetes | Docker | Linux | 羊毛资源 | 工具推荐 | 往期精彩文章 【Docker】&#xff08;全…

Github优质项目推荐(第八期)

文章目录 Github优质项目推荐 - 第八期一、【manim】&#xff0c;66.5k stars - 创建数学动画的 Python 框架二、【siyuan】&#xff0c;19.5k stars - 个人知识管理软件三、 【GetQzonehistory】&#xff0c;1.3k stars - 获取QQ空间发布的历史说说四、【SecLists】&#xff0…

【Linux系统编程】冯诺依曼体系结构与操作系统

目录 1、冯诺依曼体系结构 1.1 冯诺依曼体系结构的组成 1.2 程序运行时必须要加载到内存 1.3 数据通信 1.4 为什么要有内存 2、操作系统 2.1 概念 2.2 设计OS的目的 2.3 如何理解管理 2.4 系统调用和库函数的概念 1、冯诺依曼体系结构 我们常见的计算机&#xff0c;如…

transforms的使用

示例代码 from PIL import Image from torch.utils.tensorboard import SummaryWriter from torchvision import transforms#打开该图片 img_path"hymenoptera_data/val/bees/10870992_eebeeb3a12.jpg" imgImage.open(img_path) writerSummaryWriter("logs&quo…

CSS行块标签的显示方式

块级元素 标签&#xff1a;h1-h6&#xff0c;p,div,ul,ol,li,dd,dt 特点&#xff1a; &#xff08;1&#xff09;如果块级元素不设置默认宽度&#xff0c;那么该元素的宽度等于其父元素的宽度。 &#xff08;2&#xff09;所有的块级元素独占一行显示. &#xff08;3&#xff…

海外云手机怎样助力亚马逊店铺运营?

随着全球化的发展&#xff0c;越来越多的企业开始重视海外市场的拓展&#xff0c;尤其是出海企业和B2B外贸企业。亚马逊作为全球最大的电商平台之一&#xff0c;成为了许多企业争夺国际市场的重点战场。本文将深入分析海外云手机在优化亚马逊店铺引流中的作用和优势&#xff0c…

怿星科技薛春宇丨智能汽车软件研发工具链国产化的挑战和探索

2024年7月25日&#xff0c;由上海良益企业管理咨询有限公司主办的“2024域控制器技术论坛“在上海成功举办&#xff0c;十位嘉宾做了精彩分享。“整零有道”将陆续刊出部分演讲的文字实录&#xff0c;以飨读者。 本期刊出怿星科技副总经理薛春宇的演讲实录&#xff1a;智能汽车…

如何用 obdiag 排查 OceanBase数据库的卡合并问题——《OceanBase诊断系列》14

1. 背景 卡合并在OceanBase中是一个复杂的问题&#xff0c;其产生可能源于多种因素。目前&#xff0c;对于卡合并的明确界定尚不存在统一标准&#xff0c;一方面&#xff0c;我们界定超过36小时未完成合并为合并超时&#xff0c;此时RS会记录ERROR日志&#xff1b;另一方面&am…