0%

ElasticSearch 聚合操作

ElasticSearch 提供的聚合分析功能有指标聚合(metrics aggregations)、桶聚合(bucket aggregations)、管道聚合(pipeline aggregations)和矩阵聚合(matrix aggregations)四大类。

聚合方式

聚合可以有多种用法。

直接聚合

直接聚合指的是聚合时的 DSL 没有 query 子句,是直接对索引内的所有文档进行聚合。

POST /hotel/_search
{   
	"size": 0, 
	"aggs": {   
	  	...
	} 
}  

先查询后聚合

参加聚合的文档必须匹配 query 查询后进行聚合。

GET /hotel/_search
{ 
	"size": 0, 
	"query": {  //指定查询query逻辑
	  	...
	},  
	"aggs": {   
		...
	} 
}  

前过滤器

有时需要对聚合条件进一步地过滤,但是又不能影响当前的查询条件。例如用户进行酒店搜索时的搜索条件是天津的酒店,但是聚合时需要将非满房的酒店平均价格进行聚合并展示给用户。此时不能变更用户的查询条件,需要在聚合子句中添加过滤条件。

GET /hotel/_search
{ 
	"size": 0,
	"query": {        //指定查询的query逻辑
		"term": {  
			"city": "天津"
		}
	}, 
	"aggs": { 
		"my_agg": { 
			"filter": {   //指定过滤器逻辑
				"term": { 
					"full_room": false
				}
			},
			"aggs": {     //指定聚合逻辑      
				"my_avg": {
					"avg": {
						"field": "price"
					}
				}
			}
		}
	}
} 

后过滤器

在有些场景中,需要根据条件进行数据查询,但是聚合的结果集不受影响。例如在酒店搜索场景中,用户的查询词为“假日”,此时应该展现全国各地标题中带有“假日”的酒店。但是用户身在北京,只希望给用户展示北京的酒店的平均价格,这时可以使用 ES 提供的后过滤器功能。该过滤器是在查询和聚合之后进行过滤的,因此它的过滤条件对聚合没有影响。

GET /hotel/_search 
{
	"size": 0,   
	"query": {         //指定查询的query逻辑  
		"match": {      
			"title": "假日"    
	    }
	}, 
	"post_filter": {   //指定后过滤器逻辑   
		"term": {      
		    "city": "北京" 
		}
	},
	"aggs": {          //指定聚合逻辑
	  	"my_agg": {
	    	"avg": {  
		      	"field": "price",
		        "missing":200 
			}
	    }
	}
}  

指标聚合

输出单值聚合

计算值 说明 注意
avg 计算平均值
max 计算最大值
min 计算最小值
sum 计算加和值
value_count 统计某字段有值的文档数 如果判断的字段是数组类型,则 value_count 统计的是符合条件的所有文档中该字段数组中非空元素个数的总和,而不是数组的个数总和。因此在遇到 price 数组中有两个非空值元素时,count 值加 2。
cardinality 某字段值去重计数

以 avg 为例

GET /hotel/_search
{   
	"size": 0,             //设置size的意图是不显示命中的文档
	"aggs": { 
	  	"my_agg": {          //聚合名称 
	    	"avg": { 
		      	"field": "price" //计算文档的平均价格  
			}
	  }
	}
}  

输出多值聚合

计算值 说明
stats 计算平均值、最小值、最大值
percentiles 根据百分比位数查询对应的数据信息,默认情况下会按照 [ 1, 5, 25, 50, 75, 95, 99 ] 进行返回
percentile_ranks 根据数据信息查询其对应的百分比位数

桶聚合

聚合方式

单维度桶聚合

最简单的桶聚合是单维度桶聚合,指的是按照一个维度对文档进行分组聚合。在桶聚合时,聚合的桶也需要匹配,匹配的方式有 terms、filter、ranges 等。

GET /hotel/_search 
{ 
	"size": 0, 
	"aggs": {
	  	"my_agg": {
	    	"terms": {  
		      	//按照城市进行聚合 
		        "field": "city"
		    }
	    }
	}
} 

多维度桶嵌套聚合

在某些业务需求中,不仅需要一个维度的桶聚合,而且还可能有多维度桶嵌套聚合的需求。例如在搜索酒店时,可能需要统计各个城市的满房和非满房状态下的酒店平均价格。ES 支持嵌套桶聚合,进行嵌套时,可以使用 aggs 子句进行子桶的继续嵌套,指标放在最里面的子桶内。

GET /hotel/_search
{  
	"size": 0,
	"aggs": {  
	  	"group_city": {     //多维度桶名称 
		    "terms": {  
		      	"field": "city"
		    },
		    "aggs": {         //单维度桶  
		      	"group_full_room": { 
		        	"terms": {  
			          	"field": "full_room"
			        },
				    "aggs": {    //聚合指标   
			          	"my_sum": {  
			            	"avg": { 
				              	"field": "price",
				                "missing": 200
				            }
			            }
			        }
		        }
		    }
		}
	}
}  

聚合类型

terms 聚合

根据匹配条件分桶。

GET /hotel/_search 
{ 
	"size": 0, 
	"aggs": {
	  	"my_agg": {
	    	"terms": {  
		      	//按照城市进行聚合 
		        "field": "city"
		    }
	    }
	}
} 

ranges 聚合

ranges 聚合匹配的是数值字段,表示按照数值范围进行分组。用户可以在 ranges 中添加分组,每个分组用 from 和 to 表示分组的起止数值。注意该分组包含起始数值,不包含终止数值。

GET /hotel/_search 
{ 
	"size": 0,
	"aggs": { 
	  	"my_agg": { 
	    	"range": { 
		      	"field": "price", 
		      	//多个范围桶
		        "ranges": [
		        	{
			          	"to": 200        //不指定from,默认from为0
			        },
			        { 
			          	"from": 200,
			            "to": 500 
			        },
			        { 
			          	"from": 500      //不指定to,默认to为该字段最大值     
			        }
		        ]
	        }
	    }
    }
}  

histogram 聚合

histogram 直方图聚合

根据指定的间隔构造存储桶。 属于每个间隔的值将形成一个间隔存储桶。

GET /hotel/_search
{
	"size": 0, 
	"query": {
		"range": {
			"createTime": {
				"gte": "2022-12-21 00:00:00",
				"lte": "2022-12-21 12:00:00"
			}
		}
	}, 
	"aggs": {
		"histogram_agg": {
			"histogram": {
				"field": "money",
				"interval": 200
			}
		}
	}
}

上述分区理论上应为 0-200、200-400 … 没有目标落在0-200、200-400 区间内。 因此,第一个存储区从 400-600 间隔开始。 因此,值最小的文档将确定最小存储桶(最小 key 的存储桶)。 相应地,具有最高值的文档将确定最大存储桶(具有最高 key 的存储桶)。

我们可以使用 extended_bounds 设置来强制直方图聚合,以根据特定的最小值开始构建其存储桶,并继续构建存储桶直至达到最大值(即使不再有文档)。 假设有如下情况:

  1. 数据存在于 200-600 之间,extended_bounds 设置为 0-800,展示结果为 0-800
  2. 数据存在于 0-600 之间,extended_bounds 设置为 0-200,展示结果为 0-600
    即其区间取值为数据区间与 extended_bounds 设置区间的交集,因此在实际使用中,可以将查询过滤范围和 extended_bounds 区间保持一致。

min_doc_count 也会影响存储桶的区间。有如下几种情况:

  1. 当设置为大于 0 时,使用 extended_bounds,仅会输出满足最小文档计数的桶
  2. 当设计为 0 时,返回所有桶

date_histogram 时间直方图聚合

这个聚合类似于正常的直方图,但只能与日期或日期范围值一起使用。 与直方图聚合聚合的主要区别在于,可以使用日期/时间表达式指定间隔。 基于时间的数据需要特殊的支持,因为基于时间的间隔并不总是固定的长度。

GET /hotel/_search
{
	"size": 0, 
	"query": {
		"range": {
			"createTime": {
				"gte": "2022-12-21 00:00:00",
				"lte": "2022-12-21 12:00:00"
			}
		}
	}, 
	"aggs": {
		"histogram_agg": {
			"date_histogram": {
				"field": "createTime",
				"fixed_interval": "1h"
			}
		}
	}
}

fixed_interval (时间间隔) 的可用表达式:

  • year(1y)年
  • quarter(1q)季度
  • month(1M)月份
  • week(1w)星期
  • day(1d)天
  • hour(1h)小时
  • minute(1m)分钟
  • second(1s)秒

地理距离聚合

使用地理距离聚合,您可以定义一个原点和到该点的一组距离范围。然后,聚合将评估每个 geo_point 值到原点的距离,并确定文档属于哪个范围。如果文档的 geo_point 值与原点之间的距离落入该存储桶的距离范围内,则该文档被视为属于该存储桶。

GET /hotel/_search
{
	"size": 0,
	"aggs": {
	  	"my_agg": { 
	    	"geo_distance": {
		      	"field": "location",
		        "origin": {    //指定聚合的中心点经纬度
			        "lat":  39.915143,
			        "lon": 116.4039
		        },
		        "unit": "km",  //指定聚合时的距离计量单位
		        "ranges": [    //指定每一个聚合桶的距离范围 
			        {  
			          	"to": 3 
			        },
			        {
			          	"from": 3,
			            "to":10
			        },
			        { 
			          	"from": 10
			        }
		        ]
		    }
	    }
	}
}  

IP 范围聚合

Elasticsearch 还具有对 IP 范围的内置支持。 IP聚合的工作方式与其他范围聚合类似。

让我们为 IP 地址创建索引映射,以说明此聚合的工作方式:

PUT /ips
{
	"mappings": {
	    "properties": {
		    "ip_addr": {
		        "type": "ip"
		    }
	    }
	}
}

当索引中包含一些数据时,让我们创建一个IP范围聚合:

GET ips/_search
{
	"size": 0,
	"aggs": {
	    "ip_ranges": {
		    "ip_range": {
		        "field": "ip_addr",
				"ranges": [
					{
						"to": "172.16.0.4"
					},
					{
						"from": "172.16.0.4"
					}
				]
		    }
	    }
	}
}

聚合排序

ES 对于聚合结果的默认排序规则有时并非是我们期望的。可以使用 ES 提供的 sort 子句进行自定义排序,有多种排序方式供用户选择:可以按照聚合后的文档计数的大小进行排序;可以按照聚合后的某个指标进行排序;还可以按照每个组的名称进行排序。

按文档计数排序

GET /hotel/_search 
{ 
	"size": 0, 
	"aggs": { 
	  	"group_city": {
	    	"terms": {
		      	"field": "city",
			    "order": {    //按照文档计数进行升序排列       
		        	"_count": "asc"
		        }
		    }
	    }
	}
}  

按聚合指标排序

GET /hotel/_search 
{ 
	"size": 0,  
	"aggs": {    
	  	"group_city": {
	    	"terms": {  
		      	"field": "city", 
		        "order": {       //按照聚合指标进行升序排列 
			        "my_avg": "asc"
		        }
		    },
		    "aggs": {  
		    	"my_avg": {        //定义聚合指标    
		      	"avg": {  
				    "field": "price", 
			        "missing": 200  
		        }
		      }
		    }
  }
}

按分组Key排序

GET /hotel/_search 
{ 
	"size": 0, 
	"aggs": { 
	  	"group_city": {  
	    	"terms": {  
	      	"field": "city", 
	        "order": {     //按照分桶key的自然顺序升序排列 
	        	"_key": "asc" 
	        }
	      },
	      "aggs": {
	      	"my_avg": {    //定义聚合指标  
	        	"avg": { 
	          	"field": "price", 
	            "missing": 200
	          }
	        }
	      }
	    }
    }
}  

聚合分页

Top hits 聚合

Top hits 聚合指的是聚合时在每个分组内部按照某个规则选出前 N 个文档进行展示。

GET /hotel/_search 
{ 
	"size": 0,
	"query": { 
	  	"match": { 
	    	"title": "金都"
	    }
	},
	"aggs": { 
		"group_city": {      //按照城市进行桶聚合  
			"terms": {
				"field": "city"
			},
		    "aggs": { 
				"my_avg": {  
					"top_hits": {  //指定返回每个桶的前3个文档
						"size": 3
					}
			    }
		    }
		}
	}
}  

Collapse 聚合

当在索引中有大量数据命中时,Top hits 聚合存在效率问题,并且需要用户自行排序。针对上述问题,ES 推出了 Collapse 聚合,即用户可以在 collapse 子句中指定分组字段,匹配 query 的结果按照该字段进行分组,并在每个分组中按照得分高低展示组内的文档。当用户在 query 子句外指定 from 和 size 时,将作用在 Collapse 聚合之后,即此时的分页是作用在分组之后的。

GET /hotel/_search 
{ 
	"from": 0,    //指定分页的起始位置 
	"size": 5,    //指定每页返回的数量
	"query": {    //指定查询的query逻辑 
	    "match": {       
	    	"title": "金都"  
	    }
	}, 
	"collapse": { //指定按照城市进行Collapse聚合   
		"field": "city"
	}
}