统计聚合

本文介绍如何使用表格存储的统计聚合功能进行数据分析。您可以通过统计聚合功能实现求最小值、求最大值、求和、求平均值、统计行数、去重统计行数、百分位统计、按字段值分组、按范围分组、按地理位置分组、按过滤条件分组、直方图统计、日期直方图统计、获取统计聚合分组内的行、嵌套查询等;同时多个统计聚合功能可以组合使用,满足复杂的查询需求。

流程

统计聚合的完整执行流程如下图所示。

fig_agg_pro

统计聚合是在服务端的“查询”结束后执行,服务端会将“查询”阶段命中的所有文档根据查询请求进行统计聚合,因此统计聚合请求相比没有统计聚合的请求会复杂。

功能列表

统计聚合的详细功能请参见下表。

功能

说明

最小值

返回一个字段中的最小值,类似于SQL中的min。

最大值

返回一个字段中的最大值,类似于SQL中的max。

返回数值字段的总数,类似于SQL中的sum。

平均值

返回数值字段的平均值,类似于SQL中的avg。

统计行数

返回指定字段值的数量或者多元索引数据总行数,类似于SQL中的count。

去重统计行数

返回指定字段不同值的数量,类似于SQL中的count(distinct)。

百分位统计

百分位统计常用来统计一组数据的百分位分布情况,例如在日常系统运维中统计每次请求访问的耗时情况时,需要关注系统请求耗时的P25、P50、P90、P99值等分布情况。

字段值分组

根据一个字段的值对查询结果进行分组,相同的字段值放到同一分组内,返回每个分组的值和该值对应的个数。

说明

当分组较大时,按字段值分组可能会存在误差。

多字段分组

根据多个字段对查询结果进行分组,支持使用token进行翻页。

范围分组

根据一个字段的范围对查询结果进行分组,字段值在某范围内放到同一分组内,返回每个范围中相应的item个数。

地理位置分组

根据距离某一个中心点的范围对查询结果进行分组,距离差值在某范围内放到同一分组内,返回每个范围中相应的item个数。

过滤条件分组

按照过滤条件对查询结果进行分组,获取每个过滤条件匹配到的数量,返回结果的顺序和添加过滤条件的顺序一致。

直方图统计

按照指定数据间隔对查询结果进行分组,字段值在相同范围内放到同一分组内,返回每个分组的值和该值对应的个数。

日期直方图统计

对日期字段类型的数据按照指定间隔对查询结果进行分组,字段值在相同范围内放到同一分组内,返回每个分组的值和该值对应的个数。

获取统计聚合分组中的行

对查询结果进行分组后,获取每个分组内的一些行数据,可实现和MySQLANY_VALUE(field)类似的功能。

嵌套

分组类型的统计聚合功能支持嵌套,其内部可以添加子统计聚合。

多个统计聚合

多个统计聚合功能可以组合使用。

说明

当多个统计聚合的复杂度较高时可能会影响响应速度。

相关接口

统计聚合功能的接口为Search

前提条件

您可以使用控制台、命令行工具或者SDK进行统计聚合。进行统计聚合之前,您需要完成如下准备工作。

重要

使用控制台或命令行工具进行操作时只支持部分统计聚合功能,请以实际情况为准。

  • 使用阿里云账号或者具有表格存储操作权限的 RAM 用户进行操作。如果需要为 RAM 用户授权表格存储操作权限,请参见通过RAM PolicyRAM用户授权进行配置。

    使用 SDK 方式和命令行工具方式进行操作时,如果当前无可用 AccessKey,则需要为阿里云账号或者 RAM 用户创建 AccessKey。具体操作,请参见创建AccessKey

  • 已创建数据表。具体操作,请参见数据表操作

  • 已为数据表创建多元索引。具体操作,请参见创建多元索引

  • 使用 SDK 方式进行操作时,还需要完成初始化 Client。具体操作,请参见初始化OTSClient

  • 使用命令行工具方式进行操作前,还需要完成下载并启动命令行工具,然后配置接入实例信息并选择要操作的表。具体操作,请参见下载命令行工具启动并配置接入信息数据表操作

使用控制台

  1. 进入索引管理页签。

    1. 登录表格存储控制台

    2. 在页面上方,选择资源组和地域。

    3. 概览页面,单击实例名称或在操作列单击实例管理

    4. 实例详情页签下的数据表列表页签,单击数据表名称或在操作列单击索引管理

  2. 索引管理页签,单击目标多元索引操作列的搜索

  3. 查询数据对话框,查询数据。

    1. 系统默认返回所有列,如需显示指定属性列,关闭获取所有列并输入需要返回的属性列,多个属性列之间用半角逗号(,)隔开。

      说明

      系统默认会返回数据表的主键列。

    2. 根据需要选择逻辑操作符为 AndOr 或者 Not

      当选择逻辑操作符为 And 时,返回满足指定条件的数据。当选择逻辑操作符为 Or 时,如果配置了单个条件,则返回满足指定条件的数据;如果配置了多个条件,则返回满足任意一个条件的数据。当选择逻辑操作符为 Not 时,返回不满足指定条件的数据。

    3. 选择索引字段,单击添加,然后设置索引字段的查询类型和输入要查询的值。

      您可以根据需要重复此步骤添加多个索引字段的查询配置。

    4. 系统默认关闭排序功能,如需根据指定字段对返回结果进行排序,打开是否排序开关后,根据需要添加要进行排序的字段并配置排序方式。

    5. 系统默认关闭统计功能,当需对指定字段进行数据统计时,请打开是否统计开关后,根据需要添加要进行统计的字段、配置统计类型和统计名称以及按需配置默认值。

      系统支持一次添加多个字段进行统计操作。其中统计类型的可选项包括最小值、最大值、和、平均值、统计行数、去重统计行数,默认值参数用于表示当字段在某行中不存在时的取值。

  4. 单击确定

    符合查询条件的数据和统计结果会显示在索引管理页签中。

使用命令行工具

通过命令行工具执行search命令查询数据时配置Aggregations实现统计聚合,支持的统计类型包括最小值(min)、最大值(max)、和(sum)、平均值(avg)、统计行数(count)等。更多信息,请参见多元索引

  1. 执行search命令使用search_index多元索引查询和分析表中数据,并返回所有建立索引的列。

    search -n search_index --return_all_indexed
  2. 根据系统提示输入查询条件,示例如下:

    以下示例用于查询满足gid列值小于10gid列值精确匹配77中至少一个条件的行数据,并对gid列值求平均值。

    {
        "Offset": -1,
        "Limit": 10,
        "Collapse": null,
        "Sort": null,
        "GetTotalCount": true,
        "Token": null,
        "Query": {
            "Name": "BoolQuery",
            "Query": {
                "MinimumShouldMatch": null,
                "MustQueries": null,
                "MustNotQueries": null,
                "FilterQueries": null,
                "ShouldQueries": [{
                    "Name": "RangeQuery",
                    "Query": {
                        "FieldName": "gid",
                        "From": null,
                        "To": 10,
                        "IncludeLower": false,
                        "IncludeUpper": false
                    }
                }, {
                    "Name": "TermQuery",
                    "Query": {
                        "FieldName": "gid",
                        "Term": 77
                    }
                }]
            }
        },
        "Aggregations": [{
            "Name": "avg",
            "Aggregation": {
                "AggName": "agg1",
                "Field": "gid",
                "MissingValue": null
            }
        }]
    }

使用SDK

您可以通过Java SDKGo SDKPython SDKNode.js SDK.NET SDKPHP SDK使用统计聚合功能。此处以Java SDK为例介绍统计聚合的使用。

重要

统计聚合功能支持的字段类型属于多元索引的字段类型。关于多元索引的字段类型介绍以及多元索引字段类型和数据表字段类型的映射关系的更多信息,请参见数据类型介绍

最小值

返回一个字段中的最小值,类似于SQL中的min。

  • 参数

    参数

    说明

    aggregationName

    自定义的统计聚合名称,用于区分不同的统计聚合,可根据此名称获取本次统计聚合结果。

    fieldName

    用于统计聚合的字段,仅支持 Long、Double 和 Date 类型。

    missing

    当某行数据中的字段为空时,字段值的默认值。

    • 如果未设置 missing 值,则在统计聚合时会忽略该行。

    • 如果设置了 missing 值,则使用 missing 值作为字段值的默认值参与统计聚合。

  • 示例

    /**
     * 商品库中有每一种商品的价格,求产地为浙江省的商品中,价格最低的商品价格是多少。
     * 等效的 SQL 语句是 SELECT min(column_price) FROM product where place_of_production="浙江省"。
     */
    public void min(SyncClient client) {
        //使用 builder 模式构建查询语句。
        {
            SearchRequest searchRequest = SearchRequest.newBuilder()
                    .tableName("<TABLE_NAME>")
                    .indexName("<SEARCH_INDEX_NAME>")
                    .searchQuery(
                            SearchQuery.newBuilder()
                                    .query(QueryBuilders.term("place_of_production", "浙江省"))
                                    .limit(0) //如果只关心统计聚合结果,不关心具体数据,您可以将 limit 设置为 0 来提高性能。
                                    .addAggregation(AggregationBuilders.min("min_agg_1", "column_price").missing(100))
                                    .build())
                    .build();
            //执行查询。
            SearchResponse resp = client.search(searchRequest);
            //获取统计聚合结果。
            System.out.println(resp.getAggregationResults().getAsMinAggregationResult("min_agg_1").getValue());
        }
    
        //使用非 builder 模式构建查询语句。
        {
            SearchRequest searchRequest = new SearchRequest();
            searchRequest.setTableName("<TABLE_NAME>");
            searchRequest.setIndexName("<SEARCH_INDEX_NAME>");
    
            SearchQuery searchQuery = new SearchQuery();
            TermQuery query = new TermQuery();
            query.setTerm(ColumnValue.fromString("浙江省"));
            query.setFieldName("place_of_production");
            //下述注释的 builder 写法等效于上述 TermQuery 构建写法。
            // Query query2 = QueryBuilders.term("place_of_production", "浙江省").build();
    
            searchQuery.setQuery(query);
            searchQuery.setLimit(0);
    
            MinAggregation aggregation = new MinAggregation();
            aggregation.setAggName("min_agg_1");
            aggregation.setFieldName("column_price");
            aggregation.setMissing(ColumnValue.fromLong(100));
            //下述注释的 builder 写法等效于上述 aggregation 构建写法。
            // MinAggregation aggregation2 = AggregationBuilders.min("min_agg_1", "column_price").missing(100).build();
            List<Aggregation> aggregationList = new ArrayList<Aggregation>();
            aggregationList.add(aggregation);
            searchQuery.setAggregationList(aggregationList);
    
            //执行查询。
            SearchResponse resp = client.search(searchRequest);
            //获取统计聚合结果。
            System.out.println(resp.getAggregationResults().getAsMinAggregationResult("min_agg_1").getValue());
        }
    }

最大值

返回一个字段中的最大值,类似于SQL中的max。

  • 参数

    参数

    说明

    aggregationName

    自定义的统计聚合名称,用于区分不同的统计聚合,可根据此名称获取本次统计聚合结果。

    fieldName

    用于统计聚合的字段,仅支持 Long、Double 和 Date 类型。

    missing

    当某行数据中的字段为空时,字段值的默认值。

    • 如果未设置 missing 值,则在统计聚合时会忽略该行。

    • 如果设置了 missing 值,则使用 missing 值作为字段值的默认值参与统计聚合。

  • 示例

    /**
     * 商品库中有每一种商品的价格,求产地为浙江省的商品中,价格最高的商品价格是多少。
     * 等效的 SQL 语句是 SELECT max(column_price) FROM product where place_of_production="浙江省"。
     */
    public void max(SyncClient client) {
        //使用 builder 模式构建查询语句。
        {
            SearchRequest searchRequest = SearchRequest.newBuilder()
                    .tableName("<TABLE_NAME>")
                    .indexName("<SEARCH_INDEX_NAME>")
                    .searchQuery(
                            SearchQuery.newBuilder()
                                    .query(QueryBuilders.term("place_of_production", "浙江省"))
                                    .limit(0) //如果只关心统计聚合结果,不关心具体数据,您可以将 limit 设置为 0 来提高性能。
                                    .addAggregation(AggregationBuilders.max("max_agg_1", "column_price").missing(0))
                                    .build())
                    .build();
            //执行查询。
            SearchResponse resp = client.search(searchRequest);
            //获取统计聚合结果。
            System.out.println(resp.getAggregationResults().getAsMaxAggregationResult("max_agg_1").getValue());
        }
    
        //使用非 builder 模式构建查询语句。
        {
            SearchRequest searchRequest = new SearchRequest();
            searchRequest.setTableName("<TABLE_NAME>");
            searchRequest.setIndexName("<SEARCH_INDEX_NAME>");
    
            SearchQuery searchQuery = new SearchQuery();
            TermQuery query = new TermQuery();
            query.setTerm(ColumnValue.fromString("浙江省"));
            query.setFieldName("place_of_production");
            //下述注释的 builder 写法等效于上述 TermQuery 构建写法。
            // Query query2 = QueryBuilders.term("place_of_production", "浙江省").build();
    
            searchQuery.setQuery(query);
            searchQuery.setLimit(0);
    
            MaxAggregation aggregation = new MaxAggregation();
            aggregation.setAggName("max_agg_1");
            aggregation.setFieldName("column_price");
            aggregation.setMissing(ColumnValue.fromLong(100));
            //下述注释的 builder 写法等效于上述 aggregation 构建写法。
            // MaxAggregation aggregation2 = AggregationBuilders.max("max_agg_1", "column_price").missing(100).build();
            List<Aggregation> aggregationList = new ArrayList<Aggregation>();
            aggregationList.add(aggregation);
            searchQuery.setAggregationList(aggregationList);
    
            //执行查询。
            SearchResponse resp = client.search(searchRequest);
            //获取统计聚合结果。
            System.out.println(resp.getAggregationResults().getAsMaxAggregationResult("max_agg_1").getValue());
        }
    }

返回数值字段的总数,类似于SQL中的sum。

  • 参数

    参数

    说明

    aggregationName

    自定义的统计聚合名称,用于区分不同的统计聚合,可根据此名称获取本次统计聚合结果。

    fieldName

    用于统计聚合的字段,仅支持 Long 和 Double 类型。

    missing

    当某行数据中的字段为空时,字段值的默认值。

    • 如果未设置 missing 值,则在统计聚合时会忽略该行。

    • 如果设置了 missing 值,则使用 missing 值作为字段值的默认值参与统计聚合。

  • 示例

    /**
     * 商品库中有每一种商品的价格,求产地为浙江省的商品中,价格最高的商品价格是多少。
     * 等效的 SQL 语句是 SELECT sum(column_price) FROM product where place_of_production="浙江省"。
     */
    public void sum(SyncClient client) {
        //使用 builder 模式构建查询语句。
        {
            SearchRequest searchRequest = SearchRequest.newBuilder()
                    .tableName("<TABLE_NAME>")
                    .indexName("<SEARCH_INDEX_NAME>")
                    .searchQuery(
                            SearchQuery.newBuilder()
                                    .query(QueryBuilders.term("place_of_production", "浙江省"))
                                    .limit(0) //如果只关心统计聚合结果,不关心具体数据,您可以将 limit 设置为 0 来提高性能。
                                    .addAggregation(AggregationBuilders.sum("sum_agg_1", "column_number").missing(10))
                                    .build())
                    .build();
            //执行查询。
            SearchResponse resp = client.search(searchRequest);
            //获取统计聚合结果。
            System.out.println(resp.getAggregationResults().getAsSumAggregationResult("sum_agg_1").getValue());
        }
    
        // 使用非 builder 模式构建查询语句。
        {
            SearchRequest searchRequest = new SearchRequest();
            searchRequest.setTableName("<TABLE_NAME>");
            searchRequest.setIndexName("<SEARCH_INDEX_NAME>");
    
            SearchQuery searchQuery = new SearchQuery();
            TermQuery query = new TermQuery();
            query.setTerm(ColumnValue.fromString("浙江省"));
            query.setFieldName("place_of_production");
            //下述注释的 builder 写法等效于上述 TermQuery 构建写法。
            // Query query2 = QueryBuilders.term("place_of_production", "浙江省").build();
    
            searchQuery.setQuery(query);
            searchQuery.setLimit(0);
    
            SumAggregation aggregation = new SumAggregation();
            aggregation.setAggName("sum_agg_1");
            aggregation.setFieldName("column_number");
            aggregation.setMissing(ColumnValue.fromLong(100));
            //下述注释的 builder 写法等效于上述 aggregation 构建写法。
            // SumAggregation aggregation2 = AggregationBuilders.sum("sum_agg_1", "column_number").missing(10).build();
            List<Aggregation> aggregationList = new ArrayList<Aggregation>();
            aggregationList.add(aggregation);
            searchQuery.setAggregationList(aggregationList);
    
            //执行查询。
            SearchResponse resp = client.search(searchRequest);
            //获取统计聚合结果。
            System.out.println(resp.getAggregationResults().getAsSumAggregationResult("sum_agg_1").getValue());
        }
    }

平均值

返回数值字段的平均值,类似于SQL中的avg。

  • 参数

    参数

    说明

    aggregationName

    自定义的统计聚合名称,用于区分不同的统计聚合,可根据此名称获取本次统计聚合结果。

    fieldName

    用于统计聚合的字段,仅支持 Long、Double 和 Date 类型。

    missing

    当某行数据中的字段为空时,字段值的默认值。

    • 如果未设置 missing 值,则在统计聚合时会忽略该行。

    • 如果设置了 missing 值,则使用 missing 值作为字段值的默认值参与统计聚合。

  • 示例

    /**
     * 商品库中有每一种商品的售出数量,求产地为浙江省的商品中,平均价格是多少。
     * 等效的 SQL 语句是 SELECT avg(column_price) FROM product where place_of_production="浙江省"。
     */
    public void avg(SyncClient client) {
        //使用 builder 模式构建查询语句。
        {
            SearchRequest searchRequest = SearchRequest.newBuilder()
                    .tableName("<TABLE_NAME>")
                    .indexName("<SEARCH_INDEX_NAME>")
                    .searchQuery(
                            SearchQuery.newBuilder()
                                    .query(QueryBuilders.term("place_of_production", "浙江省"))
                                    .limit(0) //如果只关心统计聚合结果,不关心具体数据,您可以将 limit 设置为 0 来提高性能。
                                    .addAggregation(AggregationBuilders.avg("avg_agg_1", "column_price"))
                                    .build())
                    .build();
            //执行查询。
            SearchResponse resp = client.search(searchRequest);
            //获取统计聚合结果。
            System.out.println(resp.getAggregationResults().getAsAvgAggregationResult("avg_agg_1").getValue());
        }
    
        //使用非 builder 模式构建查询语句。
        {
            SearchRequest searchRequest = new SearchRequest();
            searchRequest.setTableName("<TABLE_NAME>");
            searchRequest.setIndexName("<SEARCH_INDEX_NAME>");
    
            SearchQuery searchQuery = new SearchQuery();
            TermQuery query = new TermQuery();
            query.setTerm(ColumnValue.fromString("浙江省"));
            query.setFieldName("place_of_production");
            //下述注释的 builder 写法等效于上述 TermQuery 构建写法。
            // Query query2 = QueryBuilders.term("place_of_production", "浙江省").build();
    
            searchQuery.setQuery(query);
            searchQuery.setLimit(0);
    
            AvgAggregation aggregation = new AvgAggregation();
            aggregation.setAggName("avg_agg_1");
            aggregation.setFieldName("column_price");
            //下述注释的 builder 写法等效于上述 aggregation 构建写法。
            // AvgAggregation aggregation2 = AggregationBuilders.avg("avg_agg_1", "column_price").build();
            List<Aggregation> aggregationList = new ArrayList<Aggregation>();
            aggregationList.add(aggregation);
            searchQuery.setAggregationList(aggregationList);
    
            //执行查询。
            SearchResponse resp = client.search(searchRequest);
            //获取统计聚合结果。
            System.out.println(resp.getAggregationResults().getAsAvgAggregationResult("avg_agg_1").getValue());
        }
    }                    

统计行数

返回指定字段值的数量或者多元索引数据总行数,类似于SQL中的count。

说明

通过如下方式可以统计多元索引数据总行数或者某个 query 匹配的行数。

  • 使用统计聚合的 count 功能,在请求中设置 count(*)。

  • 使用 query 功能的匹配行数,在 query 中设置 setGetTotalCount(true);如果需要统计多元索引数据总行数,则使用 MatchAllQuery。

如果需要获取多元索引数据某列出现的次数,则使用 count(列名),可应用于稀疏列的场景。

  • 参数

    参数

    说明

    aggregationName

    自定义的统计聚合名称,用于区分不同的统计聚合,可根据此名称获取本次统计聚合结果。

    fieldName

    用于统计聚合的字段,仅支持 Long、Double、Boolean、Keyword、Geo_point 和 Date 类型。

  • 示例

    /**
     * 商家库中有每一种商家的惩罚记录,求浙江省的商家中,有惩罚记录的一共有多少个商家。如果商家没有惩罚记录,则商家信息中不存在该字段。
     * 等效的 SQL 语句是 SELECT count(column_history) FROM product where place_of_production="浙江省"。
     */
    public void count(SyncClient client) {
        //使用 builder 模式构建查询语句。
        {
            SearchRequest searchRequest = SearchRequest.newBuilder()
                    .tableName("<TABLE_NAME>")
                    .indexName("<SEARCH_INDEX_NAME>")
                    .searchQuery(
                            SearchQuery.newBuilder()
                                    .query(QueryBuilders.term("place_of_production", "浙江省"))
                                    .limit(0) //如果只关心统计聚合结果,不关心具体数据,您可以将 limit 设置为 0 来提高性能。
                                    .addAggregation(AggregationBuilders.count("count_agg_1", "column_history"))
                                    .build())
                    .build();
            //执行查询。
            SearchResponse resp = client.search(searchRequest);
            //获取统计聚合结果。
            System.out.println(resp.getAggregationResults().getAsCountAggregationResult("count_agg_1").getValue());
        }
    
        //使用非 builder 模式构建查询语句。
        {
            SearchRequest searchRequest = new SearchRequest();
            searchRequest.setTableName("<TABLE_NAME>");
            searchRequest.setIndexName("<SEARCH_INDEX_NAME>");
    
            SearchQuery searchQuery = new SearchQuery();
            TermQuery query = new TermQuery();
            query.setTerm(ColumnValue.fromString("浙江省"));
            query.setFieldName("place_of_production");
            //下述注释的 builder 写法等效于上述 TermQuery 构建写法。
            // Query query2 = QueryBuilders.term("place_of_production", "浙江省").build();
    
            searchQuery.setQuery(query);
            searchQuery.setLimit(0);
    
            CountAggregation aggregation = new CountAggregation();
            aggregation.setAggName("count_agg_1");
            aggregation.setFieldName("column_history");
            //下述注释的 builder 写法等效于上述 aggregation 构建写法。
            // CountAggregation aggregation2 = AggregationBuilders.count("count_agg_1", "column_history").build();
            List<Aggregation> aggregationList = new ArrayList<Aggregation>();
            aggregationList.add(aggregation);
            searchQuery.setAggregationList(aggregationList);
    
            //执行查询。
            SearchResponse resp = client.search(searchRequest);
            //获取统计聚合结果。
            System.out.println(resp.getAggregationResults().getAsCountAggregationResult("count_agg_1").getValue());
        }
    }

去重统计行数

返回指定字段不同值的数量,类似于SQL中的count(distinct)

说明

去重统计行数的计算结果是个近似值。

  • 当去重统计行数小于 1 万时,计算结果接近精确值。

  • 当去重统计行数达到 1 亿时,计算结果的误差为 2% 左右。

  • 参数

    参数

    说明

    aggregationName

    自定义的统计聚合名称,用于区分不同的统计聚合,可根据此名称获取本次统计聚合结果。

    fieldName

    用于统计聚合的字段,仅支持 Long、Double、Boolean、Keyword、Geo_point 和 Date 类型。

    missing

    当某行数据中的字段为空时,字段值的默认值。

    • 如果未设置 missing 值,则在统计聚合时会忽略该行。

    • 如果设置了 missing 值,则使用 missing 值作为字段值的默认值参与统计聚合。

  • 示例

    /**
     * 求所有商品的产地一共来自多少个省份。
     * 等效的 SQL 语句是 SELECT count(distinct column_place) FROM product。
     */
    public void distinctCount(SyncClient client) {
        //使用 builder 模式构建查询语句。
        {
            SearchRequest searchRequest = SearchRequest.newBuilder()
                    .tableName("<TABLE_NAME>")
                    .indexName("<SEARCH_INDEX_NAME>")
                    .searchQuery(
                            SearchQuery.newBuilder()
                                    .query(QueryBuilders.matchAll())
                                    .limit(0) //如果只关心统计聚合结果,不关心具体数据,您可以将 limit 设置为 0 来提高性能。
                                    .addAggregation(AggregationBuilders.distinctCount("dis_count_agg_1", "column_place"))
                                    .build())
                    .build();
            //执行查询。
            SearchResponse resp = client.search(searchRequest);
            //获取统计聚合结果。
            System.out.println(resp.getAggregationResults().getAsDistinctCountAggregationResult("dis_count_agg_1").getValue());
        }
    
        //使用非 builder 模式构建查询语句。
        {
            SearchRequest searchRequest = new SearchRequest();
            searchRequest.setTableName("<TABLE_NAME>");
            searchRequest.setIndexName("<SEARCH_INDEX_NAME>");
    
            SearchQuery searchQuery = new SearchQuery();
            MatchAllQuery query = new MatchAllQuery();
            //下述注释的 builder 写法等效于上述 TermQuery 构建写法。
            // Query query2 = QueryBuilders.matchAll().build();
    
            searchQuery.setQuery(query);
            searchQuery.setLimit(0);
    
            DistinctCountAggregation aggregation = new DistinctCountAggregation();
            aggregation.setAggName("dis_count_agg_1");
            aggregation.setFieldName("column_place");
            //下述注释的 builder 写法等效于上述 aggregation 构建写法。
            // DistinctCountAggregation aggregation2 = AggregationBuilders.distinctCount("dis_count_agg_1", "column_place").build();
            List<Aggregation> aggregationList = new ArrayList<Aggregation>();
            aggregationList.add(aggregation);
            searchQuery.setAggregationList(aggregationList);
    
            //执行查询。
            SearchResponse resp = client.search(searchRequest);
            //获取统计聚合结果。
            System.out.println(resp.getAggregationResults().getAsDistinctCountAggregationResult("dis_count_agg_1").getValue());
        }
    }

百分位统计

百分位统计常用来统计一组数据的百分位分布情况,例如在日常系统运维中统计每次请求访问的耗时情况时,需要关注系统请求耗时的P25、P50、P90、P99值等分布情况。

说明

百分位统计为非精确统计,对不同百分位数值的计算精确度不同,较为极端的百分位数值更加准确,例如 1% 或 99% 的百分位数值会比 50% 的百分位数值更准确。

  • 参数

    参数

    说明

    aggregationName

    自定义的统计聚合名称,用于区分不同的统计聚合,可根据此名称获取本次统计聚合结果。

    fieldName

    用于统计聚合的字段,仅支持 Long、Double 和 Date 类型。

    percentiles

    百分位分布例如 50、90、99,可根据需要设置一个或者多个百分位。

    missing

    当某行数据中的字段为空时,字段值的默认值。

    • 如果未设置 missing 值,则在统计聚合时会忽略该行。

    • 如果设置了 missing 值,则使用 missing 值作为字段值的默认值参与统计聚合。

  • 示例

    /**
     * 分析系统请求耗时百分位数分布情况。
     */
    public void percentilesAgg(SyncClient client) {
        //使用 builder 模式构建查询语句。
        {
            SearchRequest searchRequest = SearchRequest.newBuilder()
                    .tableName("<TABLE_NAME>")
                    .indexName("indexName")
                    .searchQuery(
                            SearchQuery.newBuilder()
                                    .query(QueryBuilders.matchAll())
                                    .limit(0) //如果只关心统计聚合结果,不关心具体数据,您可以将 limit 设置为 0 来提高性能。
                                    .addAggregation(AggregationBuilders.percentiles("percentilesAgg", "latency")
                                            .percentiles(Arrays.asList(25.0d, 50.0d, 99.0d))
                                            .missing(1.0))
                                    .build())
                    .build();
            //执行查询。
            SearchResponse resp = client.search(searchRequest);
            //获取结果。
            PercentilesAggregationResult percentilesAggregationResult = resp.getAggregationResults().getAsPercentilesAggregationResult("percentilesAgg");
            for (PercentilesAggregationItem item : percentilesAggregationResult.getPercentilesAggregationItems()) {
                System.out.println("key:" + item.getKey() + " value:" + item.getValue().asDouble());
            }
        }
    
        //使用非 builder 模式构建查询语句。
        {
            SearchRequest searchRequest = new SearchRequest();
            searchRequest.setTableName("<TABLE_NAME>");
            searchRequest.setIndexName("<SEARCH_INDEX_NAME>");
    
            SearchQuery searchQuery = new SearchQuery();
            MatchAllQuery query = new MatchAllQuery();
            //下述注释的 builder 写法等效于上述 TermQuery 构建写法。
            // Query query2 = QueryBuilders.matchAll().build();
    
            searchQuery.setQuery(query);
            searchQuery.setLimit(0);
    
            PercentilesAggregation aggregation = new PercentilesAggregation();
            aggregation.setAggName("percentilesAgg");
            aggregation.setFieldName("latency");
            aggregation.setPercentiles(Arrays.asList(25.0d, 50.0d, 99.0d));
            //下述注释的 builder 写法等效于上述 aggregation 构建写法。
            // AggregationBuilders.percentiles("percentilesAgg", "latency").percentiles(Arrays.asList(25.0d, 50.0d, 99.0d)).missing(1.0).build();
            List<Aggregation> aggregationList = new ArrayList<Aggregation>();
            aggregationList.add(aggregation);
            searchQuery.setAggregationList(aggregationList);
    
            //执行查询。
            SearchResponse resp = client.search(searchRequest);
            //获取结果。
            PercentilesAggregationResult percentilesAggregationResult = resp.getAggregationResults().getAsPercentilesAggregationResult("percentilesAgg");
            for (PercentilesAggregationItem item : percentilesAggregationResult.getPercentilesAggregationItems()) {
                System.out.println("key:" + item.getKey() + " value:" + item.getValue().asDouble());
            }
        }
    }

字段值分组

根据一个字段的值对查询结果进行分组,相同的字段值放到同一分组内,返回每个分组的值和该值对应的个数。

说明
  • 当分组较大时,按字段值分组可能会存在误差。

  • 要实现多字段分组,您可以通过字段分组嵌套或者多字段分组方式实现。关于两种实现方式的功能对比,请参见附录:多字段分组不同实现方式对比

  • 参数

    参数

    说明

    groupByName

    自定义的统计聚合名称,用于区分不同的统计聚合,可根据此名称获取本次统计聚合结果。

    fieldName

    用于统计聚合的字段,仅支持 Long、Double、Boolean、Keyword 和 Date 类型。

    groupBySorter

    分组中的 item 排序规则,默认按照分组中 item 的数量降序排序,多个排序则按照添加的顺序进行排列。支持的排序类型如下。

    • 按照值的字典序升序排列

    • 按照值的字典序降序排列

    • 按照行数升序排列

    • 按照行数降序排列

    • 按照子统计聚合结果中值升序排列

    • 按照子统计聚合结果中值降序排列

    size

    返回的分组数量,默认值为 10。最大值为 2000。当分组数量超过 2000 时,只会返回前 2000 个分组。

    subAggregation 和 subGroupBy

    子统计聚合,子统计聚合会根据分组内容再进行一次统计聚合分析。

    • 场景

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

    • 方法

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

    • 结果示例

      • 水果:5 个(其中价格的最大值为 15,最小值为 3)

      • 洗漱用品:10 个(其中价格的最大值为 98,最小值为 1)

      • 电子设备:3 个(其中价格的最大值为 8699,最小值为 2300)

      • 其它:15 个(其中价格的最大值为 1000,最小值为 80)

  • 示例

    按单字段分组

    /**
     * 所有商品中每一个类别各有多少个,且统计每一个类别的价格最大值和最小值。
     * 返回结果举例:"水果:5 个(其中价格的最大值为 15,最小值为 3),洗漱用品:10 个(其中价格的最大值为 98,最小值为 1),电子设备:3 个(其中价格的最大值为 8699,最小值为 2300),
     * 其它:15 个(其中价格的最大值为 1000,最小值为 80)"。
     */
    public void groupByField(SyncClient client) {
        //使用 builder 模式构建查询语句。
        {
            SearchRequest searchRequest = SearchRequest.newBuilder()
                    .tableName("<TABLE_NAME>")
                    .indexName("<SEARCH_INDEX_NAME>")
                    .searchQuery(
                            SearchQuery.newBuilder()
                                    .query(QueryBuilders.matchAll())
                                    .limit(0)  //如果只关心统计聚合结果,不关心具体数据,您可以将 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());
            }
        }
    
        //使用非 builder 模式构建查询语句。
        {
            SearchRequest searchRequest = new SearchRequest();
            searchRequest.setTableName("<TABLE_NAME>");
            searchRequest.setIndexName("<SEARCH_INDEX_NAME>");
    
            SearchQuery searchQuery = new SearchQuery();
            MatchAllQuery query = new MatchAllQuery();
            //下述注释的 builder 写法等效于上述 TermQuery 构建写法。
            // Query query2 = QueryBuilders.matchAll().build();
    
            searchQuery.setQuery(query);
            searchQuery.setLimit(0);
    
            GroupByField groupByField = new GroupByField();
            groupByField.setGroupByName("name1");
            groupByField.setFieldName("column_type");
            //设置子统计聚合。
            MinAggregation minAggregation = AggregationBuilders.min("subName1", "column_price").build();
            MaxAggregation maxAggregation = AggregationBuilders.max("subName2", "column_price").build();
            groupByField.setSubAggregations(Arrays.asList(minAggregation, maxAggregation));
    
            //下述注释的 builder 写法等效于上述 aggregation 构建写法。
            // GroupByBuilders.groupByField("name1", "column_type")
            //        .addSubAggregation(AggregationBuilders.min("subName1", "column_price"))
            //        .addSubAggregation(AggregationBuilders.max("subName2", "column_price").build());
            List<GroupBy> groupByList = new ArrayList<GroupBy>();
            groupByList.add(groupByField);
            searchQuery.setGroupByList(groupByList);
            searchRequest.setSearchQuery(searchQuery);
    
            //执行查询。
            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());
            }
        }
    }

    通过字段分组嵌套进行多字段分组

    /**
     * 按照嵌套多字段分组的示例。
     * 多元索引支持通过嵌套使用两个 groupBy 实现与 SQL 中的 groupBy 多字段相似的功能。
     * 等效的 SQL 语句是 select a,d, sum(b),sum(c) from user group by a,d。
     */
    public void GroupByMultiField() {
        SearchRequest searchRequest = SearchRequest.newBuilder()
            .tableName("<TABLE_NAME>")
            .indexName("<SEARCH_INDEX_NAME>")
            .returnAllColumns(true)   //设置为 false 时,指定 addColumnsToGet,性能会高。
            //.addColumnsToGet("col_1","col_2")
            .searchQuery(SearchQuery.newBuilder()
                .query(QueryBuilders.matchAll())   //此处相当于 SQL 中的 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);
        //查询符合条件的行。
        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());
            //获取子统计聚合结果。
            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);
            }
        }
    }

    按字段分组时使用统计聚合排序

    /**
     * 使用统计聚合排序的示例。
     * 使用方法:按顺序添加 GroupBySorter 即可,添加多个 GroupBySorter 时排序结果按照添加顺序生效。GroupBySorter 支持升序和降序两种方式。
     * 默认排序是按照行数降序排列,即 GroupBySorter.rowCountSortInDesc()。
     */
    public void groupByFieldWithSort(SyncClient client) {
        //构建查询语句。
        SearchRequest searchRequest = SearchRequest.newBuilder()
            .tableName("<TABLE_NAME>")
            .indexName("<SEARCH_INDEX_NAME>")
            .searchQuery(
                SearchQuery.newBuilder()
                    .query(QueryBuilders.matchAll())
                    .limit(0)
                    .addGroupBy(GroupByBuilders
                        .groupByField("name1", "column_type")
                        //.addGroupBySorter(GroupBySorter.subAggSortInAsc("subName1")) //按照子统计聚合结果中的值升序排序。
                        .addGroupBySorter(GroupBySorter.groupKeySortInAsc())           //按照统计聚合结果中的值升序排序。
                        //.addGroupBySorter(GroupBySorter.rowCountSortInDesc())        //按照统计聚合结果中的行数降序排序。
                        .size(20)
                        .addSubAggregation(AggregationBuilders.min("subName1", "column_price"))
                        .addSubAggregation(AggregationBuilders.max("subName2", "column_price"))
                    )
                    .build())
            .build();
        //执行查询。
        SearchResponse resp = client.search(searchRequest);
    }

多字段分组

根据多个字段对查询结果进行分组,支持使用token进行翻页。

说明
  • 目前只有Java SDKGo SDK支持此功能。

  • 要实现多字段分组,您可以通过字段分组嵌套或者多字段分组方式实现。关于两种实现方式的功能对比,请参见附录:多字段分组不同实现方式对比

  • 参数

    参数

    说明

    groupByName

    自定义的统计聚合名称,用于区分不同的统计聚合,可根据此名称获取本次统计聚合结果。

    sources

    多列分组聚合字段对象,最多支持设置 32 列。支持的分组类型如下:

    重要
    • sources 内分组类型的 groupBySorter 仅支持 groupKeySort ,不支持 subAggSort 和 rowCountSort 。

    • 默认降序排序。

    • 当某一列字段值不存在时,系统会返回 NULL。

    • 字段值分组 GroupByField:仅支持设置 groupByName、fieldName 和 groupBySorter 参数。

    • 直方图统计 GroupByHistogram:仅支持设置 groupByName、fieldName、interval 和 groupBySorter 参数

    • 日期直方图统计 GroupByDateHistogram:仅支持设置 groupByName、fieldName、interval、timeZone 和 groupBySorter 参数。

    nextToken

    进行翻页时用于继续获取分组的 token 值。多列的字段值分组请求结果 GroupByCompositeResult 中会返回 nextToken 值,通过 nextToken 可翻页获取全量分组结果。

    size

    返回分组的数量。当满足条件的分组数量超过 size 限制时,您可以通过 nextToken 继续翻页获取分组。

    重要

    当要限制返回的分组数量时,不支持同时配置 size 和 suggestedSize 参数。一般情况下只需配置 size 参数即可。

    如果是用于对接例如 Spark、Presto 等计算引擎的高吞吐场景,则只需配置 suggestedSize 参数。

    suggestedSize

    可设置为大于服务端最大限制的值或 -1, 服务端按实际能力返回实际行数。适用于对接例如 Spark、Presto 等计算引擎的高吞吐场景。

    当该值超过服务端最大值限制后会被修正为最大值。实际返回的分组结果数量为min(suggestedSize, 服务端分组数量限制,总分组数量)

    subAggregation 和 subGroupBy

    子统计聚合,子统计聚合会根据分组内容再进行一次统计聚合分析。

    重要

    GroupByComposite 不支持作为 subGroupBy。

  • 示例

    /**
     * 组合类型分组聚合:根据传入的多个 SourceGroupBy(支持 groupbyField、groupByHistogram 和 groupByDataHistogram)进行分组聚合。
     * 多列的聚合返回结果以扁平化结构返回。
     */
    public static void groupByComposite(SyncClient client) {
        GroupByComposite.Builder compositeBuilder = GroupByBuilders
                .groupByComposite("groupByComposite")
                .size(2000)
                .addSources(GroupByBuilders.groupByField("groupByField", "Col_Keyword")
                        .addGroupBySorter(GroupBySorter.groupKeySortInAsc()).build())
                .addSources(GroupByBuilders.groupByHistogram("groupByHistogram", "Col_Long")
                        .addGroupBySorter(GroupBySorter.groupKeySortInAsc())
                        .interval(5)
                        .build())
                .addSources(GroupByBuilders.groupByDateHistogram("groupByDateHistogram", "Col_Date")
                        .addGroupBySorter(GroupBySorter.groupKeySortInAsc())
                        .interval(5, DateTimeUnit.DAY)
                        .timeZone("+05:30").build());
    
        SearchRequest searchRequest = SearchRequest.newBuilder()
                .indexName("<SEARCH_INDEX_NAME>")
                .tableName("<TABLE_NAME>")
                .returnAllColumnsFromIndex(true)
                .searchQuery(SearchQuery.newBuilder()
                        .addGroupBy(compositeBuilder.build())
                        .build())
                .build();
    
        SearchResponse resp = client.search(searchRequest);
    
        while (true) {
            if (resp.getGroupByResults() == null || resp.getGroupByResults().getResultAsMap().size() == 0) {
                System.out.println("groupByComposite Result is null or empty");
                return;
            }
    
            GroupByCompositeResult result = resp.getGroupByResults().getAsGroupByCompositeResult("groupByComposite");
    
            if(!result.getSourceNames().isEmpty()) {
                for (String sourceGroupByNames: result.getSourceNames()) {
                    System.out.printf("%s\t", sourceGroupByNames);
                }
                System.out.print("rowCount\t\n");
            }
    
    
            for (GroupByCompositeResultItem item : result.getGroupByCompositeResultItems()) {
                for (String value : item.getKeys()) {
                    String val = value == null ? "NULL" : value;
                    System.out.printf("%s\t", val);
    
                }
                System.out.printf("%d\t\n", item.getRowCount());
            }
    
            // 利用 token 继续翻页获取后续分组。
            if (result.getNextToken() != null) {
                searchRequest.setSearchQuery(
                        SearchQuery.newBuilder()
                                .addGroupBy(compositeBuilder.nextToken(result.getNextToken()).build())
                                .build()
                );
                resp = client.search(searchRequest);
            } else {
                break;
            }
        }
    }

范围分组

根据一个字段的范围对查询结果进行分组,字段值在某范围内放到同一分组内,返回每个范围中相应的item个数。

  • 参数

    参数

    说明

    groupByName

    自定义的统计聚合名称,用于区分不同的统计聚合,可根据此名称获取本次统计聚合结果。

    fieldName

    用于统计聚合的字段,仅支持 Long 和 Double 类型。

    range[double_from, double_to)

    分组的范围。

    起始值 double_from 可以使用最小值 Double.MIN_VALUE,结束值 double_to 可以使用最大值 Double.MAX_VALUE。

    subAggregation 和 subGroupBy

    子统计聚合,子统计聚合会根据分组内容再进行一次统计聚合分析。

    例如按销量分组后再按省份分组,即可获得某个销量范围内哪个省比重比较大,实现方法是 GroupByRange 下添加一个 GroupByField。

  • 示例

    /**
     * 求商品销量时按 [0,1000)、[1000,5000)、[5000,Double.MAX_VALUE) 这些分组计算每个范围的销量。
     */
    public void groupByRange(SyncClient client) {
        //构建查询语句。
        SearchRequest searchRequest = SearchRequest.newBuilder()
            .tableName("<TABLE_NAME>")
            .indexName("<SEARCH_INDEX_NAME>")
            .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个数。

  • 参数

    参数

    说明

    groupByName

    自定义的统计聚合名称,用于区分不同的统计聚合,可根据此名称获取本次统计聚合结果。

    fieldName

    用于统计聚合的字段,仅支持 Geo_point 类型。

    origin(double lat, double lon)

    起始中心点的经纬度。

    double lat 是起始中心点纬度,double lon 是起始中心点经度。

    range[double_from, double_to)

    分组的范围,单位为米。

    起始值 double_from 可以使用最小值 Double.MIN_VALUE,结束值 double_to 可以使用最大值 Double.MAX_VALUE。

    subAggregation 和 subGroupBy

    子统计聚合,子统计聚合会根据分组内容再进行一次统计聚合分析。

  • 示例

    /**
     * 求距离万达广场 [0,1000)、[1000,5000)、[5000,Double.MAX_VALUE) 这些范围内的人数,距离的单位为米。
     */
    public void groupByGeoDistance(SyncClient client) {
        //构建查询语句。
        SearchRequest searchRequest = SearchRequest.newBuilder()
            .tableName("<TABLE_NAME>")
            .indexName("<SEARCH_INDEX_NAME>")
            .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());
        }
    }

过滤条件分组

按照过滤条件对查询结果进行分组,获取每个过滤条件匹配到的数量,返回结果的顺序和添加过滤条件的顺序一致。

  • 参数

    参数

    说明

    groupByName

    自定义的统计聚合名称,用于区分不同的统计聚合,可根据此名称获取本次统计聚合结果。

    filter

    过滤条件,返回结果的顺序和添加过滤条件的顺序一致。

    subAggregation 和 subGroupBy

    子统计聚合,子统计聚合会根据分组内容再进行一次统计聚合分析。

  • 示例

    /**
     * 按照过滤条件进行分组,例如添加三个过滤条件(销量大于 100、产地是浙江省、描述中包含杭州关键词),然后获取每个过滤条件匹配到的数量。
     */
    public void groupByFilter(SyncClient client) {
        //构建查询语句。
        SearchRequest searchRequest = SearchRequest.newBuilder()
            .tableName("<TABLE_NAME>")
            .indexName("<SEARCH_INDEX_NAME>")
            .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);
        //按照过滤条件的顺序获取的统计聚合结果。
        for (GroupByFilterResultItem item : resp.getGroupByResults().getAsGroupByFilterResult("name1").getGroupByFilterResultItems()) {
            //打印个数。
            System.out.println(item.getRowCount());
        }
    }

直方图统计

按照指定数据间隔对查询结果进行分组,字段值在相同范围内放到同一分组内,返回每个分组的值和该值对应的个数。

  • 参数

    参数

    说明

    groupByName

    自定义的统计聚合名称,用于区分不同的统计聚合,可根据此名称获取本次统计聚合结果。

    fieldName

    用于统计聚合的字段,仅支持 Long 和 Double 类型。

    interval

    统计间隔。

    fieldRange[min,max]

    统计范围,与 interval 参数配合使用限制分组的数量。(fieldRange.max-fieldRange.min)/interval的值不能超过 2000。

    minDocCount

    最小行数。当分组中的行数小于最小行数时,不会返回此分组的统计结果。

    missing

    当某行数据中的字段为空时,字段值的默认值。

    • 如果未设置 missing 值,则在统计聚合时会忽略该行。

    • 如果设置了 missing 值,则使用 missing 值作为字段值的默认值参与统计聚合。

  • 示例

    /**
     * 统计不同年龄段用户数量分布情况。
     */
    public static void groupByHistogram(SyncClient client) {
        //构建查询语句。
        SearchRequest searchRequest = SearchRequest.newBuilder()
            .tableName("<TABLE_NAME>")
            .indexName("<SEARCH_INDEX_NAME>")
            .searchQuery(
                SearchQuery.newBuilder()
                    .addGroupBy(GroupByBuilders
                        .groupByHistogram("groupByHistogram", "age")
                        .interval(10)
                        .minDocCount(0L)
                        .addFieldRange(0, 99))
                    .build())
            .build();
        //执行查询。
        SearchResponse resp = ots.search(searchRequest);
        //获取直方图的统计聚合结果。
        GroupByHistogramResult results = resp.getGroupByResults().getAsGroupByHistogramResult("groupByHistogram");
        for (GroupByHistogramItem item : results.getGroupByHistogramItems()) {
            System.out.println("key:" + item.getKey().asLong() + " value:" + item.getValue());
        }
    }

日期直方图统计

对日期字段类型的数据按照指定间隔对查询结果进行分组,字段值在相同范围内放到同一分组内,返回每个分组的值和该值对应的个数。

重要

表格存储Java SDK5.16.1版本开始支持此功能。关于Java SDK历史迭代版本的更多信息,请参见Java SDK历史迭代版本

  • 参数

    参数

    说明

    groupByName

    自定义的统计聚合名称,用于区分不同的统计聚合,可根据此名称获取本次统计聚合结果。

    fieldName

    用于统计聚合的字段,仅支持 Date 类型。

    重要

    表格存储 Java SDK 版本从 5.13.9 版本开始支持多元索引 Date 类型。

    interval

    统计间隔。

    fieldRange[min,max]

    统计范围,与 interval 参数配合使用限制分组的数量。(fieldRange.max-fieldRange.min)/interval的值不能超过 2000。

    minDocCount

    最小行数。当分组中的行数小于最小行数时,不会返回此分组的统计结果。

    missing

    当某行数据中的字段为空时,字段值的默认值。

    • 如果未设置 missing 值,则在统计聚合时会忽略该行。

    • 如果设置了 missing 值,则使用 missing 值作为字段值的默认值参与统计聚合。

    timeZone

    时区。格式为+hh:mm或者-hh:mm,例如+08:00-09:00。只有当字段数据类型为 Date 时才需要配置。

    当 Date 类型字段的 Format 未设置时区信息时,可能会导致聚合结果存在 N 小时的偏移,此时请设置 timeZone 来解决该问题。

  • 示例

    /**
     * 统计 2017-05-01 10:00 到 2017-05-21 13:00:00 时间段内每天的 col_date 数据分布情况。
     */
    public static void groupByDateHistogram(SyncClient client) {
        //构建查询语句。
        SearchRequest searchRequest = SearchRequest.newBuilder()
        .returnAllColumns(false)
        .indexName("<SEARCH_INDEX_NAME>")
        .tableName("<TABLE_NAME>")
        .searchQuery(
            SearchQuery.newBuilder()
            .query(QueryBuilders.matchAll())
            .limit(0)
            .getTotalCount(false)
            .addGroupBy(GroupByBuilders
                        .groupByDateHistogram("groupByDateHistogram", "col_date")
                        .interval(1, DateTimeUnit.DAY)
                        .minDocCount(1)
                        .missing("2017-05-01 13:01:00")
                        .fieldRange("2017-05-01 10:00", "2017-05-21 13:00:00"))
            .build())
        .build();
        //执行查询。
        SearchResponse resp = ots.search(searchRequest);
        //获取直方图的统计聚合结果。
        List<GroupByDateHistogramItem> items = resp.getGroupByResults().getAsGroupByDateHistogramResult("groupByDateHistogram").getGroupByDateHistogramItems();
        for (GroupByDateHistogramItem item : items) {
            System.out.printf("millisecondTimestamp:%d, count:%d \n", item.getTimestamp(), item.getRowCount());
        }
    }

获取统计聚合分组中的行

对查询结果进行分组后,获取每个分组内的一些行数据,可实现和MySQLANY_VALUE(field)类似的功能。

说明

获取统计聚合分组中的行时,如果多元索引中包含嵌套类型、地理位置类型或者数组类型的字段,则返回结果中只会包含主键信息,请手动反查数据表获取所需字段。

  • 参数

    参数

    说明

    aggregationName

    自定义的统计聚合名称,用于区分不同的统计聚合,可根据此名称获取本次统计聚合结果。

    limit

    每个分组内最多返回的数据行数,默认返回1行数据。

    sort

    分组内数据的排序方式。

    columnsToGet

    指定返回的字段,仅支持多元索引中的字段,且不支持数组、Date、Geopoint 和 Nested 类型的字段。

    该参数复用 SearchRequest 中的 columnsToGet 参数,在 SearchRequest 中指定 columnsToGet 即可。

  • 示例

    /**
     * 某学校有一个活动报名表,活动报名表中包含学生姓名、班级、班主任、班长等信息,如果希望按班级进行分组,以查看每个班级的报名情况,同时获取班级的属性信息。
     * 等效的 SQL 语句是 select className, teacher, monitor, COUNT(*) as number from table GROUP BY className。
     */
    public void testTopRows(SyncClient client) {
        SearchRequest searchRequest = SearchRequest.newBuilder()
                .indexName("<SEARCH_INDEX_NAME>")
                .tableName("<TABLE_NAME>")
                .searchQuery(
                        SearchQuery.newBuilder()
                                .query(QueryBuilders.matchAll())
                                .limit(0) 
                                .addGroupBy(GroupByBuilders.groupByField("groupName", "className")
                                        .size(5)  //返回的分组数量,最大值请参见“多元索引使用限制”文档中的限制性“GroupByField 返回的分组个数”。
                                        .addSubAggregation(AggregationBuilders.topRows("topRowsName")
                                                .limit(1)
                                                .sort(new Sort(Arrays.asList(new FieldSort("teacher", SortOrder.DESC)))) // topRows 的排序。
                                        )
                                )
                                .build())
                .addColumnsToGet(Arrays.asList("teacher", "monitor"))
                .build();
        SearchResponse resp = client.search(searchRequest);
        List<GroupByFieldResultItem> items = resp.getGroupByResults().getAsGroupByFieldResult("groupName").getGroupByFieldResultItems();
        for (GroupByFieldResultItem item : items) {
            String className = item.getKey();
            long number = item.getRowCount();
            List<Row> topRows = item.getSubAggregationResults().getAsTopRowsAggregationResult("topRowsName").getRows();
            Row row = topRows.get(0);
            String teacher = row.getLatestColumn("teacher").getValue().asString();
            String monitor = row.getLatestColumn("monitor").getValue().asString();
        }
    }

嵌套

分组类型的统计聚合功能支持嵌套,其内部可以添加子统计聚合。

主要用于在分组内再次进行统计聚合,以两层的嵌套为例:

  • GroupBy+SubGroupBy:按省份分组后再按照城市分组,获取每个省份下每个城市的数据。

  • GroupBy+SubAggregation:按照省份分组后再求某个指标的最大值,获取每个省的某个指标最大值。

说明

为了性能、复杂度等综合考虑,嵌套的层级只开放了一定的层数。更多信息,请参见多元索引限制

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);
    //获取第一层求最小值和求最大值的统计聚合结果。
    AggregationResults aggResults = resp.getAggregationResults();
    System.out.println(aggResults.getAsMinAggregationResult("name1").getValue());
    System.out.println(aggResults.getAsMaxAggregationResult("name2").getValue());

    //获取第一层按字段值分组的统计聚合结果,并同时获取其嵌套的子统计聚合结果。
    GroupByFieldResult results = resp.getGroupByResults().getAsGroupByFieldResult("name3");
    for (GroupByFieldResultItem item : results.getGroupByFieldResultItems()) {
        System.out.println("数量:" + item.getRowCount());
        System.out.println("key:" + item.getKey());

        //获取子统计聚合结果。
        //打印求最大值的子统计聚合结果。
        System.out.println(item.getSubAggregationResults().getAsMaxAggregationResult("subName1"));
        //打印求和的子统计聚合结果。
        System.out.println(item.getSubAggregationResults().getAsSumAggregationResult("subName2"));
        //打印按范围分组的子统计聚合结果。
        GroupByRangeResult subResults = item.getSubGroupByResults().getAsGroupByRangeResult("subName3");
        for (GroupByRangeResultItem subItem : subResults.getGroupByRangeResultItems()) {
            System.out.println(String.format("from:%s, to:%s, count:%s", subItem.getFrom(), subItem.getTo(), subItem.getRowCount()));
        }
    }
}

多个统计聚合

多个统计聚合功能可以组合使用。

说明

当多个统计聚合的复杂度较高时可能会影响响应速度。

组合使用多个 Aggregation

public void multipleAggregation(SyncClient client) {
    //构建查询语句。
    SearchRequest searchRequest = SearchRequest.newBuilder()
        .tableName("<TABLE_NAME>")
        .indexName("<SEARCH_INDEX_NAME>")
        .searchQuery(
            SearchQuery.newBuilder()
                .query(QueryBuilders.matchAll())
                .limit(0) 
                .addAggregation(AggregationBuilders.min("name1", "long"))
                .addAggregation(AggregationBuilders.sum("name2", "long"))
                .addAggregation(AggregationBuilders.distinctCount("name3", "long"))
                .build())
        .build();
    //执行查询。
    SearchResponse resp = client.search(searchRequest);
    //获取求最小值的统计聚合结果。
    System.out.println(resp.getAggregationResults().getAsMinAggregationResult("name1").getValue());
    //获取求和的统计聚合结果。
    System.out.println(resp.getAggregationResults().getAsSumAggregationResult("name2").getValue());
    //获取去重统计行数的统计聚合结果。
    System.out.println(resp.getAggregationResults().getAsDistinctCountAggregationResult("name3").getValue());
}

组合使用 Aggregation 和 GroupBy

public void multipleGroupBy(SyncClient client) {
    //构建查询语句。
    SearchRequest searchRequest = SearchRequest.newBuilder()
        .tableName("<TABLE_NAME>")
        .indexName("<SEARCH_INDEX_NAME>")
        .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);
    //获取求最小值的统计聚合结果。
    System.out.println(resp.getAggregationResults().getAsMinAggregationResult("name1").getValue());
    //获取求和的统计聚合结果。
    System.out.println(resp.getAggregationResults().getAsSumAggregationResult("name2").getValue());
    //获取去重统计行数的统计聚合结果。
    System.out.println(resp.getAggregationResults().getAsDistinctCountAggregationResult("name3").getValue());
    //获取按字段值分组的统计聚合结果。
    for (GroupByFieldResultItem item : resp.getGroupByResults().getAsGroupByFieldResult("name4").getGroupByFieldResultItems()) {
        //打印 key。
        System.out.println(item.getKey());
        //打印个数。
        System.out.println(item.getRowCount());
    }
    //获取按范围分组的统计聚合结果。
    for (GroupByRangeResultItem item : resp.getGroupByResults().getAsGroupByRangeResult("name5").getGroupByRangeResultItems()) {
        //打印个数。
        System.out.println(item.getRowCount());
    }
}

附录:多字段分组不同实现方式对比

如果要根据多个字段对查询结果进行分组,您可以通过字段分组嵌套(即嵌套多层 groupBy)或者多字段分组(即直接使用 GroupByComposite)功能实现。具体实现方式的功能对比说明请参见下表。

功能对比

字段分组嵌套

多字段分组

size

2000

2000

字段列限制

最高支持 5 层

最高支持 32 列

翻页

不支持

支持通过 GroupByComposite 中的 nextToken 翻页

分组中 Item 排序规则

  • 按照值的字典序升序或降序排列

  • 按照行数升序或降序排列

  • 按照子统计聚合结果中值升序或降序排列

按照值的字典序升序或降序排列

是否支持子统计聚合

支持

支持

兼容性

日期类型根据 Format 返回

日期类型返回时间戳字符串