对整套潜在存储桶执行管道聚合

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

的非全局平均值