【问题标题】:Perform a pipelines aggregation over the full set of potential buckets对完整的潜在存储桶集执行管道聚合
【发布时间】:2021-12-06 14:40:48
【问题描述】:

使用 Elasticsearch 的 _search API 时,如果您将 size 设置为 10,并执行 avg 指标聚合,则平均值将是整个数据集中与查询匹配的所有值,而不仅仅是 10 个的平均值hits 数组中返回的项目。

另一方面,如果您执行 terms 聚合并将 terms 聚合的 size 设置为 10,则对这些 terms 存储桶执行 avg_buckets 聚合将计算超过只有这 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"
      }
    }
  }
}

无论 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 家商店。

【问题讨论】:

  • 你能分享你正在运行的查询吗?
  • 先生。 Walkom,我已经分享了疑问。我试图使示例尽可能简单,同时仍然显示我的问题。

标签: elasticsearch elasticsearch-aggregation


【解决方案1】:

您可以在没有管道聚合的情况下实现您的目标。它在某种程度上欺骗了聚合框架,但它确实有效。

这是数据设置:

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_average2.0(真实平均值),而旧方法(管道平均值)是 2.5 的非全局平均值

【讨论】:

  • 如果我们想找到所有商店销售的帽子的中位数(即第 50 个百分位数)怎么办。在平均的情况下,我们可以作弊并计算num_stores / num_docs,但一般来说,我们如何计算所有桶的某个值(售出的帽子数量)并对该值执行度量聚合(平均值、中值等) ,但实际上只返回几个桶?
  • 这是一个带有百分位数的选项:elastic.co/guide/en/elasticsearch/reference/current/…。您可以在这里使用相同的技巧。至于“计算每个存储桶,但只返回一对”如果您真的想在所有情况下都这样做,您需要使用transforms 首先透视您的数据(存储桶变成文档),然后再次聚合。
猜你喜欢
  • 2016-10-31
  • 2016-09-08
  • 2019-08-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-09-22
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多