Elasticsearch分组聚合必须使用aggs而非GROUP BY,需置于query同级;terms适用于keyword字段,range用于数值区间,嵌套聚合需正确层级结构,且PHP客户端需以关联数组传入aggs。

ES分组聚合必须用 aggs,不是 group by
MySQL 的 GROUP BY 在 Elasticsearch 里对应的是 aggs(aggregations),写在 query body 最外层,和 query 同级。很多人误把它塞进 must 或 filter 里,结果返回空或报错 "Unknown key for a START_OBJECT in [aggs]"。
常见场景:统计每个类目下商品数、按品牌聚合销量、查价格区间分布。这些都得靠 terms、range、histogram 这类子聚合实现。
-
terms用于离散字段(如brand、category_id),要求字段是keyword类型,不能是text -
range适合数值或日期区间,比如price分段:["gte" => 0, "lte" => 99]、["gte" => 100, "lte" => 499] - 聚合结果默认只返回前 10 个桶(
size),要查全量得显式设"size" => 1000(注意别超集群限制)
terms 聚合前务必确认字段 mapping 是 keyword
如果对 title 或 description 这种 text 字段直接做 terms,ES 会报错 "Fielddata is disabled on text fields"。这不是 bug,是设计使然——全文检索字段默认不开启正排索引(fielddata),避免内存爆炸。
正确做法是:在 mapping 里为需要聚合的字段额外定义 keyword 子字段,比如:
"category_name": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
}然后聚合时指定 "field" => "category_name.keyword"。别漏掉 .keyword 后缀,否则还是失败。
- 已有索引改 mapping 需重建(
reindex),无法热更新 - 新建索引时就在
PUT /products/_mapping中提前声明好keyword子字段 - 用
category_path做树形类目聚合时,也必须走category_path.keyword
嵌套聚合要小心顺序:先 terms 再 sum 或 avg
想查“每个品牌的平均售价”,不能把 avg 放在最外层;必须让 avg 成为 terms 的子聚合。结构是:aggs → brands(terms) → aggs → avg_price(avg)。
错误写法(avg 和 terms 并列)会导致 avg 算的是全量均值,不是按品牌分组后的均值:
"aggs": {
"brands": { "terms": { "field": "brand.keyword" } },
"avg_price": { "avg": { "field": "price" } }
}正确嵌套写法:
"aggs": {
"brands": {
"terms": { "field": "brand.keyword" },
"aggs": {
"avg_price": { "avg": { "field": "price" } }
}
}
}- 多层嵌套(比如品牌 → 类目 → 平均价)就继续往里套
aggs - 聚合层级越深,性能开销越大,线上慎用超过 3 层的嵌套
- 如果只是要总数,用
"value_count"比"terms"+"doc_count"更轻量
PHP 客户端调用时,aggs 是数组键,不是字符串拼接
用 elasticsearch/elasticsearch 客户端时,别把整个 DSL 当字符串传。aggs 必须作为请求 body 的 PHP 关联数组键传入,比如:
$params = [
'index' => 'products',
'body' => [
'query' => [ /* ... */ ],
'aggs' => [
'by_brand' => [
'terms' => ['field' => 'brand.keyword', 'size' => 50],
'aggs' => [
'avg_price' => ['avg' => ['field' => 'price']]
]
]
]
]
];
$response = $client->search($params);容易踩的坑:
- 忘记加
'body'包裹层,直接把aggs放到顶层,报错"Required [index] missing" - 用
json_encode()手动拼字符串再json_decode()回来,多此一举还易出错 - 聚合字段名含点号(如
specs.cpu)时,PHP 数组键要加引号:'specs.cpu.keyword'
聚合结果在 $response['aggregations']['by_brand']['buckets'] 里,每个 bucket 有 key(分组值)、doc_count(数量)、以及子聚合结果(如 avg_price.value)。别假设返回结构和 SQL 一样扁平——ES 的聚合响应是深度嵌套的 JSON 树。


















