使用 Elasticsearch 进行总和/平均值的子聚合

And*_*eas 4 elasticsearch

我有一个包含字段user_idamountCategory的索引交易。我想计算每个用户和类别的平均金额,然后仅获取每个类别的平均总金额。SQL 看起来像这样:

SELET AVG(average), category from

    (SELECT user_id, category, AVG(amount) AS average FROM transactions WHERE amount < 100000 
    GROUP BY user_id, category) AS a1

GROUP BY category
Run Code Online (Sandbox Code Playgroud)

我目前只得到一个包含所有用户 ID 的存储桶的响应,然后在其中包含一个包含每个类别(针对用户)的平均金额的存储桶。我不明白如何添加另一个聚合来完成我想要的事情。

{
  "aggs": {
    "group_by_users": {
      "terms": {
        "field": "user_id.keyword"
      },
      "aggs": {
        "group_by_category": {
          "terms": {
              "field": "category.keyword"
          },
          "aggs": {
            "average_amount": {
              "avg": {
                "field": "amount"
              }
            }
          }
        }
      }
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

很感谢任何形式的帮助。

编辑:请求示例,因此这里首先是一些示例数据,然后是中间结果,中间结果将以底部的所需结果结束。

-----------------------------------------
|  user_id  |   category   |   amount   |
-----------------------------------------
|     1     |   insurances |   1000     |
|     1     |   transport  |     50     |
|     1     |   transport  |    100     |
|     2     |   insurances |    700     |
|     2     |   insurances |    200     |
|     2     |   transport  |    300     |
-----------------------------------------
Run Code Online (Sandbox Code Playgroud)

用户1传输的计算:(50+100)/2

因此,需要做的第一件事是按 user_id 和类别进行分组,以获得每个用户和类别的平均值。

这会产生:

-----------------------------------------
|  user_id  |   category   |   average  |
-----------------------------------------
|     1     |   insurances |   1000     |
|     1     |   transport  |     75     |
|     2     |   insurances |    450     |
|     2     |   transport  |    300     |
-----------------------------------------
Run Code Online (Sandbox Code Playgroud)

重要的是要了解我无法对所有用户进行平均,我首先需要每个用户、每个类别的平均支出。

所以现在我只想按类别分组并计算平均金额:

-----------------------------
|   category   |   average  |
-----------------------------
|   insurances |   725      |
|   transport  |   187,5    |
-----------------------------
Run Code Online (Sandbox Code Playgroud)

保险示例:(1000 + 450) / 2

Nik*_*iev 7

avg_bucket在某些情况下,管道聚合可以完成这项工作(但可能无法随着索引的大小很好地扩展,请参阅下面的注释):

POST myindex1/_search
{
  "size": 0,
  "aggs": {
    "by category": {
      "terms": {
        "field": "category.keyword"
      },
      "aggs": {
        "by user_id": {
          "terms": {
            "field": "user_id"
          },
          "aggs": {
            "avg by user": {
              "avg": {
                "field": "amount"
              }
            }
          }
        },
        "average by user, category": {
          "avg_bucket": {
            "buckets_path": "by user_id>avg by user"
          }
        }
      }
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

这将响应看起来像这样:

{
  ...
  "aggregations" : {
    "by category" : {
      "doc_count_error_upper_bound" : 0,
      "sum_other_doc_count" : 0,
      "buckets" : [
        {
          "key" : "insurances",
          "doc_count" : 3,
          "by user_id" : {
            "doc_count_error_upper_bound" : 0,
            "sum_other_doc_count" : 0,
            "buckets" : [
              {
                "key" : 2,
                "doc_count" : 2,
                "avg by user" : {
                  "value" : 450.0
                }
              },
              {
                "key" : 1,
                "doc_count" : 1,
                "avg by user" : {
                  "value" : 1000.0
                }
              }
            ]
          },
          "average by user, category" : {
            "value" : 725.0   <--- average for `insurances`
          }
        },
        {
          "key" : "transport",
          "doc_count" : 3,
          "by user_id" : {
            "doc_count_error_upper_bound" : 0,
            "sum_other_doc_count" : 0,
            "buckets" : [
              {
                "key" : 1,
                "doc_count" : 2,
                "avg by user" : {
                  "value" : 75.0
                }
              },
              {
                "key" : 2,
                "doc_count" : 1,
                "avg by user" : {
                  "value" : 300.0
                }
              }
            ]
          },
          "average by user, category" : {
            "value" : 187.5      <--- average for `transport`
          }
        }
      ]
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

它是如何工作的?

让我们从"by user_id" terms聚合开始:我们要求 Elasticsearch 对文档进行分组并使用聚合user_id计算平均值。amountavg

POST myindex1/_search
{
  "size": 0,
  "aggs": {
    "by user_id": {
      "terms": {
        "field": "user_id"
      },
      "aggs": {
        "avg by user": {
          "avg": {
            "field": "amount"
          }
        }
      }
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

这相当于 SQL:

SELECT user_id, avg(amount)
FROM my_index
GROUP BY user_id;
Run Code Online (Sandbox Code Playgroud)

到目前为止,这非常简单。但现在我们如何才能在该类别上进行平均呢?

我们可以在一个聚合之上添加另一个terms聚合。现在还将考虑类别:"by category""by user id"avg

POST myindex1/_search
{
  "size": 0,
  "aggs": {
    "by category": {
      "terms": {
        "field": "category.keyword"
      },
      "aggs": {
        "by user_id": {
          "terms": {
            "field": "user_id"
          },
          "aggs": {
            "avg by user": {
              "avg": {
                "field": "amount"
              }
            }
          }
        }
      }
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

这相当于 SQL:

SELECT user_id, category, avg(amount)
FROM my_index
GROUP BY user_id, category;
Run Code Online (Sandbox Code Playgroud)

我们现在可以使用上一个查询的结果并category再次聚合吗?

这可以通过管道聚合来完成avg_bucket。唯一缺少的是告诉avg_bucket聚合在哪里准确找到要聚合的存储桶,这是通过buckets_path表达式完成的。

这就是我们如何到达我在顶部发布的查询,它将有效地执行与您在问题中发布的 SQL 等效的操作。

但...

会出现什么问题?

此方法的缺点是它不能很好地适应索引中文档的数量。

事实上,管道聚合仅对已经聚合的数据进行操作:

管道聚合作用于其他聚合(而不是文档集)生成的输出,从而将信息添加到输出树中。

user_id在我们的例子中,这意味着如果索引中的不同项超过 10 个,我们的平均值将不准确。

发生这种情况是因为默认情况下terms聚合仅返回前 10 个存储桶,并且 SQL 等价物应如下所示:

SELECT category, avg(avg_amount)
FROM (
    SELECT user_id, category, avg(amount) avg_amount
    FROM my_index
    GROUP BY user_id, category
    LIMIT 10 per user_id
) Q
LIMIT 10;
Run Code Online (Sandbox Code Playgroud)

size可以通过聚合参数更改此限制terms

另一件需要记住的事情是terms返回近似的文档计数,这也会影响平均值。


希望有帮助!