ElasticsearchでAggregationの結果に対する絞り込み(SQLのHAVING句)を行う

[Bucket Selector Aggregation](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-pipeline-bucket-selector-aggregation.html)を使うことでElasticsearchのAggregationの結果に対する絞り込み、SQLで言うところのHAVING句を実現できます。

#環境
Elasticsearch 6.2.4

# サンプルデータ
以下のように店名、商品名、個数を持っているデータがいくつか入っています。

```json
{
    "market_name": "A八百屋",
    "product_name": "にんじん",
    "count": 10
},
{
    "market_name": "Bスーパー",
    "product_name": "りんご"
    "count": 10
},
{
    "market_name": "Cマーケット",
    "product_name": "セロリ"
    "count": 5
}
```

# 特定の条件を満たす集計結果の絞り込み
集計した合計値が一定の値以上の場合など、特定の条件を満たす集計結果だけに絞りこむ事が出来ます。
例えば商品の個数の合計が50以上のデータのみに絞り込む場合は以下のようなクエリになります。

```sql:SQLのイメージ
SELECT product_name, SUM(count)
FROM product
GROUP BY product_name
HAVING SUM(count) > 50
```
```json:query
{
  "size": 0,
  "aggs": {
    "product_name_aggs": {
      "terms": {
        "field": "product_name",
        "size": 100
      },
      "aggs": {
        "count_sum": {
          "sum": {
            "field": "count"
          }
        },
        "count_sum_bucket": {
          "bucket_selector": {
            "buckets_path": {
              "product_count_sum": "count_sum"
            },
            "script": "params.product_count_sum > 50"
          }
        }
      }
    }
  }
}
```
```json:response(aggregations)
{
  "aggregations": {
    "product_name_aggs": {
      "doc_count_error_upper_bound": 0,
      "sum_other_doc_count": 0,
      "buckets": [
        {
          "key": "にんじん",
          "doc_count": 9,
          "count_sum": {
            "value": 63
          }
        }
      ]
    }
  }
}
```
`buckets_path`に絞り込み条件の対象となる項目を指定します。
今回はHAVING句のSUM(count)に相当する`count_sum`（自分で命名したAggregation名）を条件指定の対象にしています。

`script`には絞り込み条件を記述します。
`buckets_path`で指定した値は変数名を利用することで参照できます。
今回は`product_count_sum`という変数名をつけたので、`params.product_count_sum`という形で指定します。
`params`は実行したクエリ内の変数を参照する場合に利用する値です。

ちなみに以下のAggregationを使うことでSQLの集計関数を置き換えることが出来ます。

|Elasticsearch|SQL|
|:-----------------|:-----------------|
|[Avg Aggregation](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-metrics-avg-aggregation.html)|AVG|
|[Max Aggregation](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-metrics-max-aggregation.html)|MAX|
|[Min Aggregation](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-metrics-min-aggregation.html)|MIN|
|[Sum Aggregation](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-metrics-sum-aggregation.html)|SUM|

# doc_countに対する絞り込み
Aggregationで得られた`doc_count`に対して絞り込みを行いたい場合、`buckets_path`に`_count`を指定することで実現できます。
（`_count`の[公式ドキュメント](https://www.elastic.co/guide/en/elasticsearch/reference/6.2/search-aggregations-pipeline.html#_special_paths))

例えば商品名で集約した場合に5件以上存在する商品のみに絞り込む場合は以下のようなクエリになります。

```sql:SQLのイメージ
SELECT product_name, COUNT(product_name)
FROM product
GROUP BY product_name
HAVING COUNT(product_name) >= 5
```
```json:query
{
  "size": 0,
  "aggs": {
    "product_name_aggs": {
      "terms": {
        "field": "product_name",
        "size": 100
      },
      "aggs": {
        "product_name_bucket": {
          "bucket_selector": {
            "buckets_path": {
              "product_count": "_count"
            },
            "script": "params.product_count >= 5"
          }
        }
      }
    }
  }
}
```
```json:response(aggregations)
{
  "aggregations": {
    "product_name_aggs": {
      "doc_count_error_upper_bound": 0,
      "sum_other_doc_count": 0,
      "buckets": [
        {
          "key": "にんじん",
          "doc_count": 9
        },
        {
          "key": "りんご",
          "doc_count": 8
        },
        {
          "key": "白菜",
          "doc_count": 5
        }
      ]
    }
  }
}
```