聚合操作

Elasticsearch 聚合操作(Aggregations)详解:从基础到高级分析

聚合操作是 Elasticsearch 中用于数据分析的核心功能,能够对查询结果进行统计、分组、计算等汇总操作,类似 SQL 中的 GROUP BYSUMAVG 等,但功能更强大、更灵活。根据操作方式和用途,聚合可分为四大类:分组聚合(Bucketing)度量聚合(Metric)矩阵聚合(Matrix)管道聚合(Pipeline)

聚合操作基础语法

Elasticsearch 聚合通过 aggs(或全称 aggregations)关键字定义,基本结构如下:

{
  "aggs": {  // 聚合操作入口(可缩写为aggs)
    "<聚合名称>": {  // 自定义聚合的逻辑名称(如"avg_price")
      "<聚合类型>": {  // 聚合类型(如avg、terms、date_histogram)
        <聚合参数>  // 聚合的具体配置(如字段、间隔等)
      },
      "aggs": {  // 子聚合(可选,基于当前聚合结果进一步分析)
        "<子聚合名称>": { ... }
      }
    }
  },
  "size": 0  // 可选,设置为0仅返回聚合结果,不返回原始文档
}
  • 核心特点:支持多层嵌套(子聚合),可基于分组结果再进行度量,或基于度量结果再进行计算。
  • 适用场景:统计分析(如销售额总和)、分组统计(如按类别统计销量)、趋势分析(如按日统计访问量)等。

度量聚合(Metric)

度量聚合用于对文档中的数值型字段进行计算,返回单个或多个统计值(如平均值、最大值)。分为单值度量(返回一个值)和多值度量(返回多个相关值)。

单值度量聚合

(1)平均值(avg)

计算指定字段的平均值。

GET products/_search
{
  "size": 0,
  "aggs": {
    "avg_price": {  // 聚合名称
      "avg": { "field": "price" }  // 计算price字段的平均值
    }
  }
}

响应示例:

{
  "aggregations": {
    "avg_price": { "value": 250.5 }  // 平均值为250.5
  }
}
(2)基数(cardinality)

计算指定字段的不同值的近似数量(类似 COUNT(DISTINCT 字段))。

GET products/_search
{
  "size": 0,
  "aggs": {
    "distinct_categories": {
      "cardinality": {
        "field": "category.keyword",  // 对category字段去重计数
        "precision_threshold": 1000  // 精度阈值(低于此值时结果精确)
      }
    }
  }
}
  • 适用场景:统计不同类别、不同用户的数量(性能优于精确计数,适合大数据量)。
(3)最大值(max)、最小值(min)、求和(sum)
GET products/_search
{
  "size": 0,
  "aggs": {
    "max_price": { "max": { "field": "price" } },  // 最大值
    "min_price": { "min": { "field": "price" } },  // 最小值
    "total_sales": { "sum": { "field": "sales" } }  // 总和
  }
}

多值度量聚合

(1)统计(stats)

一次性返回字段的计数、最大值、最小值、平均值、总和

GET products/_search
{
  "size": 0,
  "aggs": {
    "price_stats": { "stats": { "field": "price" } }
  }
}

响应示例:

{
  "aggregations": {
    "price_stats": {
      "count": 100,  // 文档总数
      "min": 50.0,   // 最小值
      "max": 500.0,  // 最大值
      "avg": 250.5,  // 平均值
      "sum": 25050.0 // 总和
    }
  }
}
(2)百分比(percentiles)

计算数值字段的百分比分布(默认返回 1%、5%、25%、50%、75%、95%、99% 分位值)。

GET logs/_search
{
  "size": 0,
  "aggs": {
    "load_time_percentiles": {
      "percentiles": {
        "field": "load_time",  // 计算加载时间的百分比
        "percents": [90, 95, 99]  // 只返回90%、95%、99%分位值
      }
    }
  }
}

响应示例:

{
  "aggregations": {
    "load_time_percentiles": {
      "values": {
        "90.0": 150.0,  // 90%的请求加载时间≤150ms
        "95.0": 200.0,  // 95%的请求加载时间≤200ms
        "99.0": 300.0   // 99%的请求加载时间≤300ms
      }
    }
  }
}
(3)最高命中(top_hits)

返回每个分组中相关性最高的文档(如每个类别中销量最高的商品)。

GET products/_search
{
  "size": 0,
  "aggs": {
    "by_category": {
      "terms": { "field": "category.keyword" },  // 按类别分组
      "aggs": {
        "top_selling": {
          "top_hits": {  // 返回每个分组中得分最高的1条文档
            "size": 1,
            "_source": ["name", "price", "sales"],  // 只返回指定字段
            "sort": [{ "sales": { "order": "desc" } }]  // 按销量降序
          }
        }
      }
    }
  }
}

分组聚合(Bucketing)

分组聚合基于字段值或条件将文档划分为多个 “桶(Bucket)”,每个桶包含满足条件的文档。支持子聚合,可对每个桶进一步分析。

常用分组聚合

(1)索引词分组(terms)

按字段的精确值分组(类似 GROUP BY 字段),适用于 keywordinteger 等字段。

GET products/_search
{
  "size": 0,
  "aggs": {
    "by_category": {  // 按类别分组
      "terms": {
        "field": "category.keyword",  // 必须使用keyword类型(不分词)
        "size": 10,  // 返回前10个分组
        "order": { "avg_price": "desc" }  // 按子聚合avg_price降序排序
      },
      "aggs": {
        "avg_price": { "avg": { "field": "price" } }  // 每个分组的平均价格
      }
    }
  }
}

响应示例:

{
  "aggregations": {
    "by_category": {
      "buckets": [
        {
          "key": "electronics",  // 分组值(类别)
          "doc_count": 50,       // 该组文档数
          "avg_price": { "value": 300.0 }  // 子聚合结果
        },
        {
          "key": "clothing",
          "doc_count": 30,
          "avg_price": { "value": 150.0 }
        }
      ]
    }
  }
}
(2)直方图(histogram)

数值间隔分组(如价格每 50 元一组),适用于连续数值字段。

GET products/_search
{
  "size": 0,
  "aggs": {
    "price_ranges": {
      "histogram": {
        "field": "price",
        "interval": 50  // 间隔为50(即0-50、50-100、...)
      },
      "aggs": {
        "total_sales": { "sum": { "field": "sales" } }  // 每组的总销量
      }
    }
  }
}
(3)日期直方图(date_histogram)

时间间隔分组(如按天、月统计),适用于 date 类型字段。

GET logs/_search
{
  "size": 0,
  "aggs": {
    "daily_visits": {
      "date_histogram": {
        "field": "visit_time",
        "interval": "day",  // 按天分组
        "format": "yyyy-MM-dd",  // 日期格式
        "missing": "1970-01-01"  // 缺失值填充
      },
      "aggs": {
        "pv": { "value_count": { "field": "user_id" } }  // 每日访问量
      }
    }
  }
}
(4)范围分组(range)

自定义范围分组(如价格 0-100、100-200、200 以上)。

GET products/_search
{
  "size": 0,
  "aggs": {
    "price_buckets": {
      "range": {
        "field": "price",
        "ranges": [
          { "to": 100 },          // 0-100
          { "from": 100, "to": 200 },  // 100-200
          { "from": 200 }         // 200以上
        ]
      }
    }
  }
}

特殊分组聚合

(1)过滤分组(filter)

仅对满足过滤条件的文档进行聚合(类似 WHERE 子句)。

GET products/_search
{
  "size": 0,
  "aggs": {
    "expensive_products": {
      "filter": { "range": { "price": { "gte": 200 } } },  // 只考虑价格≥200的商品
      "aggs": {
        "avg_sales": { "avg": { "field": "sales" } }  // 计算这些商品的平均销量
      }
    }
  }
}
(2)嵌套分组(nested)

对嵌套类型(nested)的字段进行分组(需指定嵌套路径)。

GET orders/_search
{
  "size": 0,
  "aggs": {
    "nested_products": {
      "nested": { "path": "products" },  // 嵌套字段路径
      "aggs": {
        "by_product": {
          "terms": { "field": "products.name.keyword" },  // 按嵌套的商品名称分组
          "aggs": {
            "total_quantity": { "sum": { "field": "products.quantity" } }  // 总数量
          }
        }
      }
    }
  }
}

管道聚合(Pipeline)

管道聚合不直接处理文档,而是基于其他聚合的结果进行二次计算,分为父聚合(基于子聚合结果)和兄弟聚合(基于同级聚合结果)。

移动平均(moving_avg)

对时序数据计算滑动窗口的平均值(如按日销量计算 5 天移动平均)。

GET sales/_search
{
  "size": 0,
  "aggs": {
    "daily_sales": {
      "date_histogram": {  // 按日分组计算销量总和
        "field": "date",
        "interval": "day"
      },
      "aggs": {
        "total": { "sum": { "field": "amount" } },  // 每日销售额
        "avg_5d": {  // 基于total计算5天移动平均
          "moving_avg": { "buckets_path": "total", "window": 5 }
        }
      }
    }
  }
}

差值(derivative)

计算相邻分组的差值(如每日销量较前一天的变化量)。

GET sales/_search
{
  "size": 0,
  "aggs": {
    "daily_sales": {
      "date_histogram": { "field": "date", "interval": "day" },
      "aggs": {
        "total": { "sum": { "field": "amount" } },
        "day_over_day": {  // 计算相邻两天的销售额差值
          "derivative": { "buckets_path": "total" }
        }
      }
    }
  }
}

分组选择器(bucket_selector)

过滤掉不满足条件的分组(如只保留销售额≥1000 的日期)。

GET sales/_search
{
  "size": 0,
  "aggs": {
    "daily_sales": {
      "date_histogram": { "field": "date", "interval": "day" },
      "aggs": {
        "total": { "sum": { "field": "amount" } },
        "filter_high_sales": {  // 只保留total≥1000的分组
          "bucket_selector": {
            "buckets_path": { "sales": "total" },
            "script": "params.sales >= 1000"
          }
        }
      }
    }
  }
}

聚合操作的关键注意事项

  1. 字段类型限制
    • 度量聚合仅支持数值型字段(integerdoubledate 等)。
    • 分组聚合中,terms 需使用 keyword 类型(不分词),否则可能因分词导致分组混乱。
  2. 性能优化
    • 大数据量下,terms 聚合可通过 size 限制返回分组数,避免内存溢出。
    • 基数聚合(cardinality)使用近似算法,通过 precision_threshold 平衡精度与性能。
  3. 嵌套聚合深度
    过度嵌套(如多层 terms 聚合)可能导致性能下降,建议控制嵌套层级(通常不超过 3 层)。
  4. 与查询结合
    聚合默认基于所有文档,若需过滤文档,可在 query 中添加条件(聚合仅作用于匹配的文档)