search
LoginSignup
5
Help us understand the problem. What are the problem?

More than 3 years have passed since last update.

nana-musicnana music Advent Calendar 2018 Day 15

posted at

updated at

Organization

elasticsearch-dsl で集計を伴う検索クエリを書く

Elasticsearch の高レベル Python クライアントである elasticsearch-dsl (ドキュメント) を使ってみました1
elasticsearch-dsl を用いると、低レベル Python クライアントである elasticsearch を用いる場合に比べて直観的に検索クエリを作成することができます。

事前準備

まずは下記のようなインデックス・データを作成します。

from elasticsearch import Elasticsearch
elastic = Elasticsearch()

index = "dsl_sample"
elastic.indices.create(index)

data = [                                                         
    {"hoge": 1, "fuga": 1, "value": 3},
    {"hoge": 1, "fuga": 2, "value": 4},
    {"hoge": 2, "fuga": 1, "value": 5},
    {"hoge": 2, "fuga": 2, "value": 6},
    {"hoge": 1, "fuga": 1, "value": 100}
]

for i, body in enumerate(data, 1):
    elastic.index(index=index, doc_type="_doc", id=i, body=body)

検索・集計をする

今回は下記のような集計を行う検索クエリを作ってみます。

  • valueが1以上100未満の値のみが集計対象となるようにフィルタする
  • 1つのクエリで「hoge で Groub By した集計結果」と「fuga で Group By した集計結果」の2つを取得する

このクエリは elasticsearch-dsl を用いて下記のように実行できます。

from elasticsearch_dsl import Search, A

search = Search(using=elastic, index=index)  # ① Search オブジェクトの作成
search = search.filter("range", value={"gte": 1, "lt": 100})  # ②フィルタ条件の記載
search = search.extra(size=0)  # ③ドキュメントの取得件数を指定

# ④ Aggregation オブジェクトの作成
aggs_hoge = A("terms", field="hoge", size=10).metric('sum_value', 'sum', field='value')
aggs_fuga = A("terms", field="fuga", size=10).metric('sum_value', 'sum', field='value')

# ⑤ Aggregation オブジェクトを Search オブジェクトに紐付ける
search.aggs.bucket("aggs_name_hoge", aggs_hoge)
search.aggs.bucket("aggs_name_fuga", aggs_fuga)

response = search.execute()
  • ①で検索に用いるオブジェクトを作成します。例のように using で既に作成した低レベルクライアントを渡すことも可能ですし、 connections を用いて接続することもできるようです。
  • 今回は集計結果が取得できればよく、ドキュメント自体を返却する必要はないので③で取得件数を 0 に指定しています。extraを用いれば検索リクエストに任意のプロパティ値を設定できるようです。
  • ④の A で集計について定義しています。
    • Afield で集計のキーを指定しています。
    • metric を用いて、どのカラムをどのように集計するのかを指定します。なおドキュメント数のみで良い場合は metric の指定が不要となります。
  • ⑤で検索オブジェクトに集計オブジェクトを関連づけます。このように2回 bucket を呼ぶことで、2種類の集計が関連づけられます。

説明のために行を分けましたが、①〜③あたりは Search(・・・).filter(・・・).extra(・・・) みたいにメソッドチェーンで直観的に書けるのが良いですね。

結果に対しては下記のようにアクセス可能です。

response.aggregations.aggs_name_hoge.buckets
# [{'key': 1, 'doc_count': 2, 'sum_value': {'value': 7.0}}, {'key': 2, 'doc_count': 2, 'sum_value': {'value': 11.0}}]

response.aggregations.aggs_name_fuga.buckets
# [{'key': 1, 'doc_count': 2, 'sum_value': {'value': 8.0}}, {'key': 2, 'doc_count': 2, 'sum_value': {'value': 10.0}}]

正しく集計されていることがわかります。

dict のクエリを確認する

elasticsearch-dsl では to_dict を用いて、低レベルクライアントでリクエストする場合の検索クエリ dict を取得することができます。

search.to_dict()

# {
#   'query': {
#     'bool': {
#       'filter': [
#         {
#           'range': {
#             'value': {
#               'gte': 1,
#               'lt': 100
#             }
#           }
#         }
#       ]
#     }
#   },
#   'aggs': {
#     'aggs_name_hoge': {
#       'terms': {
#         'field': 'hoge',
#         'size': 10
#       },
#       'aggs': {
#         'sum_value': {
#           'sum': {
#             'field': 'value'
#           }
#         }
#       }
#     },
#     'aggs_name_fuga': {
#       'terms': {
#         'field': 'fuga',
#         'size': 10
#       },
#       'aggs': {
#         'sum_value': {
#           'sum': {
#             'field': 'value'
#           }
#         }
#       }
#     }
#   },
#   'size': 0
# }

低レベル Python クライアントの elasticsearch を用いて検索クエリを作る場合はこの dict を直接作成する必要があるので、それに比べると elasticsearch-dsl を使えばだいぶ楽に検索クエリが書ける気がしますね。

おわりに

今回は elasticsaerch-dsl で簡単な集計クエリを書いてみました。
「集計を2つ設定するにはどうするのか?」とか「size=0を指定するにはどうするのか?」など最初は少し試行錯誤しましたが、慣れれば django の QuerySet のようにメソッドチェーンで検索クエリが書けるので直観的で良いなと思いました。

明日は @usangit が何か書いてくれます。お楽しみに!


  1. 今回は elasticsearch-dsl のバージョンとして 6.3.0 を使いました。 

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
5
Help us understand the problem. What are the problem?