👓
olivereを使ってElastic Searchのネストした集計を実行する
はじめに
Go言語のElastic Searchのクライアントを調べると、
の2つが候補になるかと思います。
今回はolivere
クライアントを使った、
ネストした集約クエリの実装方法と実行結果の取得方法についてまとめます。
(補足) olivereクライアントは2022/07/23現在Elastic Search v8以降に対応していないので注意してください
実行環境
Go: 1.18
Elastic Search: 7.17
サンプルIndex
サンプルとして以下ようなIndexを想定します
POST users/_bulk
{ "index" : {} }
{ "name": "user1", "age": 15, "created_at": "2022-07-01" }
{ "index" : {} }
{ "name": "user2", "age": 18, "created_at": "2022-07-01" }
{ "index" : {} }
{ "name": "user3", "age": 22, "created_at": "2022-07-02" }
{ "index" : {} }
{ "name": "user4", "age": 34, "created_at": "2022-07-02" }
{ "index" : {} }
{ "name": "user5", "age": 14, "created_at": "2022-07-03" }
{ "index" : {} }
{ "name": "user6", "age": 40, "created_at": "2022-07-03" }
{ "index" : {} }
{ "name": "user7", "age": 60, "created_at": "2022-07-03" }
集計の設定
実際は検索結果を集計したい場面も多いと思うので、
- 検索
- 18歳以上のユーザーで絞り込み
- 集計
- 作成日で1日ごとにグループ化
- Date histogram aggregationで集計できます
- グループ内のユーザーの平均年齢を計算
- Avg aggregationで集計できます
- 作成日で1日ごとにグループ化
という設定で集計をしてみようと思います
想定クエリ
上記の要件でElastic Searchのクエリを作成すると以下になります
クエリ
GET users/_search?pretty
{
"size": 0,
"query": {
"range": {
"age": {
"gte": 18
}
}
},
"aggs": {
"group_by_created_at": {
"date_histogram": {
"calendar_interval": "day",
"field": "created_at"
},
"aggs": {
"average_age": {
"avg": {
"field": "age"
}
}
}
}
}
}
実行結果
{
"took" : 3,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 5,
"relation" : "eq"
},
"max_score" : null,
"hits" : [ ]
},
"aggregations" : {
"group_by_created_at" : {
"buckets" : [
{
"key_as_string" : "2022-07-01T00:00:00.000Z",
"key" : 1656633600000,
"doc_count" : 1,
"average_age" : {
"value" : 18.0
}
},
{
"key_as_string" : "2022-07-02T00:00:00.000Z",
"key" : 1656720000000,
"doc_count" : 2,
"average_age" : {
"value" : 28.0
}
},
{
"key_as_string" : "2022-07-03T00:00:00.000Z",
"key" : 1656806400000,
"doc_count" : 2,
"average_age" : {
"value" : 50.0
}
}
]
}
}
}
olivereによるクエリ実行
上記のクエリをoliverクライアントを使って記述すると以下になります
package main
import (
"context"
"fmt"
"github.com/olivere/elastic/v7"
)
func main() {
// See: https://olivere.github.io/elastic/#getting-started
client, err := elastic.NewClient(
elastic.SetURL("http://es:9200"),
elastic.SetBasicAuth("elastic", "password"),
)
srv := client.Search("users").Size(0)
// 1. 検索クエリを定義
query := elastic.NewRangeQuery("age").Gte(18)
srv.Query(query)
// 2. 集約クエリを定義
agg := elastic.NewDateHistogramAggregation().
Field("created_at").
CalendarInterval("day")
{
// subクエリを定義
subAgg := elastic.NewAvgAggregation().Field("age")
agg.SubAggregation("average_age", subAgg)
}
srv.Aggregation("group_by_created_at", agg)
// 3. 実行
ctx := context.Background()
result, err := srv.Pretty(true).Do(ctx)
if err != nil {
panic(err)
}
// 4. 集約結果を取得
dateHistItems, found := result.Aggregations.DateHistogram("group_by_created_at")
if !found {
panic("not found")
}
for _, bucket := range dateHistItems.Buckets {
fmt.Println("--- date ---")
fmt.Println(*bucket.KeyAsString)
avgResult, found := bucket.Aggregations.Avg("average_age")
if !found {
panic("not found")
}
fmt.Println("--- average age ---")
fmt.Println(*avgResult.Value)
fmt.Println()
}
}
実行結果
--- date ---
2022-07-01T00:00:00.000Z
--- average age ---
18
--- date ---
2022-07-02T00:00:00.000Z
--- average age ---
28
--- date ---
2022-07-03T00:00:00.000Z
--- average age ---
50
おわりに
olivereはメソッドチェーンのように記述でき、慣れてくると直感的に実装出来るようになるのでおすすめです。
ただし、複雑な集計をした実行結果を取得する方法に関するの記事が少なかったので、
少しでも役に立てば幸いです。
Discussion