第三阶段 23 · 嵌套与子聚合 / top_hits

阶段:第三阶段 / 聚合能力

ES:子聚合 / top_hits | PostgreSQL:GROUP BY 后再聚合 / 窗口函数近似


1. 概念

聚合可以层层嵌套 :桶聚合里再放桶聚合或指标聚合,

相当于 SQL 里多级 GROUP BY 再算指标。

top_hits 是一种特殊子聚合,能取出每个桶内的前 N 条原始文档

近似 SQL 窗口函数 ROW_NUMBER() OVER (PARTITION BY ...) 取 top N。


2. PostgreSQL 对照

sql 复制代码
-- 多级分组:先 geo 再 region,算金额
SELECT geo_cd, region, SUM(net_amount) AS total
FROM salesdata
GROUP BY geo_cd, region;

-- 每个 geo 取金额最高的一条(窗口函数)
SELECT * FROM (
  SELECT *, ROW_NUMBER() OVER (PARTITION BY geo_cd ORDER BY net_amount DESC) rn
  FROM salesdata
) t WHERE rn = 1;

3. ES DSL

多级分组

复制代码
GET salesdata_idx/_search
{
  "size": 0,
  "aggs": {
    "by_geo": {
      "terms": { "field": "geo_cd", "size": 10 },
      "aggs": {
        "by_region": {
          "terms": { "field": "region", "size": 10 },
          "aggs": {
            "total": { "sum": { "field": "net_amount" } }
          }
        }
      }
    }
  }
}

每个桶取 top N 文档(top_hits)

复制代码
GET salesdata_idx/_search
{
  "size": 0,
  "aggs": {
    "by_geo": {
      "terms": { "field": "geo_cd", "size": 10 },
      "aggs": {
        "top_orders": {
          "top_hits": {
            "size": 1,
            "sort": [ { "net_amount": "desc" } ],
            "_source": [ "invoice_number", "net_amount" ]
          }
        }
      }
    }
  }
}

4. Spring Boot 实现

java 复制代码
@Component
public class Doc23NestedAgg {

    @Autowired
    private ElasticsearchClient elasticsearchClient;

    /** 每个 geo 取金额最高的一条订单 */
    public Map<String, Map<String, Object>> topOrderByGeo(String indexName) throws IOException {
        SearchResponse<Void> resp = elasticsearchClient.search(s -> s
                .index(indexName)
                .size(0)
                .aggregations("by_geo", a -> a
                        .terms(t -> t.field("geo_cd").size(10))
                        .aggregations("top_orders", sub -> sub
                                .topHits(h -> h
                                        .size(1)
                                        .sort(so -> so.field(f -> f.field("net_amount").order(SortOrder.Desc)))))),
                Void.class);

        Map<String, Map<String, Object>> result = new LinkedHashMap<>();
        for (StringTermsBucket bucket :
                resp.aggregations().get("by_geo").sterms().buckets().array()) {
            String geo = bucket.key().stringValue();
            var hits = bucket.aggregations().get("top_orders").topHits().hits().hits();
            if (!hits.isEmpty()) {
                // top_hits 的 source 是 JsonData,按需转 Map
                result.put(geo, hits.get(0).source().to(Map.class));
            }
        }
        return result;
    }
}

import:...aggregations.StringTermsBucketco.elastic.clients.elasticsearch._types.SortOrder

top_hits 命中在 .topHits().hits().hits(),source 是 JsonData,用 .to(Map.class) 转换。


5. 坑与最佳实践

  1. 嵌套层数别太深:多级 terms 桶数是乘积关系,容易爆内存(桶爆炸)。
  2. top_hits 只在桶内取少量文档:它是每桶单独排序取 top N,别当分页用。
  3. 子聚合排序 :桶间排序用父 termsorder,引用子聚合名即可。
  4. size: 0:外层不要文档,只要聚合结果。
  5. 高基数 + 多级分组 :优先考虑 composite 聚合分页,避免一次性返回海量桶。

下一篇

24-filter-bucket聚合.md

相关推荐
倒流时光三十年3 小时前
第二阶段 19 · _source 字段裁剪
es
倒流时光三十年2 天前
第二阶段 15 · prefix / wildcard / fuzzy 模糊匹配
es
粗体鱼2 天前
RAG/Agent 记忆混合检索多路召回:RRF 算法与Chunk RRF、Document RRF如何决策TopK
postgresql·milvus·es·rag·rff·mermory
huisheng_qaq2 个月前
【项目篇-01】Vmware虚拟机和环境安装配置
redis·mysql·canal·rocketmq·es·vaware虚拟机
JAVA面经实录9172 个月前
Elasticsearch 完整版完整知识体系
java·elasticsearch·搜索引擎·es
代码讲故事2 个月前
在没有kibana的ES(elasticsearch)线上生产环境集群中,如何通过命令行修改或增加字段而不需要reindex?
大数据·elasticsearch·搜索引擎·命令行·es·索引·模版
yurenpai(27届找实习中)2 个月前
Elasticsearch 核心总结 + 面试题实战(黑马点评项目)
redis·es
徐小青青啊2 个月前
es集群不中断实时数据更新损坏节点硬盘
大数据·elasticsearch·搜索引擎·es
醇氧2 个月前
【Hermes Agent】使用阿里云百炼 Token Plan(方式一:命令行一键配置)
阿里云·云计算·es·openclaw