使用 Tablestore Java SDK 可对多元索引查询结果计算指标或进行分组,支持直方图、多层分组和获取分组内的行。
前提条件
安装Tablestore Java SDK并初始化客户端。
功能说明
统计聚合在多元索引完成查询后,对所有匹配行计算指标或进行分组。使用指标聚合可计算最小值、最大值、和、平均值、行数、去重行数和百分位数;使用分组可按字段值、多个字段、数值范围、地理距离、过滤条件、数值间隔、日期间隔或地理网格统计行数。分组内还可以继续添加子指标聚合或子分组。
|
类别 |
配置类型 |
功能 |
|
指标聚合 |
MinAggregation |
返回字段的最小值,类似于 SQL 的 |
|
指标聚合 |
MaxAggregation |
返回字段的最大值,类似于 SQL 的 |
|
指标聚合 |
SumAggregation |
返回数值字段的和,类似于 SQL 的 |
|
指标聚合 |
AvgAggregation |
返回字段的平均值,类似于 SQL 的 |
|
指标聚合 |
CountAggregation |
返回指定字段存在值的行数,类似于 SQL 的 |
|
指标聚合 |
DistinctCountAggregation |
返回指定字段不同值的数量,类似于 SQL 的 |
|
指标聚合 |
PercentilesAggregation |
返回指定字段的一个或多个百分位数。 |
|
指标聚合 |
TopRowsAggregation |
在每个分组内按指定顺序返回前若干行。 |
|
分组 |
GroupByField |
按单个字段值分组。 |
|
分组 |
GroupByComposite |
按多个字段分组,并通过翻页凭证读取后续分组。 |
|
分组 |
GroupByRange |
按数值范围分组。 |
|
分组 |
GroupByGeoDistance |
按数据点与中心点之间的距离范围分组。 |
|
分组 |
GroupByFilter |
按多个过滤条件分组。 |
|
分组 |
GroupByHistogram |
按固定数值间隔生成直方图。 |
|
分组 |
GroupByDateHistogram |
按固定日期或时间间隔生成直方图。 |
|
分组 |
GroupByGeoGrid |
按 GeoHash 网格分组。 |
用于统计聚合的多元索引字段必须启用排序与统计聚合。不同聚合类型支持的字段类型不同,具体要求参见对应参数表。多元索引字段类型及其与数据表字段类型的映射关系请参见数据类型。
统计聚合基于查询匹配结果计算。包含统计聚合的请求比仅查询数据的请求复杂;如果不需要返回具体行,可将
limit设置为0。去重行数、百分位数和字段值分组采用近似计算。去重行数小于 1 万时结果接近精确值,达到 1 亿时误差约为 2%。百分位越接近两端通常越准确,例如 P1 或 P99 通常比 P50 更准确。字段值分组并行计算时可能存在少量误差。
多个统计聚合可以组合使用。聚合数量多或嵌套层级深时会增加请求复杂度并影响响应速度。嵌套层级限制请参见多元索引使用限制。
调用 search 方法查询数据,通过 SearchQuery.aggregationList 配置指标聚合,通过 SearchQuery.groupByList 配置分组。
SearchResponse search(SearchRequest request)
以下示例查询多元索引中的全部数据,计算价格的最小值、最大值、和、平均值、行数、去重类别数和 P50,并按类别分组。
SearchQuery searchQuery = SearchQuery.newBuilder()
.query(QueryBuilders.matchAll())
.limit(0)
.addAggregation(AggregationBuilders.min("min_price", "price"))
.addAggregation(AggregationBuilders.max("max_price", "price"))
.addAggregation(AggregationBuilders.sum("sum_price", "price"))
.addAggregation(AggregationBuilders.avg("avg_price", "price"))
.addAggregation(AggregationBuilders.count("price_count", "price"))
.addAggregation(AggregationBuilders.distinctCount(
"category_count", "category"))
.addAggregation(AggregationBuilders.percentiles(
"price_percentiles", "price")
.percentiles(Arrays.asList(50.0)))
.addGroupBy(GroupByBuilders.groupByField(
"category_group", "category").size(10))
.build();
SearchRequest request =
new SearchRequest("example_table", "example_index", searchQuery);
SearchResponse response = client.search(request);
AggregationResults aggregationResults = response.getAggregationResults();
System.out.println(aggregationResults
.getAsMinAggregationResult("min_price").getValue());
System.out.println(aggregationResults
.getAsMaxAggregationResult("max_price").getValue());
System.out.println(aggregationResults
.getAsSumAggregationResult("sum_price").getValue());
System.out.println(aggregationResults
.getAsAvgAggregationResult("avg_price").getValue());
System.out.println(aggregationResults
.getAsCountAggregationResult("price_count").getValue());
System.out.println(aggregationResults
.getAsDistinctCountAggregationResult("category_count").getValue());
System.out.println(aggregationResults
.getAsPercentilesAggregationResult("price_percentiles")
.getPercentilesAggregationItems());
GroupByFieldResult groupResult = response.getGroupByResults()
.getAsGroupByFieldResult("category_group");
for (GroupByFieldResultItem item :
groupResult.getGroupByFieldResultItems()) {
System.out.println(item.getKey() + ": " + item.getRowCount());
}
参数说明
查询请求
request 的类型为 SearchRequest,包含以下参数。
|
名称 |
类型 |
说明 |
|
tableName(必选) |
String |
数据表名称。 |
|
indexName(必选) |
String |
多元索引名称。 |
|
searchQuery(必选) |
SearchQuery |
查询条件和统计聚合配置。 |
|
columnsToGet(可选) |
SearchRequest.ColumnsToGet |
返回列配置。仅 |
|
timeoutInMillisecond(可选) |
int |
请求级查询超时时间,单位为毫秒。默认值为 |
|
routingValues(可选) |
|
自定义路由字段对应的主键值列表。未配置自定义路由时无需设置。 |
查询配置
request.searchQuery 的类型为 SearchQuery,包含以下与统计聚合相关的参数。
|
名称 |
类型 |
说明 |
|
query(必选) |
Query |
统计范围对应的查询条件。对多元索引中的全部数据统计时,设置为 |
|
aggregationList(可选) |
|
指标聚合配置。与 |
|
groupByList(可选) |
|
分组配置。与 |
|
limit(可选) |
Integer |
本次查询返回的最大行数,默认值为 |
|
offset(可选) |
Integer |
本次查询的起始行位置,默认值为 |
|
sort(可选) |
Sort |
查询结果的排序方式,不影响指标聚合和普通分组的计算范围。 |
|
trackTotalCount(可选) |
int |
期望统计的最大匹配行数。设置为 |
|
filter(可选) |
SearchFilter |
对 |
指标聚合
以下参数对象添加到 request.searchQuery.aggregationList[]。aggName 用于从返回结果中获取对应聚合结果,同一请求中的名称必须唯一。
MinAggregation、MaxAggregation 和 AvgAggregation
|
名称 |
类型 |
说明 |
|
aggName(必选) |
String |
聚合名称。 |
|
fieldName(必选) |
String |
聚合字段名称,支持 Long、Double 和 Date 类型。 |
|
missing(可选) |
ColumnValue |
缺少 |
SumAggregation
|
名称 |
类型 |
说明 |
|
aggName(必选) |
String |
聚合名称。 |
|
fieldName(必选) |
String |
聚合字段名称,支持 Long 和 Double 类型。 |
|
missing(可选) |
ColumnValue |
缺少 |
CountAggregation
|
名称 |
类型 |
说明 |
|
aggName(必选) |
String |
聚合名称。 |
|
fieldName(必选) |
String |
要统计非空值行数的字段名称,支持 Long、Double、Boolean、Keyword、Date、IP 和 Geo_point 类型。稀疏列中缺少该字段的行不计入结果。 |
如果要统计查询匹配总行数,在 SearchQuery 中设置 trackTotalCount,并读取 SearchResponse.totalCount。如果要统计多元索引中的全部行,将查询类型设置为 MatchAllQuery。
DistinctCountAggregation
|
名称 |
类型 |
说明 |
|
aggName(必选) |
String |
聚合名称。 |
|
fieldName(必选) |
String |
要去重统计的字段名称,支持 Long、Double、Boolean、Keyword、Date、IP 和 Geo_point 类型。 |
|
missing(可选) |
ColumnValue |
缺少 |
PercentilesAggregation
|
名称 |
类型 |
说明 |
|
aggName(必选) |
String |
聚合名称。 |
|
fieldName(必选) |
String |
聚合字段名称,支持 Long、Double 和 Date 类型。 |
|
percentiles(必选) |
|
要计算的百分位数列表,例如 |
|
missing(可选) |
ColumnValue |
缺少 |
TopRowsAggregation
TopRowsAggregation 作为分组的子聚合使用。
|
名称 |
类型 |
说明 |
|
aggName(必选) |
String |
聚合名称。 |
|
limit(可选) |
Integer |
每个分组内最多返回的行数,默认值为 |
|
sort(可选) |
Sort |
分组内行的排序方式。 |
返回哪些属性列由 request.columnsToGet 控制。要直接从多元索引返回属性列,请在创建多元索引时存储相应字段;未指定返回列时只返回主键。
分组
以下参数对象添加到 request.searchQuery.groupByList[]。groupByName 用于从返回结果中获取对应分组结果,同一请求中的名称必须唯一。
GroupByField
|
名称 |
类型 |
说明 |
|
groupByName(必选) |
String |
分组名称。 |
|
fieldName(必选) |
String |
分组字段名称,支持 Long、Double、Boolean、Keyword、Date 和 IP 类型。 |
|
size(可选) |
Integer |
返回的分组数,默认值为 |
|
minDocCount(可选) |
Long |
分组内最少行数。行数小于该值的分组不返回。 |
|
groupBySorters(可选) |
|
分组排序规则。默认按行数降序排列。多个规则按添加顺序生效。 |
|
subAggregations(可选) |
|
子指标聚合,对每个分组内的数据计算指标。 |
|
subGroupBys(可选) |
|
子分组,对每个父分组内的数据继续分组。 |
groupBySorters[] 支持以下取值。
|
取值 |
说明 |
|
groupKeySortInAsc |
按分组值的字典序升序排列。 |
|
groupKeySortInDesc |
按分组值的字典序降序排列。 |
|
rowCountSortInAsc |
按分组行数升序排列。 |
|
rowCountSortInDesc |
按分组行数降序排列,默认规则。 |
|
subAggSortInAsc |
按指定子指标聚合的值升序排列。 |
|
subAggSortInDesc |
按指定子指标聚合的值降序排列。 |
GroupByComposite
|
名称 |
类型 |
说明 |
|
groupByName(必选) |
String |
分组名称。 |
|
sources(必选) |
|
多字段分组源,最多包含 32 个字段。支持 |
|
nextToken(可选) |
String |
下一页分组凭证。首次请求不设置;返回结果中的 |
|
size(可选) |
Integer |
返回的分组数,默认值为 |
|
suggestedSize(可选) |
Integer |
面向 Spark、Presto 等计算引擎高吞吐场景的软限制。可设置为 |
|
subAggregations(可选) |
|
子指标聚合。 |
|
subGroupBys(可选) |
|
子分组。 |
nextToken 在 Java SDK 中以字符串表示。持久化或跨系统传输时,不要改变字符串内容。
GroupByRange
|
名称 |
类型 |
说明 |
|
groupByName(必选) |
String |
分组名称。 |
|
fieldName(必选) |
String |
分组字段名称,支持 Long 和 Double 类型。 |
|
ranges(必选) |
|
分组范围列表。每个范围为左闭右开区间 |
|
subAggregations(可选) |
|
子指标聚合。 |
|
subGroupBys(可选) |
|
子分组。 |
GroupByGeoDistance
|
名称 |
类型 |
说明 |
|
groupByName(必选) |
String |
分组名称。 |
|
fieldName(必选) |
String |
分组字段名称,仅支持 Geo_point 类型。 |
|
origin(必选) |
GeoPoint |
中心点坐标,构造参数依次为纬度和经度。纬度范围为 |
|
ranges(必选) |
|
距离范围列表,单位为米。每个范围为左闭右开区间 |
|
subAggregations(可选) |
|
子指标聚合。 |
|
subGroupBys(可选) |
|
子分组。 |
GroupByFilter
|
名称 |
类型 |
说明 |
|
groupByName(必选) |
String |
分组名称。 |
|
filters(必选) |
|
过滤条件列表。结果顺序与过滤条件添加顺序一致。 |
|
subAggregations(可选) |
|
子指标聚合。 |
|
subGroupBys(可选) |
|
子分组。 |
GroupByHistogram
|
名称 |
类型 |
说明 |
|
groupByName(必选) |
String |
分组名称。 |
|
fieldName(必选) |
String |
分组字段名称,支持 Long 和 Double 类型。 |
|
interval(必选) |
ColumnValue |
直方图间隔。 |
|
fieldRange(可选) |
FieldRange |
统计范围,包含 |
|
offset(可选) |
ColumnValue |
桶边界相对默认起点的偏移量。 |
|
minDocCount(可选) |
Long |
分组内最少行数。行数小于该值的桶不返回。 |
|
missing(可选) |
ColumnValue |
缺少 |
|
groupBySorters(可选) |
|
桶排序规则。 |
|
subAggregations(可选) |
|
子指标聚合。 |
|
subGroupBys(可选) |
|
子分组。 |
GroupByDateHistogram
Java SDK 5.16.1 及以上版本支持日期直方图统计。Java SDK 5.13.9 及以上版本支持多元索引 Date 类型。版本信息请参见Java SDK 历史迭代版本。
|
名称 |
类型 |
说明 |
|
groupByName(必选) |
String |
分组名称。 |
|
fieldName(必选) |
String |
分组字段名称,仅支持 Date 类型。 |
|
interval(必选) |
DateTimeValue |
日期或时间间隔,由数值和 |
|
fieldRange(可选) |
FieldRange |
统计范围,包含 |
|
minDocCount(可选) |
Long |
分组内最少行数。行数小于该值的桶不返回。 |
|
missing(可选) |
ColumnValue |
缺少 |
|
timeZone(可选) |
String |
时区,格式为 |
|
groupBySorters(可选) |
|
桶排序规则。 |
|
subAggregations(可选) |
|
子指标聚合。 |
|
subGroupBys(可选) |
|
子分组。 |
GroupByGeoGrid
|
名称 |
类型 |
说明 |
|
groupByName(必选) |
String |
分组名称。 |
|
fieldName(必选) |
String |
分组字段名称,仅支持 Geo_point 类型。 |
|
precision(必选) |
GeoHashPrecision |
GeoHash 网格精度,从约 5009 km × 4992 km 的 |
|
size(可选) |
Integer |
返回的网格分组数。 |
|
subAggregations(可选) |
|
子指标聚合。 |
|
subGroupBys(可选) |
|
子分组。 |
返回值
search 方法返回 SearchResponse,与统计聚合相关的核心字段如下。
|
名称 |
类型 |
说明 |
|
aggregationResults |
AggregationResults |
指标聚合结果,通过 |
|
groupByResults |
GroupByResults |
分组结果,通过 |
|
totalCount |
long |
查询匹配行数,通过 |
|
isAllSuccess |
boolean |
是否已成功查询全部索引分区,通过 |
指标聚合结果
|
配置类型 |
结果类型 |
结果字段和获取方法 |
|
MinAggregation |
MinAggregationResult |
|
|
MaxAggregation |
MaxAggregationResult |
|
|
SumAggregation |
SumAggregationResult |
|
|
AvgAggregation |
AvgAggregationResult |
|
|
CountAggregation |
CountAggregationResult |
|
|
DistinctCountAggregation |
DistinctCountAggregationResult |
|
|
PercentilesAggregation |
PercentilesAggregationResult |
|
|
TopRowsAggregation |
TopRowsAggregationResult |
|
分组结果
|
配置类型 |
结果类型 |
核心结果字段 |
|
GroupByField |
GroupByFieldResult |
|
|
GroupByComposite |
GroupByCompositeResult |
|
|
GroupByRange |
GroupByRangeResult |
|
|
GroupByGeoDistance |
GroupByGeoDistanceResult |
|
|
GroupByFilter |
GroupByFilterResult |
|
|
GroupByHistogram |
GroupByHistogramResult |
|
|
GroupByDateHistogram |
GroupByDateHistogramResult |
|
|
GroupByGeoGrid |
GroupByGeoGridResult |
|
场景示例
使用子聚合和子分组
以下示例按类别分组,在每个类别中计算最高价格,并继续按城市分组。分组排序规则按添加顺序生效。
SearchQuery searchQuery = SearchQuery.newBuilder()
.query(QueryBuilders.matchAll())
.limit(0)
.addGroupBy(GroupByBuilders.groupByField(
"category_group", "category")
.size(10)
.addGroupBySorter(GroupBySorter.groupKeySortInAsc())
.addSubAggregation(AggregationBuilders.max(
"max_price", "price"))
.addSubGroupBy(GroupByBuilders.groupByField(
"city_group", "city").size(10)))
.build();
SearchRequest request =
new SearchRequest("example_table", "example_index", searchQuery);
SearchResponse response = client.search(request);
GroupByFieldResult result = response.getGroupByResults()
.getAsGroupByFieldResult("category_group");
for (GroupByFieldResultItem item :
result.getGroupByFieldResultItems()) {
double maxPrice = item.getSubAggregationResults()
.getAsMaxAggregationResult("max_price")
.getValue();
GroupByFieldResult cityResult = item.getSubGroupByResults()
.getAsGroupByFieldResult("city_group");
System.out.println(item.getKey() + ": " + maxPrice);
System.out.println(cityResult.getGroupByFieldResultItems());
}
使用多字段分组并翻页
GroupByComposite 将多列键以扁平结构返回,并支持通过 nextToken 翻页。
GroupByComposite.Builder compositeBuilder = GroupByBuilders
.groupByComposite("category_city_group")
.addSources(GroupByBuilders.groupByField(
"category", "category")
.addGroupBySorter(GroupBySorter.groupKeySortInAsc()))
.addSources(GroupByBuilders.groupByField(
"city", "city")
.addGroupBySorter(GroupBySorter.groupKeySortInAsc()))
.size(100);
String nextToken = null;
do {
GroupByComposite groupBy = nextToken == null
? compositeBuilder.build()
: compositeBuilder.nextToken(nextToken).build();
SearchQuery searchQuery = SearchQuery.newBuilder()
.query(QueryBuilders.matchAll())
.limit(0)
.addGroupBy(groupBy)
.build();
SearchRequest request = new SearchRequest(
"example_table", "example_index", searchQuery);
SearchResponse response = client.search(request);
GroupByCompositeResult result = response.getGroupByResults()
.getAsGroupByCompositeResult("category_city_group");
for (GroupByCompositeResultItem item :
result.getGroupByCompositeResultItems()) {
System.out.println(item.getKeys() + ": " + item.getRowCount());
}
nextToken = result.getNextToken();
} while (nextToken != null);
按范围、距离和过滤条件分组
以下代码展示三种分组的核心配置。它们可以在同一个 SearchQuery 中组合。
GroupByRange priceRanges = GroupByBuilders
.groupByRange("price_ranges", "price")
.addRange(0, 100)
.addRange(100, 500)
.build();
GroupByGeoDistance distanceRanges = GroupByBuilders
.groupByGeoDistance("distance_ranges", "location")
.origin(30.2741, 120.1551)
.addRange(0, 10000)
.addRange(10000, 100000)
.build();
GroupByFilter categoryFilters = GroupByBuilders
.groupByFilter("category_filters")
.addFilter(QueryBuilders.term("category", "books"))
.addFilter(QueryBuilders.term("category", "games"))
.build();
生成数值和日期直方图
以下示例分别按 20 的数值间隔和 1 个月的日期间隔分组。
GroupByHistogram priceHistogram = GroupByBuilders
.groupByHistogram("price_histogram", "price")
.interval(20)
.offset(0)
.minDocCount(1L)
.addFieldRange(0, 100)
.addGroupBySorter(GroupBySorter.groupKeySortInAsc())
.build();
GroupByDateHistogram dateHistogram = GroupByBuilders
.groupByDateHistogram("date_histogram", "event_date")
.interval(1, DateTimeUnit.MONTH)
.fieldRange("2026-01-01", "2026-06-01")
.timeZone("+08:00")
.minDocCount(1L)
.addGroupBySorter(GroupBySorter.groupKeySortInAsc())
.build();
SearchQuery searchQuery = SearchQuery.newBuilder()
.query(QueryBuilders.matchAll())
.limit(0)
.addGroupBy(priceHistogram)
.addGroupBy(dateHistogram)
.build();
SearchResponse response = client.search(new SearchRequest(
"example_table", "example_index", searchQuery));
按地理网格分组
以下示例按约 39 km × 19 km 的 GeoHash 网格统计地理位置字段。
SearchQuery searchQuery = SearchQuery.newBuilder()
.query(QueryBuilders.matchAll())
.limit(0)
.addGroupBy(GroupByBuilders.groupByGeoGrid(
"geo_grid", "location")
.precision(GeoHashPrecision.GHP_39KM_19KM_4)
.size(100))
.build();
SearchResponse response = client.search(new SearchRequest(
"example_table", "example_index", searchQuery));
GroupByGeoGridResult result = response.getGroupByResults()
.getAsGroupByGeoGridResult("geo_grid");
System.out.println(result.getGroupByGeoGridResultItems());
获取分组内的行
以下示例按类别分组,并返回每个类别中价格最高的一行。
SearchQuery searchQuery = SearchQuery.newBuilder()
.query(QueryBuilders.matchAll())
.limit(0)
.addGroupBy(GroupByBuilders.groupByField(
"category_group", "category")
.size(10)
.addSubAggregation(AggregationBuilders.topRows(
"top_price")
.limit(1)
.sort(new Sort(Arrays.asList(
new FieldSort(
"price", SortOrder.DESC))))))
.build();
SearchRequest.ColumnsToGet columnsToGet =
new SearchRequest.ColumnsToGet();
columnsToGet.setColumns(Arrays.asList("category", "price"));
SearchRequest request =
new SearchRequest("example_table", "example_index", searchQuery);
request.setColumnsToGet(columnsToGet);
SearchResponse response = client.search(request);
GroupByFieldResult result = response.getGroupByResults()
.getAsGroupByFieldResult("category_group");
for (GroupByFieldResultItem item :
result.getGroupByFieldResultItems()) {
List<Row> rows = item.getSubAggregationResults()
.getAsTopRowsAggregationResult("top_price")
.getRows();
System.out.println(item.getKey() + ": " + rows);
}
多字段分组方式对比
按多个字段分组时,可嵌套多个 GroupByField,也可直接使用 GroupByComposite。根据是否需要分组翻页、返回结构和排序规则选择。
|
对比项 |
字段分组嵌套 |
多字段分组 |
|
配置方式 |
在父 |
在 |
|
分组数量 |
每层最多返回 2000 个分组。 |
每页最多返回 2000 个分组。 |
|
字段数量 |
最多嵌套 3 层。 |
最多 32 个字段。 |
|
返回结构 |
按父子层级嵌套返回。 |
多列键以扁平列表返回。 |
|
翻页 |
不支持。 |
支持通过 |
|
排序 |
支持按分组值、行数或子聚合值排序。 |
每个分组源仅支持按分组值字典序排序,默认降序。 |
|
子聚合 |
支持。 |
支持。 |
|
Date 字段兼容性 |
按字段定义的日期格式返回分组键。 |
日期分组键以时间戳字符串返回。 |