对所有可能的桶执行管道聚合



当使用Elasticsearch的_searchAPI时,如果您将size设置为10,并执行avg度量聚合,则平均值将是匹配查询的数据集中所有值的平均值,而不仅仅是hits数组中返回的10项的平均值。

另一方面,如果执行terms聚合并将terms聚合的size设置为10,那么对这些terms桶执行avg_buckets聚合将仅计算这10个桶的平均值—而不是所有潜在的桶。

我如何计算一些字段在所有潜在桶的平均值,但仍然只有10项在buckets数组?


为了使我的问题更具体,考虑这个例子:假设我是一个帽子制造商。很多商店都有卖我的帽子。我有一个Elasticsearch索引hat-sales,每当我的一顶帽子被出售时,它就有一个文档。在这个文档中包含了价格和出售帽子的商店。

下面是我在上面测试的两个文档示例:
{
"type": "top",
"color": "black",
"price": 19,
"store": "Macy's"
}
{
"type": "fez",
"color": "red",
"price": 94,
"store": "Walmart"
}

如果我想找到所有帽子的平均价格,我可以这样运行:

GET hat-sales/_search
{
"size": 0, 
"query": {
"match_all": {}
},
"aggs": {
"average_hat_price": {
"avg": {
"field": "price"
}
}
}
}

无论size是0还是3,average_hat_price都是一样的

好的,现在我想找出销售最多帽子的前3家商店。我还想将它们与商店出售的帽子的平均数量进行比较。所以我想要做这样的事情:

GET hat-sales/_search
{
"size": 0, 
"query": {
"match_all": {}
},
"aggs": {
"by_store": {
"terms": {
"field": "store.keyword",
"size": 3
},
"aggs": {
"sales_count": {
"cardinality": {
"field": "_id"
}
}
}
},
"avg sales at a store": {
"avg_bucket": {
"buckets_path": "by_store>sales_count"
}
}
}
}

产生

的响应
"aggregations" : {
"by_store" : {
"doc_count_error_upper_bound" : 0,
"sum_other_doc_count" : 8,
"buckets" : [
{
"key" : "Macy's",
"doc_count" : 6,
"sales_count" : {
"value" : 6
}
},
{
"key" : "Walmart",
"doc_count" : 5,
"sales_count" : {
"value" : 5
}
},
{
"key" : "Dillard's",
"doc_count" : 3,
"sales_count" : {
"value" : 3
}
}
]
},
"avg sales at a store" : {
"value" : 4.666666666666667
}
}

问题是avg sales at a store只计算Macy's, Walmart和Dillard's。如果我想找到所有存储的平均值,我必须将aggs.by_store.terms.size设置为65536。(65536,因为这是术语桶的默认最大数量,我不知道先验可能有多少桶。)结果如下:

"aggregations" : {
"by_store" : {
"doc_count_error_upper_bound" : 0,
"sum_other_doc_count" : 0,
"buckets" : [
{
"key" : "Macy's",
"doc_count" : 6,
"sales_count" : {
"value" : 6
}
},
{
"key" : "Walmart",
"doc_count" : 5,
"sales_count" : {
"value" : 5
}
},
{
"key" : "Dillard's",
"doc_count" : 3,
"sales_count" : {
"value" : 3
}
},
{
"key" : "Target",
"doc_count" : 3,
"sales_count" : {
"value" : 3
}
},
{
"key" : "Harrod's",
"doc_count" : 2,
"sales_count" : {
"value" : 2
}
},
{
"key" : "Men's Warehouse",
"doc_count" : 2,
"sales_count" : {
"value" : 2
}
},
{
"key" : "Sears",
"doc_count" : 1,
"sales_count" : {
"value" : 1
}
}
]
},
"avg sales at a store" : {
"value" : 3.142857142857143
}
}

所以每家商店平均售出的帽子数量是3.1,而不是4.6。但是在buckets数组中,我只想看到前3个商店。

您可以在没有管道聚合的情况下实现您的目标。它有点欺骗聚合框架,但是,它工作。

数据设置如下:

PUT hat_sales
{
"mappings": {
"properties": {
"storename": {
"type": "keyword"
}
}
}
}
POST hat_sales/_bulk?refresh=true
{"index": {}}
{"storename": "foo"}
{"index": {}}
{"storename": "foo"}
{"index": {}}
{"storename": "bar"}
{"index": {}}
{"storename": "baz"}
{"index": {}}
{"storename": "baz"}
{"index": {}}
{"storename": "baz"}

这是一个棘手的查询:

GET hat_sales/_search?size=0
{
"aggs": {
"stores": {
"terms": {
"field": "storename",
"size": 2
}
},
"average_sales_count": {
"avg_bucket": {
"buckets_path": "stores>_count"
}
},
"cheat": {
"filters": {
"filters": {
"all": {
"exists": {
"field": "storename"
}
}
}
},
"aggs": {
"count": {
"value_count": {
"field": "storename"
}
},
"unique_count": {
"cardinality": {
"field": "storename"
}
},
"total_average": {
"bucket_script": {
"buckets_path": {
"total": "count",
"unique": "unique_count"
},
"script": "params.total / params.unique"
}
}
}
}
}
}
这是对aggs框架的一个小的滥用。但是,这个想法是,你实际上想要num_stores/num_docs。我将num_docs限制为仅使用storefield名称的文档。

我通过使用过滤器agg绕过了一些验证,这在技术上是一个多桶agg(尽管我只关心一个桶)。

然后我通过基数(num stores)和总计数(value_count)获得唯一计数,并使用bucket_script完成它。

总而言之,这是稍微混乱的结果:D
{
"took" : 3,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 6,
"relation" : "eq"
},
"max_score" : null,
"hits" : [ ]
},
"aggregations" : {
"cheat" : {
"buckets" : {
"all" : {
"doc_count" : 6,
"count" : {
"value" : 6
},
"unique_count" : {
"value" : 3
},
"total_average" : {
"value" : 2.0
}
}
}
},
"stores" : {
"doc_count_error_upper_bound" : 0,
"sum_other_doc_count" : 1,
"buckets" : [
{
"key" : "baz",
"doc_count" : 3
},
{
"key" : "foo",
"doc_count" : 2
}
]
},
"average_sales_count" : {
"value" : 2.5
}
}
}

注意cheat.buckets.all.total_average2.0(真实平均值),而旧方法(管道平均值)是2.5的非全局平均值