一、聚合查询概述
Elasticsearch中的聚合查询是一种功能强大的数据分析工具,它能够提供从索引中提取和计算有关数据的复杂统计信息的能力。聚合查询不仅可以帮助用户理解和分析数据中的趋势和模式,还能在业务决策中发挥关键作用。聚合查询支持多种类型,包括指标聚合、桶聚合和管道聚合,每一种都有其特定的应用场景和使用方法。
在 Elasticsearch 中,聚合操作主要依赖于 doc_values 或 fielddata 来进行。用于聚合的字段可以是精确值字段(如keyword类型)或分词字段(如text类型)。这两类字段在聚合查询时的处理方式有所不同。
精确值字段通常用于存储不需要分词和全文搜索的数据,如用户ID、产品类别等。对于这类字段,Elasticsearch默认使用doc_values数据结构来支持高效的聚合、排序和统计操作。doc_values以列式存储格式在磁盘上保存字段值,并在需要时加载到JVM堆内存中进行计算。由于doc_values直接在磁盘上操作,因此性能通常很高,且适用于大规模数据集。
分词字段(如text类型)通常用于存储需要分词和全文搜索的文本数据。对于这类字段,Elasticsearch默认不启用fielddata,因为fielddata会将字段值加载到堆内存中,导致在处理大数据集时容易引发内存溢出(OOM)问题。然而,有时我们确实需要在分词字段上执行聚合操作(例如,按产品名称分组统计销售数据)。在这种情况下,有几种解决方案可供选择:
在Elasticsearch中,聚合操作主要依赖于doc_values或fielddata来访问文档中的字段值。了解这两种数据结构的差异和适用场景,有助于优化聚合查询的性能。
总之, 对于精确值字段,利用doc_values可以获得高效且准确的聚合结果;对于分词字段,通过添加.keyword子字段或使用其他解决方案来避免启用fielddata带来的性能问题。通过合理配置字段映射和选择聚合查询策略,可以充分发挥Elasticsearch在数据分析领域的强大功能。
示例场景:统计每个作者写了多少篇文章,并按文章数量降序排序。 查询语句:
POST /blog/_search
{
"size": 0,
"aggs": {
"articles_per_author": {
"terms": {
"field": "author.keyword",
"size": 10,
"order": { "_count": "desc" }
}
}
}
}
示例场景:分析每月的销售记录数量。 查询语句:
POST /sales/_search
{
"size": 0,
"aggs": {
"sales_over_time": {
"date_histogram": {
"field": "sale_date",
"calendar_interval": "month",
"format": "yyyy-MM"
}
}
}
}
示例场景:分析不同价格区间的产品数量。 查询语句:
post /products/_search
{
"size": 0,
"aggs": {
"price_ranges": {
"range": {
"field": "price",
"ranges": [
{ "to": 100 },
{ "from": 100, "to": 500 },
{ "from": 500 }
]
}
}
}
}
示例场景:分析每个订单中不同产品的平均价格。 假设数据:一个订单可以有多个产品,每个产品都有一个价格。 查询语句:
POST /orders/_search
{
"size": 0,
"aggs": {
"orders": {
"nested": {
"path": "products"
},
"aggs": {
"avg_price_per_order": {
"avg": {
"field": "products.price"
}
}
}
}
}
}
示例场景:在按月份统计的销售记录中找出销售额最高的月份,并计算该月的平均销售额。 查询语句:
POST /sales/_search
{
"size": 0,
"aggs": {
"sales_over_time": {
"date_histogram": {
"field": "sale_date",
"calendar_interval": "month"
},
"aggs": {
"total_sales": {
"sum": {
"field": "amount"
}
},
"top_sales_month": {
"top_hits": {
"sort": [
{ "total_sales": { "order": "desc" } }
],
"size": 1
}
},
"avg_sales_top_month": {
"avg_bucket": {
"buckets_path": "total_sales"
}
}
}
}
}
}
示例场景:分析销售数据的变化趋势,计算销售额的日增长率。 查询语句:
POST /sales/_search
{
"size": 0,
"aggs": {
"sales_over_time": {
"date_histogram": {
"field": "sale_date",
"calendar_interval": "day"
},
"aggs": {
"total_sales": {
"sum": {
"field": "amount"
}
},
"sales_derivative": {
"derivative": {
"buckets_path": "total_sales"
}
}
}
}
}
}
我们首先按天对销售数据进行分组,并计算每天的总销售额。然后,我们使用derivative管道聚合来计算销售额的日增长率。
示例场景:计算销售数据的累计和,展示销售额的累计增长情况。 查询语句:
POST /sales/_search
{
"size": 0,
"aggs": {
"sales_over_time": {
"date_histogram": {
"field": "sale_date",
"calendar_interval": "month"
},
"aggs": {
"total_sales": {
"sum": {
"field": "amount"
}
},
"cumulative_sales": {
"cumulative_sum": {
"buckets_path": "total_sales"
}
}
}
}
}
}
我们按月对销售数据进行分组,并计算每月的总销售额。然后,我们使用cumulative_sum管道聚合来计算销售额的累计和。
示例场景:分析销售数据的移动平均线,以平滑数据波动并识别趋势。 查询语句:
POST /sales/_search
{
"size": 0,
"aggs": {
"sales_over_time": {
"date_histogram": {
"field": "sale_date",
"calendar_interval": "day"
},
"aggs": {
"total_sales": {
"sum": {
"field": "amount"
}
},
"moving_avg_sales": {
"moving_avg": {
"buckets_path": "total_sales",
"window": 7 // 计算7天的移动平均
}
}
}
}
}
}
我们按天对销售数据进行分组,并计算每天的总销售额。然后,我们使用moving_avg管道聚合来计算7天的移动平均销售额。
示例场景:计算每个销售桶中不同产品的销售额占比。 查询语句(假设每个销售桶中按产品分组):
POST /sales/_search
{
"size": 0,
"aggs": {
"sales_by_product": {
"terms": {
"field": "product.keyword"
},
"aggs": {
"total_sales": {
"sum": {
"field": "amount"
}
},
"sales_percentage": {
"bucket_script": {
"buckets_path": {
"thisSales": "total_sales",
"totalSales": "_sum" // 假设外层还有一个求和聚合来计算总销售额
},
"script": "params.thisSales / params.totalSales * 100"
}
}
}
},
"total_sales": {
"sum": {
"field": "amount"
}
}
}
}
bucket_script引用了两个buckets_path,其中_sum是Elasticsearch中的一个特殊变量,它引用了当前聚合上下文中所有桶的总和。这个示例假设外层还有一个求和聚合来计算所有产品的销售总额。然后,我们计算每个产品销售额占总销售额的百分比。
示例场景:分析不同分类产品的销售情况。 查询语句:
POST /products/_search
{
"size": 0,
"aggs": {
"sales_by_category": {
"filters": {
"filters": {
"electronics": { "term": { "category": "electronics" }},
"books": { "term": { "category": "books" }},
"other": { "match_all": {} }
}
},
"aggs": {
"total_sales": {
"sum": {
"field": "price"
}
}
}
}
}
}
我们使用了filters聚合来按产品分类过滤文档,并在每个过滤器内部使用sum聚合来计算总销售额。