首页 > 解决方案 > 对完整的潜在存储桶集执行管道聚合

问题描述

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

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

如何计算所有潜在存储桶中某个字段的平均值,但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"
      }
    }
  }
}

无论设置为 0、3 还是其他任何值,都average_hat_price将是相同的。size

好的,现在我想找到销售帽子数量最多的前 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仅针对梅西百货、沃尔玛和迪拉德百货进行计算。如果我想找到所有商店的平均值,我必须设置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 家商店。

标签: elasticsearchelasticsearch-aggregation

解决方案


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

这是数据设置:

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)获得唯一计数,并使用 abucket_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


推荐阅读