通过多元索引的统计聚合功能,可对查询结果执行值计算和分组统计。值计算包括最小值、最大值、求和、平均值、统计行数、去重统计行数和百分位统计。分组统计支持按字段值、范围、地理位置、过滤条件、直方图和多字段组合等方式分组。多个统计聚合可组合使用。
Python SDK 5.2.1 及以上版本开始支持统计聚合功能。
功能列表
统计聚合的详细功能请参见下表。
值聚合
功能 | 说明 |
最小值 | 返回一个字段中的最小值,类似于 SQL 中的 min。 |
最大值 | 返回一个字段中的最大值,类似于 SQL 中的 max。 |
和 | 返回数值字段的总数,类似于 SQL 中的 sum。 |
平均值 | 返回数值字段的平均值,类似于 SQL 中的 avg。 |
统计行数 | 返回指定字段值的数量或者多元索引数据总行数,类似于 SQL 中的 count。 |
去重统计行数 | 返回指定字段不同值的数量,类似于 SQL 中的 count(distinct)。 |
百分位统计 | 百分位统计常用来统计一组数据的百分位分布情况,例如在日常系统运维中统计每次请求访问的耗时情况时,需要关注系统请求耗时的 P25、P50、P90、P99 值等分布情况。 |
分组聚合
功能 | 说明 |
字段值分组 | 根据一个字段的值对查询结果进行分组,相同的字段值放到同一分组内,返回每个分组的值和该值对应的个数。 说明 当分组较大时,按字段值分组可能会存在误差。 |
范围分组 | 根据一个字段的范围对查询结果进行分组,字段值在某范围内放到同一分组内,返回每个范围中相应的 item 个数。 |
地理位置分组 | 根据距离某一个中心点的范围对查询结果进行分组,距离差值在某范围内放到同一分组内,返回每个范围中相应的 item 个数。 |
过滤条件分组 | 按照过滤条件对查询结果进行分组,获取每个过滤条件匹配到的数量,返回结果的顺序和添加过滤条件的顺序一致。 |
直方图统计 | 按照指定数据间隔对查询结果进行分组,字段值在相同范围内放到同一分组内,返回每个分组的值和该值对应的个数。 |
多字段分组 | 根据多个字段对查询结果进行组合分组,类似于 SQL 中的 |
值聚合
最小值
返回一个字段中的最小值,类似于 SQL 中的 min。
参数
参数 | 说明 |
name | 自定义的统计聚合名称,用于区分不同聚合并获取对应结果。 |
field | 用于统计聚合的字段,仅支持 Long、Double 和 Date 类型。 |
missing | 字段为空时的默认值。未设置时忽略该行;设置后以该值参与统计聚合。 |
示例
统计年龄为 18 岁的人中得分的最低分数。
query = TermQuery('age', 18)
agg = Min('score', name='min')
search_response = client.search(table_name, index_name,
SearchQuery(query, limit=0, aggs=[agg]),
ColumnsToGet(return_type=ColumnReturnType.ALL_FROM_INDEX))
for agg_result in search_response.agg_results:
print("name: %s, value: %s" % (agg_result.name, str(agg_result.value)))最大值
返回一个字段中的最大值,类似于 SQL 中的 max。
参数
参数 | 说明 |
name | 自定义的统计聚合名称,用于区分不同聚合并获取对应结果。 |
field | 用于统计聚合的字段,仅支持 Long、Double 和 Date 类型。 |
missing | 字段为空时的默认值。未设置时忽略该行;设置后以该值参与统计聚合。 |
示例
统计年龄为 18 岁的人中得分的最高分数。如果某人没有分数,则对应分数的默认值为 0。
query = TermQuery('age', 18)
agg = Max('score', missing_value=0, name='max')
search_response = client.search(table_name, index_name,
SearchQuery(query, limit=0, aggs=[agg]),
ColumnsToGet(return_type=ColumnReturnType.ALL_FROM_INDEX))
for agg_result in search_response.agg_results:
print("name: %s, value: %s" % (agg_result.name, str(agg_result.value)))和
返回数值字段的总数,类似于 SQL 中的 sum。
参数
参数 | 说明 |
name | 自定义的统计聚合名称,用于区分不同聚合并获取对应结果。 |
field | 用于统计聚合的字段,仅支持 Long 和 Double 类型。 |
missing | 字段为空时的默认值。未设置时忽略该行;设置后以该值参与统计聚合。 |
示例
统计年龄为 18 岁的所有人得分的总和。
query = TermQuery('age', 18)
agg = Sum('score', name='sum')
search_response = client.search(table_name, index_name,
SearchQuery(query, limit=0, aggs=[agg]),
ColumnsToGet(return_type=ColumnReturnType.ALL_FROM_INDEX))
for agg_result in search_response.agg_results:
print("name: %s, value: %s" % (agg_result.name, str(agg_result.value)))平均值
返回数值字段的平均值,类似于 SQL 中的 avg。
参数
参数 | 说明 |
name | 自定义的统计聚合名称,用于区分不同聚合并获取对应结果。 |
field | 用于统计聚合的字段,仅支持 Long、Double 和 Date 类型。 |
missing | 字段为空时的默认值。未设置时忽略该行;设置后以该值参与统计聚合。 |
示例
统计年龄为 18 岁的所有人得分的平均分。
query = TermQuery('age', 18)
agg = Avg('score', name='avg')
search_response = client.search(table_name, index_name,
SearchQuery(query, limit=0, aggs=[agg]),
ColumnsToGet(return_type=ColumnReturnType.ALL_FROM_INDEX))
for agg_result in search_response.agg_results:
print("name: %s, value: %s" % (agg_result.name, str(agg_result.value)))统计行数
返回指定字段值的数量或者多元索引数据总行数,类似于 SQL 中的 count。
通过如下方式可以统计多元索引数据总行数或者某个 query 匹配的行数。
使用统计聚合的 count 功能,在请求中设置 count(*)。
使用 query 功能的匹配行数,在 query 中设置 setGetTotalCount(true);如果需要统计多元索引数据总行数,则使用 MatchAllQuery。
如果需要获取多元索引数据某列出现的次数,则使用 count(列名),可应用于稀疏列的场景。
参数
参数 | 说明 |
name | 自定义的统计聚合名称,用于区分不同聚合并获取对应结果。 |
field | 用于统计聚合的字段,仅支持 Long、Double、Boolean、Keyword、Geo_point 和 Date 类型。 |
示例
统计年龄为 18 岁的人中参加考试有分数的人数。
query = TermQuery('age', 18)
agg = Count('score', name='count')
search_response = client.search(table_name, index_name,
SearchQuery(query, limit=0, aggs=[agg]),
ColumnsToGet(return_type=ColumnReturnType.ALL_FROM_INDEX))
for agg_result in search_response.agg_results:
print("name: %s, value: %s" % (agg_result.name, str(agg_result.value)))去重统计行数
返回指定字段不同值的数量,类似于 SQL 中的 count(distinct)。
去重统计行数的计算结果是个近似值。
当去重统计行数小于 1 万时,计算结果接近精确值。
当去重统计行数达到 1 亿时,计算结果的误差为 2% 左右。
参数
参数 | 说明 |
name | 自定义的统计聚合名称,用于区分不同聚合并获取对应结果。 |
field | 用于统计聚合的字段,仅支持 Long、Double、Boolean、Keyword、Geo_point 和 Date 类型。 |
missing | 字段为空时的默认值。未设置时忽略该行;设置后以该值参与统计聚合。 |
示例
去重统计年龄为 18 岁的人中一共有多少种不同的姓名。
query = TermQuery('age', 18)
agg = DistinctCount('name', name='distinct_name')
search_response = client.search(table_name, index_name,
SearchQuery(query, limit=0, aggs=[agg]),
ColumnsToGet(return_type=ColumnReturnType.ALL_FROM_INDEX))
for agg_result in search_response.agg_results:
print("name: %s, value: %s" % (agg_result.name, str(agg_result.value)))百分位统计
百分位统计常用来统计一组数据的百分位分布情况,例如在日常系统运维中统计每次请求访问的耗时情况时,需要关注系统请求耗时的 P25、P50、P90、P99 值等分布情况。
百分位统计为非精确统计,对不同百分位数值的计算精确度不同,较为极端的百分位数值更加准确,例如 1% 或 99% 的百分位数值会比 50% 的百分位数值准确。
参数
参数 | 说明 |
name | 自定义的统计聚合名称,用于区分不同聚合并获取对应结果。 |
field | 用于统计聚合的字段,仅支持 Long、Double 和 Date 类型。 |
percentiles | 百分位值列表,如 50、90、99,支持设置一个或多个。 |
missing_value | 字段为空时的默认值。未设置时忽略该行;设置后以该值参与统计聚合。 |
示例
query = TermQuery('product', '10010')
agg = Percentiles('latency', percentiles_list=[50, 90, 95])
search_response = client.search(table_name, index_name,
SearchQuery(query, limit=0, aggs=[agg]),
ColumnsToGet(return_type=ColumnReturnType.ALL_FROM_INDEX))
for agg_result in search_response.agg_results:
print("name: %s" % agg_result.name)
for item in agg_result.value:
print(" percentile: %s, value: %s" % (str(item.key), str(item.value)))分组聚合
字段值分组
根据一个字段的值对查询结果进行分组,相同的字段值放到同一分组内,返回每个分组的值和该值对应的个数。
当分组较大时,按字段值分组可能会存在误差。
参数
参数 | 说明 |
name | 自定义的统计聚合名称,用于区分不同聚合并获取对应结果。 |
field | 用于统计聚合的字段,仅支持 Long、Double、Boolean、Keyword 和 Date 类型。 |
size | 返回的分组数量,默认值为 10。最大值为 2000。当分组数量超过 2000 时,只会返回前 2000 个分组。 |
group_by_sort | 分组中的 item 排序规则,默认按 item 数量降序排序,设置多个时按添加顺序依次排列。支持按值的字典序、行数、子统计聚合结果值升序或降序排列。 |
sub_aggs 和 sub_group_bys | 子统计聚合,根据分组内容做进一步聚合分析。例如按类别分组后添加 Max 和 Min 子聚合,即可得到每个类别的商品数量及价格的最大值和最小值。 |
示例 1
将年龄为 18 岁的人按分数分组,并获取人数最多的 10 个分数值以及每个分数的人数。
query = TermQuery('age', 18)
group_by = GroupByField('score', size=10)
search_response = client.search(table_name, index_name,
SearchQuery(query, limit=0, group_bys=[group_by]),
ColumnsToGet(return_type=ColumnReturnType.ALL_FROM_INDEX))
for group_by_result in search_response.group_by_results:
print("name: %s" % group_by_result.name)
for item in group_by_result.items:
print(" key: %s, count: %d" % (item.key, item.row_count))示例 2
将年龄为 18 岁的人按分数分组,并获取人数最少的 2 个分数值以及每个分数的人数。
group_by = GroupByField('score', size=2, group_by_sort=[RowCountSort(sort_order=SortOrder.ASC)])
search_response = client.search(table_name, index_name,
SearchQuery(TermQuery('age', 18), limit=0, get_total_count=True, group_bys=[group_by]),
ColumnsToGet(return_type=ColumnReturnType.ALL_FROM_INDEX))
for group_by_result in search_response.group_by_results:
print("name: %s" % group_by_result.name)
for item in group_by_result.items:
print(" key: %s, count: %d" % (item.key, item.row_count))示例 3
将年龄为 18 岁的人按分数分组,并获取人数最多的 2 个分数值、每个分数的人数以及按主键排序前三的人的信息。
sort = RowCountSort(sort_order=SortOrder.DESC)
sub_agg = [TopRows(limit=3, sort=Sort([PrimaryKeySort(sort_order=SortOrder.DESC)]), name='top_rows')]
group_by = GroupByField('score', size=2, group_by_sort=[sort], sub_aggs=sub_agg)
search_response = client.search(table_name, index_name,
SearchQuery(TermQuery('age', 18), limit=0, get_total_count=True, group_bys=[group_by]),
ColumnsToGet(return_type=ColumnReturnType.ALL_FROM_INDEX))
for group_by_result in search_response.group_by_results:
print("name: %s" % group_by_result.name)
for item in group_by_result.items:
print(" key: %s, count: %d" % (item.key, item.row_count))
for sub_agg in item.sub_aggs:
print(" sub_agg: %s" % sub_agg.name)
for entry in sub_agg.value:
print(" value: %s" % str(entry))示例 4
将年龄为 18 岁的人按分数和性别分组。
sort = RowCountSort(sort_order=SortOrder.ASC)
sub_group = GroupByField('sex', size=10, group_by_sort=[sort])
group_by = GroupByField('score', size=10, group_by_sort=[sort], sub_group_bys=[sub_group])
search_response = client.search(table_name, index_name,
SearchQuery(TermQuery('age', 18), limit=0, get_total_count=True, group_bys=[group_by]),
ColumnsToGet(return_type=ColumnReturnType.ALL_FROM_INDEX))
for group_by_result in search_response.group_by_results:
print("name: %s" % group_by_result.name)
for item in group_by_result.items:
print(" key: %s, count: %d" % (item.key, item.row_count))
for sub_group in item.sub_group_bys:
print(" sub_group: %s" % sub_group.name)
for sub_item in sub_group.items:
print(" key: %s, count: %s" % (str(sub_item.key), str(sub_item.row_count)))范围分组
根据一个字段的范围对查询结果进行分组,字段值在某范围内放到同一分组内,返回每个范围中相应的 item 个数。
参数
参数 | 说明 |
name | 自定义的统计聚合名称,用于区分不同聚合并获取对应结果。 |
field | 用于统计聚合的字段,仅支持 Long 和 Double 类型。 |
range[double_from, double_to) | 分组的范围。起始值 double_from 可以使用最小值 Double.MIN_VALUE,结束值 double_to 可以使用最大值 Double.MAX_VALUE。 |
sub_aggs 和 sub_group_bys | 子统计聚合,根据分组内容做进一步聚合分析。例如按销量分组后再按省份分组,即可获得某个销量范围内哪个省比重比较大,实现方法是 GroupByRange 下添加一个 GroupByField。 |
示例
统计年龄为 18 岁的人中得分的分数在 [80, 90) 和 [90, 100) 两个区间段的人数。
query = TermQuery('age', 18)
group_by = GroupByRange(field_name='score', ranges=[(80, 90), (90, 100)])
search_response = client.search(table_name, index_name,
SearchQuery(query, limit=0, group_bys=[group_by]),
ColumnsToGet(return_type=ColumnReturnType.ALL_FROM_INDEX))
for group_by_result in search_response.group_by_results:
print("name: %s" % group_by_result.name)
for item in group_by_result.items:
print(" range: %.1f~%.1f, count: %d" % (item.range_from, item.range_to, item.row_count))地理位置分组
根据距离某一个中心点的范围对查询结果进行分组,距离差值在某范围内放到同一分组内,返回每个范围中相应的 item 个数。
参数
参数 | 说明 |
name | 自定义的统计聚合名称,用于区分不同聚合并获取对应结果。 |
field | 用于统计聚合的字段,仅支持 Geo_point 类型。 |
origin(double lat, double lon) | 起始中心点的经纬度。lat 是起始中心点坐标纬度,lon 是起始中心点坐标经度。 |
range[double_from, double_to) | 分组的范围,单位为米。起始值 double_from 可以使用最小值 Double.MIN_VALUE,结束值 double_to 可以使用最大值 Double.MAX_VALUE。 |
sub_aggs 和 sub_group_bys | 子统计聚合,根据分组内容做进一步聚合分析。 |
示例
统计年龄为 18 岁的人中家庭住址在距离学校一公里以内和一公里到两公里内的人数。其中学校经纬度为(31,116)。
query = TermQuery('age', 18)
group_by = GroupByGeoDistance(field_name='address', origin=GeoPoint(31, 116), ranges=[(0, 1000), (1000, 2000)])
search_response = client.search(table_name, index_name,
SearchQuery(query, limit=0, group_bys=[group_by]),
ColumnsToGet(return_type=ColumnReturnType.ALL_FROM_INDEX))
for group_by_result in search_response.group_by_results:
print("name: %s" % group_by_result.name)
for item in group_by_result.items:
print(" range: %.1f~%.1f, count: %d" % (item.range_from, item.range_to, item.row_count))过滤条件分组
按照过滤条件对查询结果进行分组,获取每个过滤条件匹配到的数量,返回结果的顺序和添加过滤条件的顺序一致。
参数
参数 | 说明 |
name | 自定义的统计聚合名称,用于区分不同聚合并获取对应结果。 |
filter | 过滤条件,返回结果的顺序和添加过滤条件的顺序一致。 |
sub_aggs 和 sub_group_bys | 子统计聚合,根据分组内容做进一步聚合分析。 |
示例
分别统计年龄为 18 岁的人中数学考了 100 分和语文考了 100 分的人数。
query = TermQuery('age', 18)
filter1 = TermQuery('math', 100)
filter2 = TermQuery('chinese', 100)
filters = [filter1, filter2]
group_by = GroupByFilter(filters)
search_response = client.search(table_name, index_name,
SearchQuery(query, limit=0, group_bys=[group_by]),
ColumnsToGet(return_type=ColumnReturnType.ALL_FROM_INDEX))
for group_by_result in search_response.group_by_results:
print("name: %s" % group_by_result.name)
i = 0
for item in group_by_result.items:
print(" filter: %s=%s, count: %d" % (str(filters[i].field_name), str(filters[i].column_value), item.row_count))
i += 1直方图统计
按照指定数据间隔对查询结果进行分组,字段值在相同范围内放到同一分组内,返回每个分组的值和该值对应的个数。
参数
参数 | 说明 |
name | 自定义的统计聚合名称,用于区分不同聚合并获取对应结果。 |
field | 用于统计聚合的字段,仅支持 Long 和 Double 类型。 |
interval | 统计间隔。 |
field_range[min,max] | 统计范围,与 interval 参数配合使用限制分组的数量。(fieldRange.max-fieldRange.min)/interval 的值不能超过 2000。 |
min_doc_count | 最小行数。当分组中的行数小于最小行数时,不会返回此分组的统计结果。 |
missing_value | 字段为空时的默认值。未设置时忽略该行;设置后以该值参与统计聚合。 |
示例
query = TermQuery('product', '10010')
group_by = GroupByHistogram(field_name='latency', interval=100, field_range=FieldRange(0, 10000), missing_value=0)
search_response = client.search(table_name, index_name,
SearchQuery(query, limit=0, group_bys=[group_by]),
ColumnsToGet(return_type=ColumnReturnType.ALL_FROM_INDEX))
for group_by_result in search_response.group_by_results:
print("name: %s" % group_by_result.name)
for item in group_by_result.items:
print(" key: %s, value: %s" % (item.key, item.value))多字段分组
根据多个字段对查询结果进行组合分组,类似于 SQL 中的 GROUP BY column1, column2, ...。与字段值分组不同,多字段分组支持多个数据源同时分组,并支持翻页获取所有分组结果。sources 中最多允许 32 个数据源。
Python SDK 6.4.4 及以上版本开始支持多字段分组功能。
参数
参数 | 说明 |
name | 自定义的统计聚合名称,用于区分不同聚合并获取对应结果。默认值为 |
sources | 数据源列表,支持多个 GroupBy 类型(如 说明
|
size | 每次返回的分组数量。可选参数,默认值为 10,最大值为 2000。精确控制返回的分组条数,超过最大值时服务端会返回错误。不能与 |
next_token | 翻页标记。首次请求时无需设置,后续翻页请求时使用上一次返回结果中的 |
suggested_size | 建议的分组数量。可选参数,支持任意正整数或 -1。建议模式,超过最大值(2000)时自动调整为最大值而不会报错,设置为 -1 表示直接使用最大值。适用于数据探索、批量处理等希望尽可能多返回数据但不希望超限报错的场景。不能与 |
sub_aggs 和 sub_group_bys | 子统计聚合,根据分组内容做进一步聚合分析。 |
返回结果中每个分组的
keys是字符串列表,与 sources 中各数据源的顺序一一对应。当某个数据源对应的字段值为空时,keys中对应位置的值为None。当分组数量较多时,建议设置
size或suggested_size参数并配合next_token进行翻页获取,避免单次返回数据量过大。当返回结果中next_token为None时,表示所有分组结果已获取完毕。
示例 1
按字段 score 对年龄为 18 岁的人进行分组,获取每个分数值对应的行数。
query = TermQuery('age', 18)
# 作为数据源的 GroupByField 只需指定 field_name 和 name,不能设置 size
source = GroupByField('score', name='group_by_score')
group_by = GroupByComposite(sources=[source])
search_response = client.search(table_name, index_name,
SearchQuery(query, limit=0, get_total_count=True, group_bys=[group_by]),
ColumnsToGet(return_type=ColumnReturnType.NONE))
for group_by_result in search_response.group_by_results:
print("name: %s" % group_by_result.name)
print("source_names: %s" % str(group_by_result.source_group_by_names))
for item in group_by_result.items:
print(" keys: %s, count: %d" % (str(item.keys), item.row_count))示例 2
按字段 score 和 sex 对年龄为 18 岁的人进行组合分组,获取每个分数和性别组合对应的行数。
query = TermQuery('age', 18)
source1 = GroupByField('score', name='group_by_score')
source2 = GroupByField('sex', name='group_by_sex')
group_by = GroupByComposite(sources=[source1, source2])
search_response = client.search(table_name, index_name,
SearchQuery(query, limit=0, get_total_count=True, group_bys=[group_by]),
ColumnsToGet(return_type=ColumnReturnType.NONE))
for group_by_result in search_response.group_by_results:
print("name: %s" % group_by_result.name)
print("source_names: %s" % str(group_by_result.source_group_by_names))
for item in group_by_result.items:
# keys 是字符串列表,与 sources 顺序一一对应;字段值为空时对应位置为 None
print(" keys: %s, count: %d" % (str(item.keys), item.row_count))示例 3
使用 next_token 实现分页获取所有分组结果。按字段 score 分组,每次获取 2 个分组,通过翻页获取所有结果。
query = TermQuery('age', 18)
source = GroupByField('score')
group_by = GroupByComposite(sources=[source], size=2)
# 首次请求
search_response = client.search(table_name, index_name,
SearchQuery(query, limit=0, get_total_count=True, group_bys=[group_by]),
ColumnsToGet(return_type=ColumnReturnType.NONE))
group_by_result = search_response.group_by_results[0]
all_items = list(group_by_result.items)
# 翻页获取剩余结果,next_token 为 None 时表示已获取完毕
while group_by_result.next_token is not None:
group_by = GroupByComposite(sources=[source], size=2, next_token=group_by_result.next_token)
search_response = client.search(table_name, index_name,
SearchQuery(query, limit=0, get_total_count=True, group_bys=[group_by]),
ColumnsToGet(return_type=ColumnReturnType.NONE))
group_by_result = search_response.group_by_results[0]
all_items.extend(group_by_result.items)
# 输出所有分组结果
for item in all_items:
print("keys: %s, count: %d" % (str(item.keys), item.row_count))示例 4
使用子统计聚合。按字段 score 分组后,统计每个分组中 score 的最大值。
query = TermQuery('age', 18)
source = GroupByField('score')
sub_agg = Max('score')
group_by = GroupByComposite(sources=[source], sub_aggs=[sub_agg])
search_response = client.search(table_name, index_name,
SearchQuery(query, limit=0, get_total_count=True, group_bys=[group_by]),
ColumnsToGet(return_type=ColumnReturnType.NONE))
for group_by_result in search_response.group_by_results:
print("name: %s" % group_by_result.name)
for item in group_by_result.items:
print(" keys: %s, count: %d" % (str(item.keys), item.row_count))
for sub_agg in item.sub_aggs:
print(" sub_agg: %s, value: %s" % (sub_agg.name, str(sub_agg.value)))