对整套潜在存储桶执行管道聚合
Perform a pipelines aggregation over the full set of potential buckets
当使用 Elasticsearch 的 _search
API 时,如果将大小设置为 10,并执行 avg
度量聚合,则平均值将是整个数据集匹配的所有值查询,而不仅仅是 hits
数组中返回的 10 项的平均值。
另一方面,如果执行 terms
聚合并将 terms
聚合的 size
设置为 10,则执行 avg_buckets
聚合这些 terms
个桶将仅计算这 10 个桶的平均值 - 而不是所有潜在的桶。
如何计算所有潜在存储桶中某些字段的平均值,但 buckets
数组中仍然只有 10 个项目?
为了使我的问题更具体,请考虑以下示例:假设我是一名制帽商。多家商店都有我的帽子。我有一个 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"
}
}
}
}
和 average_hat_price
将是相同的,无论 size
设置为 0、3 还是其他值。
好的,现在我想找到销售帽子数量最多的前 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
名称的文档。
我通过使用 filters 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_average
是 2.0
(真实平均值),而旧方法(管道平均值)是 2.5
的非全局平均值
当使用 Elasticsearch 的 _search
API 时,如果将大小设置为 10,并执行 avg
度量聚合,则平均值将是整个数据集匹配的所有值查询,而不仅仅是 hits
数组中返回的 10 项的平均值。
另一方面,如果执行 terms
聚合并将 terms
聚合的 size
设置为 10,则执行 avg_buckets
聚合这些 terms
个桶将仅计算这 10 个桶的平均值 - 而不是所有潜在的桶。
如何计算所有潜在存储桶中某些字段的平均值,但 buckets
数组中仍然只有 10 个项目?
为了使我的问题更具体,请考虑以下示例:假设我是一名制帽商。多家商店都有我的帽子。我有一个 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"
}
}
}
}
和 average_hat_price
将是相同的,无论 size
设置为 0、3 还是其他值。
好的,现在我想找到销售帽子数量最多的前 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
名称的文档。
我通过使用 filters 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_average
是 2.0
(真实平均值),而旧方法(管道平均值)是 2.5