ElasticSearch聚合
Elasticsearch中的聚合是一种以结构化的方式提取和展示数据的机制。可以把它视为SQL中的GROUP BY语句,但是它更加强大和灵活。
聚合类型
Elasticsearch支持很多类型的聚合,包括:
- 指标聚合(Metrics agregations):主要用于最大值、最小值、平均值、字段之和等指标的统计。这类聚合基于文档字段的数值进行计算并返回一个单一的数值结果。例如最大值(max)、最小值(min)、平均值(average)、总和(sum)、统计信息(stats,包含了上述几种操作),以及其他复杂的聚合如百分数(percentiles)、基数(cardinality)等。
- 分桶聚合(Bucket agregations):类比SQL中的group by的作用,主要用于统计不同类型数据的数量。这类聚合会创建一组buckets,每个bucket对应一个特定的条件或范围,然后文档会根据这些条件或范围被分类到相应的bucket中。常见的包括区间(range)、日期区间(date range)、直方图(histogram)、日期直方图(date histogram)、地理哈希网格(geohash grid)等。
- 管道聚合(Pipeline agregations):用于对聚合的结果进行二次聚合,如要统计绑定数量最多的标签bucket,就是要先按照标签进行分桶,再在分桶的结果上计算最大值。这类聚合可以基于其他聚合的结果进行二次计算。比如计算差异、比例、移动平均等。
Elasticsearch的聚合操作支持嵌套,即一个聚合内部可以包含别的子聚合,从而实现非常复杂的数据挖掘和统计需求。
聚合查询语法
{
"size" : 0,
"aggregations" : {
"自已命名的聚合名称1" : {
"filter" : {
"bool" : {
"filter" : [
{
"term" : {
"查询字段1" : {
"value" : 查询值1,
"boost" : 1.0
}
}
}
]
}
},
"aggregations" : {
"自已命名的聚合名称2" : {
"sum" : {
"field" : "查询值2"
}
}
}
}
}
}
编写聚合
Nest 提供了 3 种方式来让你使用聚合:
- 通过 lambda 表达式的方式。
- 通过内建的请求对象 AggregationDictionary。
- 通过结合二元运算符来简化 AggregationDictionary 的使用。
假设有以下 Project 类:
public class Project
{
public string Name { get; set; }
public int Quantity { get; set; }
}
三种方式的请求命令见下方:
POST /project/_search?typed_keys=true
{
"aggs": { //关键字 aggregations,可以用 aggs 简写
"average_quantity": { //聚合的名字
"avg": { //聚合的类型,可以理解为相当于 sql server 中的聚合函数
"field": "quantity" //聚合体,对哪些字段进行聚合
}
},
"max_quantity": {
"max": {
"field": "quantity"
}
},
"min_quantity": {
"min": {
"field": "quantity"
}
}
}
}
lambda 方式
通过 lambda 表达式来使用聚合是简洁的方式
var searchResponse = _client.Search<Project>(s => s
.Aggregations(aggs => aggs
.Average("average_quantity", avg => avg.Field(p => p.Quantity))
.Max("max_quantity", avg => avg.Field(p => p.Quantity))
.Min("min_quantity", avg => avg.Field(p => p.Quantity))
)
);
一般进行聚合查询的时候,并不需要 _source 的东西,所以你在进行聚合查询是,可以在查询语句上指定 size=0,这样就只会返回 聚合 的结果,方式如下:
var searchResponse = _client.Search<Project>(s => s
.Size(0) //显式指定为 0
.Aggregations(aggs => aggs
.Average("average_quantity", avg => avg.Field(p => p.Quantity))
.Max("max_quantity", avg => avg.Field(p => p.Quantity))
.Min("min_quantity", avg => avg.Field(p => p.Quantity))
)
);
通过内建对象 AggregationDictionary
以下代码的效果和通过 lambda 表达式的效果一样
var searchRequest = new SearchRequest<Project>
{
Size = 0,
Aggregations = new AggregationDictionary
{
{"average_quantity", new AverageAggregation("average_quantity", "quantity")},
{"max_quantity", new MaxAggregation("max_quantity", "quantity")},
{"min_quantity", new MinAggregation("min_quantity", "quantity")},
}
};
var searchResponse = _client.Search<Project>(searchRequest);
通过结合二元运算符来简化 AggregationDictionary 的使用
通过二元运算符,可以让代码的可读性更高,以下代码等效于上方:
var searchRequest = new SearchRequest<Project>
{
Size = 0,
Aggregations = new AverageAggregation("average_quantity", "quantity")
&&new MaxAggregation("max_quantity", "quantity")
&&new MinAggregation("min_quantity", "quantity")
};
var searchResponse = _client.Search<Project>(searchRequest);
查询聚合值实践
多个聚合条件查询
在C#中使用NEST进行聚合查询时,可以使用AggregationContainer
类来添加多个聚合条件。
首先,你需要创建一个AggregationContainer
对象,并添加你需要的聚合条件。例如,假设你要查询一个索引中商品的总销售量和平均价格,你可以这样编写代码:
var searchResponse = client.Search<Product>(s => s
.Aggregations(a => a
.Sum("total_sales", st => st
.Field(f => f.Sales)
)
.Average("average_price", ap => ap
.Field(f => f.Price)
)
)
);
在上面的代码中,我们创建了一个AggregationContainer
对象,并使用Sum
和Average
方法来添加两个聚合条件。Sum
方法用来计算销售总量,Average
方法用来计算价格平均值。同时,我们还指定了对应的字段,Sales
和Price
。
最后,我们使用client.Search
方法来执行查询,查询结果会包含聚合结果。
请注意,上面的示例中使用了一个名为Product
的类来表示商品,你需要根据自己的数据结构来替换。同时,你需要根据实际的字段名来替换示例中的Sales
和Price
字段。
多个过滤条件的聚合查询
要使用NEST来编写C#代码生成上述的Elasticsearch聚合查询语句,你可以使用AggregationContainer
类的Filters
方法来添加多个过滤条件。
{"aggregations": {
"group": {
"filters": {
"filters": {
"pendingCount": {
"match": {
"dataBody.status": 0
}
},
"storedingCount": {
"match": {
"dataBody.status": 1
}
},
"finishedCount": {
"match": {
"dataBody.status": 2
}
},
"closedCount": {
"match": {
"dataBody.status": 3
}
},
"thisMonthCount": {
"range": {
"dataBody.creationTime": {
"gte": "2023-11-03"
}
}
}
}
}
}
}
下面是一个示例代码,展示如何使用NEST来生成上述的聚合查询语句:
var searchResponse = client.Search<Document>(s => s
.Aggregations(a => a
.Filters("group", f => f
.NamedFilters(flt => flt
.Filter("pendingCount", q => q
.Match(m => m
.Field("dataBody.status")
.Query("0")
)
)
.Filter("storedingCount", q => q
.Match(m => m
.Field("dataBody.status")
.Query("1")
)
)
.Filter("finishedCount", q => q
.Match(m => m
.Field("dataBody.status")
.Query("2")
)
)
.Filter("closedCount", q => q
.Match(m => m
.Field("dataBody.status")
.Query("3")
)
)
.Filter("thisMonthCount", q => q
.DateRange(dr => dr
.Field("dataBody.creationTime")
.GreaterThanOrEquals("2023-11-03")
)
)
)
)
)
);
在上面的代码中,我们使用Filters
方法来创建一个AggregationContainer
对象,并添加了多个过滤条件。每个过滤条件都是通过Filter
方法添加的,使用Match
或DateRange
方法来指定字段和查询条件。
请注意,上面的示例中使用了一个名为Document
的类来表示文档,你需要根据自己的数据结构来替换。同时,你需要根据实际的字段名来替换示例中的dataBody.status
和dataBody.creationTime
字段。
获取聚合结果中的值
要获取使用以上代码查询出来的聚合结果中的值,你可以使用AggregationContainer
类的Aggregations
属性来访问聚合结果。然后,可以使用聚合名称来获取具体的聚合结果。
以下是一个示例代码,展示如何获取聚合结果中的值并根据字段名称进行访问:
var groupAggregation = searchResponse.Aggregations.Filters("group");
var pendingCount = groupAggregation.Buckets["pendingCount"].DocCount;
var storingCount = groupAggregation.Buckets["storingCount"].DocCount;
var finishedCount = groupAggregation.Buckets["finishedCount"].DocCount;
var closedCount = groupAggregation.Buckets["closedCount"].DocCount;
var thisMonthCount = groupAggregation.Buckets["thisMonthCount"].DocCount;
// 或如下方式
var pendingCount = ((SingleBucketAggregate)groupAggregation["pendingCount"]).DocCount;
var storingCount = ((SingleBucketAggregate)groupAggregation["storedingCount"]).DocCount;
var finishedCount = ((SingleBucketAggregate)groupAggregation["finishedCount"]).DocCount;
var closedCount = ((SingleBucketAggregate)groupAggregation["closedCount"]).DocCount;
var thisMonthCount = ((SingleBucketAggregate)groupAggregation["thisMonthCount"]).DocCount;
在上面的代码中,我们首先使用Aggregations
属性来获取聚合结果,然后使用Filters
方法来获取名为"group"的聚合。接下来,我们可以通过访问聚合结果的Buckets
属性来获取每个过滤条件的结果。使用聚合名称作为键,可以直接访问聚合结果的DocCount
属性来获取匹配的文档数量。
注意,聚合结果的类型可能是不同的,具体取决于你在查询语句中使用的聚合类型。上面的示例假设聚合结果是一个FiltersAggregation
类型,你可能需要根据实际的聚合类型来进行适当的类型转换。