Elasticsearch如何按桶内文档数将查询结果划分为指定数量区间
该需求无法通过Elasticsearch单条原生聚合语句直接实现——ES自带的所有直方图类聚合(包括你测试过的variable_width_histogram、固定宽度histogram)都是基于字段值的分布划分桶,没有提供「按排序后文档数量等分为指定数量桶」的原生能力。但可以通过轻量的两步查询组合实现,完全能得到你预期的返回结果,性能开销很低。
步骤1:计算分桶切分点
首先拿到符合查询条件的总文档数,按指定分桶数计算每个桶的起始文档偏移量,再拉取对应偏移位置的字段值作为聚合边界。
以你给出的10条数据分3桶的场景为例:
- 总文档数为10,分3桶时各桶起始偏移分别为0、3、7,对应桶大小为3、4、3(余数可按业务规则分配,示例中余数分配给中间桶)
- 按分桶字段
id升序排序,通过分页查询拿到对应偏移位置的id值即可,单条查询示例如下:
GET /test_index/_search { "size": 1, "from": 0, // 替换为对应偏移量3、7即可拿到对应位置的id值 "sort": [{"id": "asc"}], "_source": ["id"], "query": {"match_all": {}} // 替换为实际业务的查询条件 }
如果分桶数较多,可使用_msearch批量提交多个分页查询,减少网络请求次数;如果总文档量很大(十万级以上),建议用search_after替代from做深度分页,避免深度分页的性能开销。
步骤2:构造range聚合得到最终结果
拿到所有切分点的id值(示例中为0、3、7)后,使用range聚合按切分点划分区间,同时在每个桶内嵌套min聚合取区间最小值,查询语句如下:
GET /test_index/_search?size=0 { "aggs": { "result": { "range": { "field": "id", "ranges": [ {"from": 0, "to": 3}, {"from": 3, "to": 7}, {"from": 7} ] }, "aggs": { "min": { "min": {"field": "id"} } } } }, "query": {"match_all": {}} // 和步骤1保持相同的查询条件 }
对返回结果做一层简单的字段提取,把每个桶内的min.value提至桶层级,就能得到你预期的结构:
{ "aggregations": { "result": { "buckets": [ {"min": 0.0, "doc_count": 3}, {"min": 3.0, "doc_count": 4}, {"min": 7.0, "doc_count": 3} ] } } }
如果你对分桶的文档数均匀度要求不高,能接受小范围误差,可以直接用percentiles百分位聚合一步拿到切分点,再配合range聚合(或直接在应用层基于百分位值计算)得到结果。比如分3桶时取0%、33.3%、66.6%三个百分位的值作为切分点,在你给出的示例数据下返回的百分位值正好是0、3、7,也能得到正确结果,查询语句如下:
GET /test_index/_search?size=0 { "aggs": { "cut_points": { "percentiles": { "field": "id", "percents": [0, 33.3, 66.6] } } }, "query": {"match_all": {}} }
注意该方案是基于字段值的百分位划分,不是严格按文档数等分,数据分布不均匀时会出现桶内文档数偏差较大的情况,仅适合对精度要求低的场景。
内容的提问来源于stack exchange,提问作者Aleksey

