3. Bucket聚合实战:terms、range、date_range、histogram、date_histogram、filter、filters

好了,咱们进入Bucket聚合的实战环节。

Bucket聚合,说白了就是「分桶」。把数据按某种规则扔进不同的桶里,然后每个桶再单独做统计。我刚开始学ES时,觉得这玩意儿跟SQL里的GROUP BY差不多。嗯,确实有相似之处,但ES的桶更灵活,能嵌套,能组合,花样多得很。

3.1 terms聚合:最常用的分组利器

terms聚合,按字段值分组。比如按城市、按状态、按商品类别分组统计。

我个人习惯,做数据看板时第一个聚合就是terms,先看看数据分布情况。

GET /orders/_search
{
  "size": 0,
  "aggs": {
    "by_city": {
      "terms": {
        "field": "city.keyword",
        "size": 10
      }
    }
  }
}

返回结果里,每个城市就是一个桶,里面有个doc_count字段,告诉你这个桶里有多少文档。

小提示:terms聚合默认只返回前10个桶。如果你想看更多,记得调大size参数。但别设太大,否则性能扛不住。

我在项目中遇到过一个问题:某个字段的基数特别高(比如用户ID),用terms聚合时内存直接爆了。后来我加了"execution_hint": "map"才缓解。嗯,这里要注意,高基数字段慎用terms。

3.2 range聚合:数值范围分桶

range聚合,按数值范围分桶。比如按价格区间、按年龄区间、按分数区间。

GET /products/_search
{
  "size": 0,
  "aggs": {
    "price_ranges": {
      "range": {
        "field": "price",
        "ranges": [
          { "to": 100 },
          { "from": 100, "to": 500 },
          { "from": 500 }
        ]
      }
    }
  }
}

这里要注意,from是包含边界,to是不包含边界。也就是说,价格100的商品会落在第二个桶里,而不是第一个。

避坑指南:我曾经因为边界问题,把一批价格刚好等于100的商品算错了桶。后来我养成了习惯,每次写range聚合都先跑个小样本数据验证一下边界。

3.3 date_range聚合:时间范围分桶

date_range跟range差不多,只不过字段类型是日期。适合按时间段统计,比如按周、按月、按季度。

GET /orders/_search
{
  "size": 0,
  "aggs": {
    "order_dates": {
      "date_range": {
        "field": "order_date",
        "format": "yyyy-MM-dd",
        "ranges": [
          { "from": "2024-01-01", "to": "2024-03-31" },
          { "from": "2024-04-01", "to": "2024-06-30" },
          { "from": "2024-07-01", "to": "2024-09-30" }
        ]
      }
    }
  }
}

日期格式一定要跟字段里的格式匹配,否则ES会报错。我建议统一用yyyy-MM-dd或者yyyy-MM-dd HH:mm:ss,别搞花里胡哨的格式。

3.4 histogram聚合:固定间隔直方图

histogram聚合,按固定间隔分桶。比如每10块钱一个区间,每5岁一个区间。

GET /products/_search
{
  "size": 0,
  "aggs": {
    "price_histogram": {
      "histogram": {
        "field": "price",
        "interval": 50
      }
    }
  }
}

这个聚合特别适合做直方图可视化。你想想看,前端拿到数据直接就能画柱状图,省事得很。

核心要点:interval参数决定了桶的宽度。选太小,桶太多;选太大,数据都挤在一个桶里。我一般先看数据分布,再决定interval值。

3.5 date_histogram聚合:时间直方图

date_histogram是histogram的时间版本。按固定时间间隔分桶,比如每小时、每天、每月。

GET /logs/_search
{
  "size": 0,
  "aggs": {
    "logs_over_time": {
      "date_histogram": {
        "field": "timestamp",
        "fixed_interval": "1h"
      }
    }
  }
}

这里有两个参数容易搞混:fixed_intervalcalendar_interval

  • fixed_interval:固定间隔,比如1小时就是3600秒,不管是不是整点。
  • calendar_interval:日历间隔,比如1个月,它会按自然月来分桶。

我个人习惯,做日志分析用fixed_interval,做业务报表用calendar_interval。为什么?日志是连续的,业务是按月结算的,各取所需嘛。

3.6 filter聚合:单个条件过滤桶

filter聚合,先过滤再聚合。它只产生一个桶,桶里是满足条件的文档。

GET /orders/_search
{
  "size": 0,
  "aggs": {
    "high_value_orders": {
      "filter": {
        "range": {
          "total_amount": { "gte": 1000 }
        }
      },
      "aggs": {
        "avg_amount": {
          "avg": { "field": "total_amount" }
        }
      }
    }
  }
}

这个聚合特别适合做「条件内再统计」。比如我想看大额订单的平均金额,用filter聚合一步到位。

小技巧:filter聚合可以跟其他聚合嵌套使用。先过滤出目标数据,再对过滤后的数据做terms、histogram等聚合。

3.7 filters聚合:多个条件并行桶

filters聚合是filter的升级版。它可以定义多个过滤条件,每个条件生成一个桶。

GET /orders/_search
{
  "size": 0,
  "aggs": {
    "order_status": {
      "filters": {
        "filters": {
          "pending": { "term": { "status": "pending" } },
          "shipped": { "term": { "status": "shipped" } },
          "completed": { "term": { "status": "completed" } },
          "cancelled": { "term": { "status": "cancelled" } }
        }
      },
      "aggs": {
        "total_amount": {
          "sum": { "field": "total_amount" }
        }
      }
    }
  }
}

你看,一个请求就把所有状态的订单金额统计出来了。前端拿到数据,直接画饼图或者柱状图,非常方便。

避坑指南:我曾经在filters聚合里写了20多个过滤条件,结果查询超时了。filters数量太多会影响性能,建议控制在10个以内。如果确实需要很多条件,考虑拆成多个请求。

3.8 实战:组合使用多种Bucket聚合

真正的业务场景,很少只用一种聚合。我给大家看一个我实际做过的报表查询:

GET /orders/_search
{
  "size": 0,
  "aggs": {
    "by_city": {
      "terms": {
        "field": "city.keyword",
        "size": 5
      },
      "aggs": {
        "by_status": {
          "filters": {
            "filters": {
              "pending": { "term": { "status": "pending" } },
              "completed": { "term": { "status": "completed" } }
            }
          },
          "aggs": {
            "over_time": {
              "date_histogram": {
                "field": "order_date",
                "calendar_interval": "month"
              }
            }
          }
        }
      }
    }
  }
}

这个查询干了三件事:

  1. 先按城市分组(terms)
  2. 每个城市里再按订单状态过滤(filters)
  3. 每个状态里再按月统计订单数量(date_histogram)

三层嵌套,一个请求搞定。这就是ES聚合的强大之处——可以无限嵌套,组合出你想要的任何统计维度。

总结一下:
  • terms:按字段值分组,最常用
  • range/date_range:按数值/时间范围分组
  • histogram/date_histogram:按固定间隔分组,适合做图表
  • filter/filters:按条件过滤后分组,适合做条件统计

嗯,Bucket聚合的内容就这些。下一章咱们聊聊Metric聚合,看看怎么在桶里做各种统计计算。