You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Elasticsearch Java嵌套字段聚合:Count正确但Sum子聚合异常排查

问题场景

Elasticsearch索引结构如下(places为嵌套类型,包含MAIN_LOCATION和AFFILIATE两种类型):

[
  {
    "_index" : "hotel",
    "_type" : "_doc",
    "_id" : "13171",
    "_score" : 6.072218,
    "_source" : {
      "_class" : "hotel",
      "id" : 13171,
      "places" : [{"type" : "MAIN_LOCATION","placeId" : 2032}],
      "numberOfRecommendations" : 0
    }
  },
  {
    "_index" : "hotel",
    "_type" : "_doc",
    "_id" : "7146",
    "_score" : 6.072218,
    "_source" : {
      "_class" : "hotel",
      "id" : 7146,
      "places" : [{"type" : "MAIN_LOCATION","placeId" : 2032}],
      "numberOfRecommendations" : 1
    }  
  },
  {
    "_index" : "hotel",
    "_type" : "_doc",
    "_id" : "7146",
    "_score" : 6.072218,
    "_source" : {
      "_class" : "hotel",
      "id" : 7146,
      "places" : [{"type" : "AFFILIATE","placeId" : 2032}],
      "numberOfRecommendations" : 3
    }  
  }
]

需求:统计特定地点的酒店数量,以及MAIN_LOCATION类型下的推荐总数(示例中应返回酒店数2、推荐数1)。

现有Java代码能正确统计酒店数量,但推荐数总和始终为0,生成的查询及输出如下:

生成的ES查询

{
  "query": {
    "bool" : {
      "must" : [
        {
          "nested" : {
            "query" : {
              "bool" : {
                "must" : [
                  {"term" : {"places.type" : {"value" : "MAIN_LOCATION","boost" : 1.0}}},
                  {"terms" : {"places.placeId" : [7146],"boost" : 1.0}}
                ],
                "adjust_pure_negative" : true,
                "boost" : 1.0
              }
            },
            "path" : "places",
            "ignore_unmapped" : false,
            "score_mode" : "min",
            "boost" : 1.0
          }
        },
        {
          "nested" : {
            "query" : {"exists" : {"field" : "places","boost" : 1.0}},
            "path" : "places",
            "ignore_unmapped" : false,
            "score_mode" : "none",
            "boost" : 1.0
          }
        }
      ],
      "adjust_pure_negative" : true,
      "boost" : 1.0
    }
  },
  "aggs": {
    "nestedPlaces":{
      "nested":{"path":"places"},
      "aggregations":{
        "placeFilter":{
          "filters":{
            "filters":[{
              "bool":{
                "must":[
                  {"term":{"places.type":{"value":"MAIN_LOCATION","boost":1.0}}},
                  {"terms":{"places.placeId":[7146],"boost":1.0}}
                ],
                "adjust_pure_negative":true,
                "boost":1.0
              }
            }],
            "other_bucket":false,
            "other_bucket_key":"_other_"
          },
          "aggregations":{
            "group_by_place_id":{
              "terms":{
                "field":"places.placeId",
                "size":193,
                "min_doc_count":1,
                "shard_min_doc_count":0,
                "show_term_doc_count_error":false,
                "order":[{"_count":"desc"},{"_key":"asc"}],
                "include":["7146"]
              },
              "aggregations":{
                "totalRecommendationsForPlace":{
                  "sum":{"field":"numberOfRecommendations"}
                }
              }
            }
          }
        }
      }
    }
  }
}

当前查询输出

"aggregations" : {
  "nestedPlaces" : {
    "doc_count" : 7,
    "placeFilter" : {
      "buckets" : [
        {
          "doc_count" : 3,
          "group_by_place_id" : {
            "doc_count_error_upper_bound" : 0,
            "sum_other_doc_count" : 0,
            "buckets" : [
              {
                "key" : 2032,
                "doc_count" : 3,
                "totalRecommendationsForPlace" : {"value" : 0.0}
              }
            ]
          }
        }
      ]
    }
  }
}

问题原因

问题出在聚合上下文的范围:

  • 使用nested聚合后,聚合进入了places嵌套文档的上下文,此时统计的是每个嵌套的places元素,而非根文档。
  • numberOfRecommendations是根文档字段,在嵌套上下文里无法直接访问,因此sum聚合无法获取有效值,结果始终为0。

解决方案

需要使用**reverse_nested聚合**跳出嵌套上下文,回到根文档层面,再对numberOfRecommendations做sum统计。

修改后的Java代码

调整聚合部分,在terms聚合的子聚合中先添加reverse_nested,再在其内部做sum:

// 原terms聚合部分修改
TermsAggregationBuilder aggregationBuilders =
    AggregationBuilders.terms(aggregationGroupByPlaceId)
        .field("places.placeId")
        .size(filter.getPlaceIds().size())
        .includeExclude(new IncludeExclude(includedPlaceIds, null))
        // 添加reverse_nested回到根文档上下文
        .subAggregation(AggregationBuilders.reverseNested("to_root")
            // 在根文档层面统计推荐数总和
            .subAggregation(AggregationBuilders.sum("totalRecommendationsForPlace")
                .field("numberOfRecommendations")));

修改后的ES查询结构

对应的聚合部分会变成:

"group_by_place_id":{
  "terms":{
    "field":"places.placeId",
    "size":193,
    "min_doc_count":1,
    "shard_min_doc_count":0,
    "show_term_doc_count_error":false,
    "order":[{"_count":"desc"},{"_key":"asc"}],
    "include":["7146"]
  },
  "aggregations":{
    "to_root":{
      "reverse_nested":{},
      "aggregations":{
        "totalRecommendationsForPlace":{
          "sum":{"field":"numberOfRecommendations"}
        }
      }
    }
  }
}

结果解析逻辑调整

同时需要修改getTotalRecommendationsForPlace方法,先获取reverse_nested聚合,再从中取sum值:

private int getTotalRecommendationsForPlace(Terms.Bucket bucket) {
  var reverseNestedAgg = bucket.getAggregations().get("to_root");
  if (reverseNestedAgg != null) {
    var aggregationTotalRecommendation = reverseNestedAgg.getAggregations().get("totalRecommendationsForPlace");
    if (aggregationTotalRecommendation != null) {
      return (int) ((ParsedSum) aggregationTotalRecommendation).getValue();
    }
  }
  return 0;
}

最终效果

修改后,聚合会正确回到根文档统计numberOfRecommendations的总和,示例场景中会返回:

  • 酒店数:2
  • 推荐数:1

内容的提问来源于stack exchange,提问作者Sandeep Nair

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.21 13:14:55