本文主要为您介绍多元索引的聚合接口。

字段值分组

  • 定义:在查询结果上根据一个字段的值进行分组,相同的值会放到一个分组内,最终返回每一个组的值和该值对应的个数。字段值分组在分组较大的情况下会存在极少量的误差。
  • 参数说明
    参数 说明
    groupByName 用户给本次统计聚合功能自定义的名字,用于区分不同的统计聚合操作。在使用结果时,需要根据该名字找到本次统计聚合的结果。
    fieldName 聚合作用的字段,仅支持keyword、long、double、bool类型。
    groupBySorter 添加分组中的item排序规则。默认按照分组中item的数量降序排序,多个排序则按照添加的顺序进行排列。支持的参数包括:
    • 按照key的字典序升序排列
    • 按照key的字典序降序排列
    • 按照行数升序排列
    • 按照行数降序排列
    • 按照子统计的值升序排列
    • 按照子统计的值降序排列
    size 返回的分组数量。
    subAggregation和subGroupBy 添加子统计聚合。子统计聚合会根据分组内容进行再一次的统计聚合分析。举例:
    • 场景

      统计每个类别的商品数量,且统计每个类别价格的最大值和最小值。

    • 方法

      最外层的聚合是根据类别进行分组,然后添加两个子Aggregation求价格的最大值和最小值。

    • 结果示例
      • 水果:5个(其中价格最高15元,最低3元)
      • 洗漱用品:10个(其中价格最高98元,最低1元)
      • 电子设备:3个(其中价格最高8699元,最低2300元)
      • 其它:15个(其中价格最高1000元,最低80元)
  • Java示例1
    /**
     * 所有商品中每一个类别各有多少个?且统计每一个类别的价格最大值和最小值。
     * 返回结果举例:"水果:5个(其中价格最贵15元,最便宜3元),洗漱用品:10个(其中价格最贵98元,最便宜1元),电子设备:3个(其中价格最贵8699元,最便宜2300元),其它:15个(其中价格最贵1000
     * 元,最便宜80元)"
     */
    public void groupByField(SyncClient client) {
        // 构建查询语句
        SearchRequest searchRequest = SearchRequest.newBuilder()
            .tableName("tableName")
            .indexName("indexName")
            .searchQuery(
                SearchQuery.newBuilder()
                    .query(QueryBuilders.matchAll())
                    .limit(0)
                    .addGroupBy(GroupByBuilders
                        .groupByField("name1", "column_type")
                        .addSubAggregation(AggregationBuilders.min("subName1", "column_price"))
                        .addSubAggregation(AggregationBuilders.max("subName2", "column_price"))
                    )
                    .build())
            .build();
        //执行查询
        SearchResponse resp = client.search(searchRequest);
        //获取统计聚合的结果
        for (GroupByFieldResultItem item : resp.getGroupByResults().getAsGroupByFieldResult("name1").getGroupByFieldResultItems()) {
            // 值
            System.out.println(item.getKey());
            // 个数
            System.out.println(item.getRowCount());
            // 最低价格
            System.out.println(item.getSubAggregationResults().getAsMinAggregationResult("subName1").getValue());
            // 最高价格
            System.out.println(item.getSubAggregationResults().getAsMaxAggregationResult("subName2").getValue());
        }
    }
  • Java示例2
        /**
         * GroupBy多字段example
         * 多元索引目前为止不能原生支持sql中groupBy多字段,但是可以嵌套使用两个groupBy完成相似功能
         * 下方的代码完成的是SQL: select a,d, sum(b),sum(c) from user group by a,d
         */
        public void GroupByMultiField() {
            SearchRequest searchRequest = SearchRequest.newBuilder()
                .tableName("tableName")
                .indexName("indexName")
                .returnAllColumns(true)   // false,然后指定addColumnsToGet,性能会高
                //.addColumnsToGet("col_1","col_2")
                .searchQuery(SearchQuery.newBuilder()
                    .query(QueryBuilders.matchAll())   // 这里写where条件, 可以用 QueryBuilders.bool() 查询嵌套复杂的查询
                    .addGroupBy(
                        GroupByBuilders
                            .groupByField("任意唯一名字标识_1", "field_a")
                            .size(20)
                            .addSubGroupBy(
                                GroupByBuilders
                                    .groupByField("任意唯一名字标识_2", "field_d")
                                    .size(20)
                                    .addSubAggregation(AggregationBuilders.sum("任意唯一名字标识_3", "field_b"))
                                    .addSubAggregation(AggregationBuilders.sum("任意唯一名字标识_4", "field_c"))
                            )
                    )
                    .build())
                .build();
            SearchResponse response = client.search(searchRequest);
            // query命中到的行
            List<Row> rows = response.getRows();
            // 取聚合结果,需要取出嵌套两层的结果
            GroupByFieldResult groupByFieldResult1 = response.getGroupByResults().getAsGroupByFieldResult("任意唯一名字标识_1");
            for (GroupByFieldResultItem resultItem : groupByFieldResult1.getGroupByFieldResultItems()) {
                System.out.println("field_a key:" + resultItem.getKey() + " Count:" + resultItem.getRowCount());
                // 取内部的第二个GroupBy结果
                GroupByFieldResult subGroupByResult = resultItem.getSubGroupByResults().getAsGroupByFieldResult("任意唯一名字标识_2");
                for (GroupByFieldResultItem item : subGroupByResult.getGroupByFieldResultItems()) {
                    System.out.println("field_a " + resultItem.getKey() + " field_d key:" + item.getKey() + " Count:" + item.getRowCount());
                    double sumOf_field_b = item.getSubAggregationResults().getAsSumAggregationResult("任意唯一名字标识_3").getValue();
                    double sumOf_field_c = item.getSubAggregationResults().getAsSumAggregationResult("任意唯一名字标识_4").getValue();
                    System.out.println("sumOf_field_b:" + sumOf_field_b);
                    System.out.println("sumOf_field_c:" + sumOf_field_c);
                }
            }
        }
  • Java示例3
        /**
         * 聚合使用排序示例
         * 使用方法:按顺序添加 GroupBySorter 即可,添加多个 GroupBySorter 的话排序结果按照添加顺序生效。GroupBySorter支持升序和降序两种方式。
         * 默认排序是按照行数降序排列即GroupBySorter.rowCountSortInDesc()
         */
        public void groupByFieldWithSort(SyncClient client) {
            // 构建查询语句
            SearchRequest searchRequest = SearchRequest.newBuilder()
                .tableName("tableName")
                .indexName("indexName")
                .searchQuery(
                    SearchQuery.newBuilder()
                        .query(QueryBuilders.matchAll())
                        .limit(0)
                        .addGroupBy(GroupByBuilders
                            .groupByField("name1", "column_type")
                            //.addGroupBySorter(GroupBySorter.subAggSortInAsc("subName1"))      // 按照子统计的值升序排序
                            .addGroupBySorter(GroupBySorter.groupKeySortInAsc())                // 按照聚合结果中的值key进行升序排序
                            //.addGroupBySorter(GroupBySorter.rowCountSortInDesc())             // 按照聚合结果中的行数RowCount进行降序排序
                            .size(20)
                            .addSubAggregation(AggregationBuilders.min("subName1", "column_price"))
                            .addSubAggregation(AggregationBuilders.max("subName2", "column_price"))
                        )
                        .build())
                .build();
            //执行查询
            SearchResponse resp = client.search(searchRequest);
        }

范围分组

  • 定义:在查询结果上根据一个字段的范围进行分组,字段值在某范围内则会放到一个分组内,最终返回每一个范围中相应的item个数。例如求商品销量时候按这些分组计算每个范围的销量:[0,1k)、[1k,10k)、[10k,正无穷)。
  • 参数说明
    参数 说明
    groupByName 用户给本次统计聚合功能自定义的名字,用于区分不同的统计聚合操作。在使用结果时,需要根据该名字找到本次统计聚合的结果。
    fieldName 聚合作用的字段,仅支持long、double类型。
    range[double_from, double_to) 添加分组的范围。起始值from可以使用最小值Double.MIN_VALUE,结束值to可以使用最大值Double.MAX_VALUE。
    subAggregation和subGroupBy 添加子统计聚合。子统计聚合会根据分组内容进行再一次的统计聚合分析。例如:按销量分组后再按省份分组,即可获得某个销量段内哪个省比重比较大,其实现是GroupByRange内添加一个GroupByField。
  • Java示例
    /**
     * 求商品销量时候按这些分组计算每个范围的销量:[0,1k)、[1k,5k)、[5k,正无穷)。
     */
    public void groupByRange(SyncClient client) {
        // 构建查询语句
        SearchRequest searchRequest = SearchRequest.newBuilder()
            .tableName("tableName")
            .indexName("indexName")
            .searchQuery(
                SearchQuery.newBuilder()
                    .query(QueryBuilders.matchAll())
                    .limit(0)
                    .addGroupBy(GroupByBuilders
                        .groupByRange("name1", "column_number")
                        .addRange(0, 1000)
                        .addRange(1000, 5000)
                        .addRange(5000, Double.MAX_VALUE)
                    )
                    .build())
            .build();
        //执行查询
        SearchResponse resp = client.search(searchRequest);
        //获取统计聚合的结果
        for (GroupByRangeResultItem item : resp.getGroupByResults().getAsGroupByRangeResult("name1").getGroupByRangeResultItems()) {
    
            // 个数
            System.out.println(item.getRowCount());
        }
    }

地理距离分组

  • 定义:在查询结果上根据距离某一个中心点的范围进行分组,距离差值在某范围内则会放到一个分组内,最终返回每一个范围中相应的item个数。例如求距离万达广场这些范围内的人数:[0,1000米)、[1000米,5000米)、[5000米,正无穷)。
  • 参数说明
    参数 说明
    groupByName 用户给本次统计聚合功能自定义的名字,用于区分不同的统计聚合操作。在使用结果时,需要根据该名字找到本次统计聚合的结果。
    fieldName 聚合作用的字段,仅支持geo_point类型。
    origin(double lat, double lon) lat是起始中心点坐标纬度,lon是起始中心点坐标经度。
    range[double_from, double_to) 添加分组的范围,单位为米。起始值from可以使用最小值Double.MIN_VALUE,结束值to可以使用最大值Double.MAX_VALUE。
    subAggregation和subGroupBy 添加子统计聚合。子统计聚合会根据分组内容进行再一次的统计聚合分析。
  • Java示例
    /**
     * 求距离万达广场这些范围内的人数:[0,1000米)、[1000米,5000米)、[5000米,正无穷)。
     */
    public void groupByGeoDistance(SyncClient client) {
        // 构建查询语句
        SearchRequest searchRequest = SearchRequest.newBuilder()
            .tableName("tableName")
            .indexName("indexName")
            .searchQuery(
                SearchQuery.newBuilder()
                    .query(QueryBuilders.matchAll())
                    .limit(0)
                    .addGroupBy(GroupByBuilders
                        .groupByGeoDistance("name1", "column_geo_point")
                        .origin(3.1, 6.5)
                        .addRange(0, 1000)
                        .addRange(1000, 5000)
                        .addRange(5000, Double.MAX_VALUE)
                    )
                    .build())
            .build();
        //执行查询
        SearchResponse resp = client.search(searchRequest);
        //获取统计聚合的结果
        for (GroupByGeoDistanceResultItem item : resp.getGroupByResults().getAsGroupByGeoDistanceResult("name1").getGroupByGeoDistanceResultItems()) {
           // 个数
            System.out.println(item.getRowCount());
        }
    }

过滤条件分组

  • 定义:在查询结果上,按照Filter(即Query)进行分组,然后获得每个Filter匹配到的数量。返回的结果顺序和添加的Filter顺序一致。例如添加三个Filter(销量大于100、产地是浙江、描述中包含浙江关键词),然后获得这三个Filter匹配到的数量。
  • 参数说明
    参数 说明
    groupByName 用户给本次统计聚合功能命名的名字,用于区分不同的统计聚合操作。在使用结果时候,需要根据该名字找到本次统计聚合的结果。
    filter 添加的Filter(Query),使用QueryBuilders进行构建。
    subAggregation和subGroupBy 添加子统计聚合。子统计聚合会根据分组内容进行再一次的统计聚合分析。
  • Java示例
    /**
     * 按照Filter(即Query)进行分组,例如添加三个Filter(销量大于100、产地是浙江、描述中包含浙江关键词),然后获得每个Filter匹配到的数量。
     */
    public void groupByFilter(SyncClient client) {
        // 构建查询语句
        SearchRequest searchRequest = SearchRequest.newBuilder()
            .tableName("tableName")
            .indexName("indexName")
            .searchQuery(
                SearchQuery.newBuilder()
                    .query(QueryBuilders.matchAll())
                    .limit(0)
                    .addGroupBy(GroupByBuilders
                        .groupByFilter("name1")
                        .addFilter(QueryBuilders.range("number").greaterThanOrEqual(100))
                        .addFilter(QueryBuilders.term("place","浙江省"))
                        .addFilter(QueryBuilders.match("text","杭州"))
                    )
                    .build())
            .build();
        //执行查询
        SearchResponse resp = client.search(searchRequest);
        //获取统计聚合的结果, 按照addFilter的顺序。
        for (GroupByFilterResultItem item : resp.getGroupByResults().getAsGroupByFilterResult("name1").getGroupByFilterResultItems()) {
            // 个数
            System.out.println(item.getRowCount());
        }
    }

多个统计聚合

每次请求支持多个统计和聚合一起分析。

说明 多个统计聚合如果复杂度较高可能会影响响应速度。

Java示例

public void multipleGroupBy(SyncClient client) {
    // 构建查询语句
    SearchRequest searchRequest = SearchRequest.newBuilder()
        .tableName("tableName")
        .indexName("indexName")
        .searchQuery(
            SearchQuery.newBuilder()
                .query(QueryBuilders.matchAll())
                .limit(0)
                .addAggregation(AggregationBuilders.min("name1", "long"))
                .addAggregation(AggregationBuilders.sum("name2", "long"))
                .addAggregation(AggregationBuilders.distinctCount("name3", "long"))
                .addGroupBy(GroupByBuilders.groupByField("name4", "type"))
                .addGroupBy(GroupByBuilders.groupByRange("name5", "long").addRange(1, 15))
                .build())
        .build();
    //执行查询
    SearchResponse resp = client.search(searchRequest);
    //获取第1个统计聚合的结果
    System.out.println(resp.getAggregationResults().getAsMinAggregationResult("name1").getValue());
    //获取第2个统计聚合的结果
    System.out.println(resp.getAggregationResults().getAsSumAggregationResult("name2").getValue());
    //获取第3个统计聚合的结果
    System.out.println(resp.getAggregationResults().getAsDistinctCountAggregationResult("name3").getValue());
    //获取第4个统计聚合的结果
    for (GroupByFieldResultItem item : resp.getGroupByResults().getAsGroupByFieldResult("name4").getGroupByFieldResultItems()) {
        // key
        System.out.println(item.getKey());
        // 个数
        System.out.println(item.getRowCount());
    }
    //获取第5个统计聚合的结果
    for (GroupByRangeResultItem item : resp.getGroupByResults().getAsGroupByRangeResult("name4").getGroupByRangeResultItems()) {
        // 个数
        System.out.println(item.getRowCount());
    }
}

嵌套

GroupBy类型的聚合支持嵌套,其内部可以添加Aggregation和GroupBy类型的子统计聚合。GroupBy原本可以一直嵌套下去,为了性能、复杂度等综合考虑,只开放了一定的层数。

嵌套类型的统计聚合使用的场景主要是在分组内再次进行统计聚合,以两层的嵌套为例:
  • GroupBy+SubGroupBy:按省份分组后再按照城市分组,获得每个省份下每个城市的数据。
  • GroupBy+SubAggregation:按照省份分组后再求某一个指标的最大值,获得每一个省的某个指标最大值。

Java示例

/**
 * 嵌套的统计聚合示例:外层2个Aggregation和1个GroupByField,GroupByField中又添加了2个Aggregation和1个GroupByRange
 */
public void subGroupBy(SyncClient client) {
    // 构建查询语句
    SearchRequest searchRequest = SearchRequest.newBuilder()
        .indexName("index_name")
        .tableName("table_name")
        .returnAllColumns(true)
        .searchQuery(
            SearchQuery.newBuilder()
                .query(QueryBuilders.match("textField", "hello"))
                .limit(10)
                .addAggregation(AggregationBuilders.min("name1", "fieldName1"))
                .addAggregation(AggregationBuilders.max("name2", "fieldName2"))
                .addGroupBy(GroupByBuilders
                    .groupByField("name3", "fieldName3")
                    .addSubAggregation(AggregationBuilders.max("subName1", "fieldName4"))
                    .addSubAggregation(AggregationBuilders.sum("subName2", "fieldName5"))
                    .addSubGroupBy(GroupByBuilders
                        .groupByRange("subName3", "fieldName6")
                        .addRange(12, 90)
                        .addRange(100, 900)
                    ))
                .build())
        .build();
    //执行查询
    SearchResponse resp = client.search(searchRequest);
    //第一层的agg结果
    AggregationResults aggResults = resp.getAggregationResults();
    System.out.println(aggResults.getAsMinAggregationResult("name1").getValue());
    System.out.println(aggResults.getAsMaxAggregationResult("name2").getValue());

    //取出第一层的groupByField结果,并同时取出其嵌套的agg结果
    GroupByFieldResult results = resp.getGroupByResults().getAsGroupByFieldResult("someName1");
    for (GroupByFieldResultItem item : results.getGroupByFieldResultItems()) {
        System.out.println("数量:" + item.getRowCount());
        System.out.println("key:" + item.getKey());

        //取出sub统计聚合的结果
        //SubAggregation: min 的结果
        System.out.println(item.getSubAggregationResults().getAsMaxAggregationResult("subName1"));
        //SubAggregation: max 的结果
        System.out.println(item.getSubAggregationResults().getAsSumAggregationResult("subName2"));
        //SubGroupBy: GroupByRange 的结果
        GroupByRangeResult subResults = resp.getGroupByResults().getAsGroupByRangeResult("subName3");
        for (GroupByRangeResultItem subItem : subResults.getGroupByRangeResultItems()) {
            System.out.println("数量:" + subItem.getRowCount());
            System.out.println("key:" + subItem.getKey());
        }
    }
}