阶段:第三阶段 / 聚合能力
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.StringTermsBucket、co.elastic.clients.elasticsearch._types.SortOrder。
top_hits命中在.topHits().hits().hits(),source 是JsonData,用.to(Map.class)转换。
5. 坑与最佳实践
- 嵌套层数别太深:多级 terms 桶数是乘积关系,容易爆内存(桶爆炸)。
top_hits只在桶内取少量文档:它是每桶单独排序取 top N,别当分页用。- 子聚合排序 :桶间排序用父
terms的order,引用子聚合名即可。 size: 0:外层不要文档,只要聚合结果。- 高基数 + 多级分组 :优先考虑
composite聚合分页,避免一次性返回海量桶。
下一篇
24-filter-bucket聚合.md。