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

Elasticsearch中路径层级令牌的分层聚合解决方案咨询

问题描述

我有一个存储层级数据的Elasticsearch索引locations_hierarchy,示例文档如下:

{
    "_index" : "locations_hierarchy",
    "_type" : "_doc",
    "_id" : "1",
    "_score" : 1.0,
    "_source" : {
      "category_path" : "Art Gallery, Paintings, Case 1"
    }
  },
  {
    "_index" : "locations_hierarchy",
    "_type" : "_doc",
    "_id" : "2",
    "_score" : 1.0,
    "_source" : {
      "category_path" : "Display Gallery, Small Worlds, Case 1, Shelf 2"
    }
}

我希望实现分层嵌套聚合的效果,示例输出如下:

{
          "key" : "Art Gallery",
          "doc_count" : 4,
          "category_sub" : {
            "doc_count_error_upper_bound" : 0,
            "sum_other_doc_count" : 0,
            "buckets" : [
              {
                "key" : "Paintings",
                "doc_count" : 2,
                "category_sub_sub" : {
                  "doc_count_error_upper_bound" : 0,
                  "sum_other_doc_count" : 0,
                  "buckets" : [
                    {
                      "key" : "Case 1",
                      "doc_count" : 1
                    },
                    {
                      "key" : "Case 3",
                      "doc_count" : 1
                    }
                  ]
                }
}

之前尝试用聚合的include/exclude参数手动处理,但操作繁琐,也无法适配层级数量不固定的场景。想知道Elasticsearch有没有针对这类路径层级字段的分层聚合方案?


解决方案

思路1:优化数据存储结构(推荐)

如果可以修改索引结构,直接把层级路径拆分为结构化格式,能从根源上简化聚合操作:

方式1:存储为层级数组

将category_path拆分为数组字段category_hierarchy,示例文档改为:

{
  "_source": {
    "category_hierarchy": ["Art Gallery", "Paintings", "Case 1"]
  }
}

然后使用多层terms聚合,结合脚本提取对应层级的元素:

{
  "aggs": {
    "level_1": {
      "terms": {
        "script": "doc['category_hierarchy'][0]"
      },
      "aggs": {
        "level_2": {
          "terms": {
            "script": "doc['category_hierarchy'].length > 1 ? doc['category_hierarchy'][1] : ''"
          },
          "aggs": {
            "level_3": {
              "terms": {
                "script": "doc['category_hierarchy'].length > 2 ? doc['category_hierarchy'][2] : ''"
              }
            }
          }
        }
      }
    }
  }
}

这种方式可根据业务最大层级数提前定义聚合层级,也能通过脚本适配不同长度的层级数据。

方式2:存储为嵌套对象

如果需要明确层级的父子关系,用nested类型存储每个层级节点:

{
  "_source": {
    "categories": [
      {"level": 1, "name": "Art Gallery"},
      {"level": 2, "name": "Paintings"},
      {"level": 3, "name": "Case 1"}
    ]
  }
}

需先在索引映射中设置categories为nested类型,再通过nested聚合+terms聚合实现分层统计:

{
  "aggs": {
    "level_1": {
      "nested": {
        "path": "categories"
      },
      "aggs": {
        "filter_level_1": {
          "filter": {
            "term": {"categories.level": 1}
          },
          "aggs": {
            "category_name": {
              "terms": {"field": "categories.name.keyword"},
              "aggs": {
                "level_2": {
                  "nested": {
                    "path": "categories"
                  },
                  "aggs": {
                    "filter_level_2": {
                      "filter": {
                        "term": {"categories.level": 2}
                      },
                      "aggs": {
                        "category_name": {
                          "terms": {"field": "categories.name.keyword"}
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}

思路2:基于原字符串字段的动态聚合(无需修改数据)

如果无法修改现有索引结构,可通过脚本+分词预处理实现动态分层聚合:

步骤1:添加path_hierarchy分词器字段

修改索引映射,为category_path添加使用path_hierarchy分词器的子字段:

{
  "mappings": {
    "_doc": {
      "properties": {
        "category_path": {
          "type": "text",
          "fields": {
            "hierarchy": {
              "type": "text",
              "analyzer": {
                "type": "custom",
                "tokenizer": "path_hierarchy",
                "delimiter": ","
              }
            }
          }
        }
      }
    }
  }
}

该分词器会将Art Gallery, Paintings, Case 1拆分为:

  • Art Gallery
  • Art Gallery,Paintings
  • Art Gallery,Paintings,Case 1

步骤2:脚本实现分层聚合

使用terms聚合结合脚本提取各层级节点:

{
  "aggs": {
    "level_1": {
      "terms": {
        "script": {
          "source": """
            def parts = doc['category_path.keyword'].value.split(',');
            return parts.length > 0 ? parts[0].trim() : '';
          """
        }
      },
      "aggs": {
        "level_2": {
          "terms": {
            "script": {
              "source": """
                def parts = doc['category_path.keyword'].value.split(',');
                return parts.length > 1 ? parts[1].trim() : '';
              """
            }
          },
          "aggs": {
            "level_3": {
              "terms": {
                "script": {
                  "source": """
                    def parts = doc['category_path.keyword'].value.split(',');
                    return parts.length > 2 ? parts[2].trim() : '';
                  """
                }
              }
            }
          }
        }
      }
    }
  }
}

这种方案无需修改原数据,但脚本聚合性能略低于结构化存储方案,适合数据量不大的场景。

补充说明

  • 如果层级数量完全不确定,Elasticsearch原生不支持无限递归的动态聚合,需在应用层递归调用聚合接口:每次以上一层的key作为过滤条件,查询下一层的节点。
  • 优先选择优化数据结构的方案,不仅聚合性能更高,也更易维护。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 11:16:34