SpringCloud(八)ES 进阶 -- 数据聚合
- 聚合的种类
- DSL实现聚合
- RestAPI实现聚合
一、聚合的种类
桶聚合:TermAggregation 主要针对的是字符串keyword类型(不分词)
度量聚合:主要针对数值类型
管理聚合:基于其它聚合结果再做聚合
二、DSL实现聚合
1)DSL实现Bucket聚合
#聚合功能 - bucket GET /hotel/_search { "size": 0, "aggs": { "brandAgg": { "terms": { "field": "brand", "size": 20 } } } }
聚合结果排序
默认情况下,Bucket聚合会统计Bucket内的文档数量,记为_count,并且按照_count降序排序。
我们可以修改排序方式,如:按_count升序排列:
#聚合功能 - bucket GET /hotel/_search { "size": 0, "aggs": { "brandAgg": { "terms": { "field": "brand", "size": 20, "order": { "_count": "asc" } } } } }
限定聚合范围
默认情况下,Bucket聚合是对索引库的所有文档做聚合,我们可以限定要聚合的文档范围,只要添加query条件即可,例如:只对价格在300-500元之间的(含300和500)文档做聚合。
#聚合功能 - bucket GET /hotel/_search { "query": { "range": { "price": { "gte": 300, "lte": 500 } } }, "size": 0, "aggs": { "brandAgg": { "terms": { "field": "brand", "size": 20 } } } }
总结:
2)DSL实现Metrics聚合
#聚合功能 - Metrics GET /hotel/_search { "size": 0, "aggs": { "brandAgg": { "terms": { "field": "brand", "size": 20 }, "aggs": { "scoreAgg": { "stats": { "field": "score" } } } } } }
聚合结果排序
因为此排序是在桶聚合的基础上做的排序,所以排序应该是外环的桶聚合上做排序。
#聚合功能 - Metrics GET /hotel/_search { "size": 0, "aggs": { "brandAgg": { "terms": { "field": "brand", "size": 20, "order": { "scoreAgg.avg": "desc" } }, "aggs": { "scoreAgg": { "stats": { "field": "score" } } } } } }
三、RestAPI实现聚合
之前步骤参考 SpringCloud(七.6)ES(elasticsearch)-- RestClient查询文档和结果处理
- 准备Request
- 准备DSL
- 发送请求
- 解析响应
@Test void testAgg() throws IOException { // 1.准备请求 SearchRequest request = new SearchRequest("hotel"); // 2.请求参数 // 2.1.size request.source().size(0); // 2.2.聚合 request.source().aggregation( AggregationBuilders.terms("brandAgg").field("brand").size(20)); // 3.发出请求 SearchResponse response = client.search(request, RequestOptions.DEFAULT); System.out.println(response); }
聚合查询数据解析
@Test void testAgg() throws IOException { // 1.准备请求 SearchRequest request = new SearchRequest("hotel"); // 2.请求参数 // 2.1.size request.source().size(0); // 2.2.聚合 request.source().aggregation( AggregationBuilders.terms("brandAgg").field("brand").size(20)); // 3.发出请求 SearchResponse response = client.search(request, RequestOptions.DEFAULT); // 4.解析结果 Aggregations aggregations = response.getAggregations(); // 4.1.根据聚合名称,获取聚合结果 Terms brandAgg = aggregations.get("brandAgg"); // 4.2.获取buckets List<? extends Terms.Bucket> buckets = brandAgg.getBuckets(); // 4.3.遍历 for (Terms.Bucket bucket : buckets) { String brandName = bucket.getKeyAsString(); System.out.println("brandName = " + brandName); long docCount = bucket.getDocCount(); System.out.println("docCount = " + docCount); } }
实战测试练习
1、准备工作:需要准备RestClient,在单元测试中是new出来的。在这里我们应该通过@bean注入到spring容器中。然后在HotleService中就可以@Autowired注入使用了。
参考 SpringCloud(七.7)ES(elasticsearch)-- 实战练习
2、编写Controller(这里先不写接口,先把方法写出来。)
3、IService中创建新接口getFilter
Map<String, List<String>> getFilters();
4、实现Service,复制上面的聚合demo进行改写
@Override public Map<String, List<String>> getFilters() { try { // 1.准备请求 SearchRequest request = new SearchRequest("hotel"); // 2.请求参数 // 2.1.query // 2.2.size request.source().size(0); // 2.3.聚合 (多个聚合) buildAggregations(request); // 3.发出请求 SearchResponse response = restHighLevelClient.search(request, RequestOptions.DEFAULT); // 4.解析结果 Aggregations aggregations = response.getAggregations(); Map<String, List<String>> filters = new HashMap<>(3); // 封装解析方法getAggregationByName // 4.1.解析品牌 List<String> brandList = getAggregationByName(aggregations, "brandAgg"); filters.put("brand", brandList); // 4.1.解析品牌 List<String> cityList = getAggregationByName(aggregations, "cityAgg"); filters.put("city", cityList); // 4.1.解析品牌 List<String> starList = getAggregationByName(aggregations, "starAgg"); filters.put("starName", starList); return filters; } catch (IOException e) { throw new RuntimeException(e); } } private void buildAggregations(SearchRequest request) { 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("starAgg").field("starName").size(100)); } private List<String> getAggregationByName(Aggregations aggregations, String aggName) { // 4.1.根据聚合名称,获取聚合结果 Terms terms = aggregations.get(aggName); // 4.2.获取buckets List<? extends Terms.Bucket> buckets = terms.getBuckets(); // 4.3.遍历 List<String> list = new ArrayList<>(buckets.size()); for (Terms.Bucket bucket : buckets) { String brandName = bucket.getKeyAsString(); list.add(brandName); } return list; }
5、在单元测试中测试返回效果
@Autowired private IHotelService hotelService; @Test void getFilterTest() { Map<String, List<String>> filters = hotelService.getFilters(); System.out.println(filters); }
6、构思接口,不应该对所有数据去做聚合,假设用户搜索了虹桥,虹桥归属上海这个城市,此时再显示其它城市没有意义。所以应该限定聚合的范围,加上query条件。
7、编写Controller ,alt+enter 重构 IService 和 Service
@PostMapping("filters") public Map<String, List<String>> getFilters(@RequestBody RequestParams params) { return hotelService.getFilters(params); }
8、修改Service ,聚合时添加 query 条件
第4步 2.1 query 下新增 buildBasicQuery(params, request);
private void buildBasicQuery(RequestParams params, SearchRequest request) { // 1.准备Boolean查询 BoolQueryBuilder boolQuery = QueryBuilders.boolQuery(); // 1.1.关键字搜索,match查询,放到must中 String key = params.getKey(); if (StringUtils.isNotBlank(key)) { // 不为空,根据关键字查询 boolQuery.must(QueryBuilders.matchQuery("all", key)); } else { // 为空,查询所有 boolQuery.must(QueryBuilders.matchAllQuery()); } // 1.2.品牌 String brand = params.getBrand(); if (StringUtils.isNotBlank(brand)) { boolQuery.filter(QueryBuilders.termQuery("brand", brand)); } // 1.3.城市 String city = params.getCity(); if (StringUtils.isNotBlank(city)) { boolQuery.filter(QueryBuilders.termQuery("city", city)); } // 1.4.星级 String starName = params.getStarName(); if (StringUtils.isNotBlank(starName)) { boolQuery.filter(QueryBuilders.termQuery("starName", starName)); } // 1.5.价格范围 Integer minPrice = params.getMinPrice(); Integer maxPrice = params.getMaxPrice(); if (minPrice != null && maxPrice != null) { maxPrice = maxPrice == 0 ? Integer.MAX_VALUE : maxPrice; boolQuery.filter(QueryBuilders.rangeQuery("price").gte(minPrice).lte(maxPrice)); } // 2.算分函数查询 FunctionScoreQueryBuilder functionScoreQuery = QueryBuilders.functionScoreQuery( boolQuery, // 原始查询,boolQuery new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{ // function数组 new FunctionScoreQueryBuilder.FilterFunctionBuilder( QueryBuilders.termQuery("isAD", true), // 过滤条件 ScoreFunctionBuilders.weightFactorFunction(10) // 算分函数 ) } ); // 3.设置查询条件 request.source().query(functionScoreQuery); }
演示效果:
带关键字