SpringCloud(八)ES 进阶 -- 数据聚合

本章依据上章  SpringCloud(七.7)ES(elasticsearch)-- 实战练习 的demo基础上练习
 
本章知识点 --- 
  • 聚合的种类
  • 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);
    }

 

演示效果:

 带关键字

 



 

 
posted @ 2024-04-21 14:41  一介桃白白  阅读(26)  评论(0编辑  收藏  举报