【応用編】Elasticsearchの検索クエリを使いこなそう

Elasticsearch-Logo-Color-V

この記事はeureka Engineering Advent Calendar 2017 14日目の記事です。
昨日は山内さんの「【HTML/CSS】たかがフェードイン/フェードアウトするだけの挙動に全力で取り組んだ結果、最強のCSSができてしまった話【最強】」でした。


こんにちは! Pairsのサーバサイドを担当している小島です。


去年はアドベントカレンダーでElasticsearchの基礎的なクエリについて書きました。
今回はその基礎編に続き、応用編として特殊な検索を行うためのクエリについて書きます。


【基礎編】Elasticsearchの検索クエリを使いこなそう

もくじ

  • More Like This クエリについて
  • Percolate クエリについて
  • Geo クエリについて

環境

Elasticsearch 6.0.1
Mac ローカル環境


サンプルデータは公式にのっているkibanaのtutorialで使用しているデータを使用しています。
Kibana User Guide [6.0] — Loading Sample Data

More Like This クエリ

More Like Thisクエリは類似文書検索をするクエリです。
テキストまたはドキュメントのIDを指定し、類似した文章を検索することができます。


まずは簡単にテキストを指定して検索してみましょう。


サンプルではtext_entryに文章が入っているのでfieldsにtext_entryを指定します。
likeには検索したいテキストをいれます。今回は適当なものをサンプルから選んでそのまま使用しました。

curl -XGET "http://localhost:9200/_search" -H 'Content-Type: application/json' -d'
{
"query": {
"more_like_this": {
"fields": [
"text_entry"
],
"like": "To be, or not to be: that is the question"
}
}
}'

検索結果

{
"took": 9,
"timed_out": false,
"_shards": {
"total": 36,
"successful": 36,
"skipped": 0,
"failed": 0
},
"hits": {
"total": 22520,
"max_score": 6.3361917,
"hits": [
{
"_index": "shakespeare",
"_type": "doc",
"_id": "108434",
"_score": 6.3361917,
"_source": {
"type": "line",
"line_id": 108435,
"play_name": "A Winters Tale",
"speech_number": 97,
"line_number": "1.2.471",
"speaker": "POLIXENES",
"text_entry": "Which way to be prevented, if to be;"
}
},
{
"_index": "shakespeare",
"_type": "doc",
"_id": "1461",
"_score": 6.2816606,
"_source": {
"type": "line",
"line_id": 1462,
"play_name": "Henry IV",
"speech_number": 168,
"line_number": "2.4.456",
"speaker": "FALSTAFF",
"text_entry": "to be fat be to be hated, then Pharaohs lean kine"
}
},

]
}
}

それらしい似た単語の入った文章をもつドキュメントが取得できました。


ドキュメントIDで指定する場合は以下のようなクエリにします。

curl -XGET "http://localhost:9200/_search" -H 'Content-Type: application/json' -d'
{
"query": {
"more_like_this": {
"fields": [
"text_entry"
],
"like": [
{
"_index": "shakespeare",
"_type": "doc",
"_id": 34229
}
]
}
}
}'

テキストで指定した場合と同じ結果が返ってくることが確認できると思います。
上記のクエリからわかると思いますが、likeの中身には複数指定することもでき、IDとテキストを同時に指定することも可能です。


また検索の対象としたくない文章がある場合にはunlikeを指定することで検索結果から該当の文章に一致するドキュメントを検索結果から弾くことができます。


More Like Thisクエリには文章一致の仕方をチューニングするパラメータが複数あるので、結果をみつつ最適なものを設定する必要があります。
More Like This Query — Term Selection Parameters

Percolateクエリ

これまで紹介した検索クエリは「検索条件から一致するドキュメント」を探すためのものでした。
Percolateクエリはその逆で「ドキュメントから一致する検索条件」を探すことができます。


サンプルで作成したbankインデックスのデータを参考に検索条件用のインデックスを作るところから始めます。

curl -XPUT "http://localhost:9200/condition" -H 'Content-Type: application/json' -d'
{
"mappings": {
"doc": {
"properties": {
"query": {
"type": "percolator"
},
"gender": {
"type": "keyword"
},
"state": {
"type": "keyword"
}
}
}
}
}'

queryフィールドをpercolator型として定義し検索クエリを登録します。
queryフィールドの他には検索条件として使用するフィールドを一緒に定義する必要があります。


今回はgender, stateのみを定義したインデックスを用意します。

次に検索条件を作成します。
今回は以下2つを用意します。

  • 女性を取得するクエリ
  • stateがDCの女性を取得するクエリ
curl -XPOST "http://localhost:9200/condition/doc/1" -H 'Content-Type: application/json' -d'
{
"query": {
"term": {
"gender": {
"value": "F"
}
}
}
}'
curl -XPOST "http://localhost:9200/condition/doc/2" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
"must": [
{
"term": {
"gender": {
"value": "F"
}
}
},
{
"term": {
"state": {
"value": "DC"
}
}
}
]
}
}
}'

これでインデックスの準備が整いました。

以下の検索クエリを試します。

curl -XGET "http://localhost:9200/condition/_search" -H 'Content-Type: application/json' -d'
{
"query": {
"percolate": {
"field": "query",
"document": {
"account_number": 147,
"balance": 35921,
"firstname": "Charmaine",
"lastname": "Whitney",
"age": 28,
"gender": "F",
"address": "484 Seton Place",
"employer": "Comveyer",
"email": "charmainewhitney@comveyer.com",
"city": "Dexter",
"state": "DC"
}
}
}
}'

検索結果

{
"took": 5,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"skipped": 0,
"failed": 0
},
"hits": {
"total": 2,
"max_score": 0.5753642,
"hits": [
{
"_index": "condition",
"_type": "doc",
"_id": "1",
"_score": 0.5753642,
"_source": {
"query": {
"bool": {
"must": [
{
"term": {
"gender": {
"value": "F"
}
}
},
{
"term": {
"state": {
"value": "DC"
}
}
}
]
}
}
}
},
{
"_index": "condition",
"_type": "doc",
"_id": "2",
"_score": 0.2876821,
"_source": {
"query": {
"term": {
"gender": {
"value": "F"
}
}
}
}
}
]
}
}

登録した検索条件を取り出すことができ、指定したドキュメントに一致する条件の多い検索条件クエリを上位にして取得することができました。
検索クエリに設定したdocument内にはconditionインデックスに定義していないフィールドをいれても無視してくれるので、わざわざ必要なフィールドのみを抜き出す必要がないのが地味にうれしいですね。

Geoクエリ

Elasticsearchは位置情報用の型としてgeo_pointとgeo_shapesの2つの型が用意されています。
geo_pointは緯度経度で、geo_shapesは点、線、面など図形で位置情報を範囲で表現できます。
geo_pointとgeo_shapesでは使用できる検索クエリが異なるのでそれぞれ紹介します。

geo_pointフィールド

サンプルのlogstashインデックスにはgeo_pointデータが入っています。
logstashインデックス ドキュメント一部抜粋

"geo": {
"coordinates": {
"lat": 36.38025,
"lon": -88.98547778
},
"src": "US",
"dest": "ET",
"srcdest": "US:ET"
},

配列で表現する場合 [lon, lat] の並びに変わるので注意が必要です。

Geo Distanceクエリ

緯度経度と距離を指定し、指定地点からの距離内の緯度経度データをもつドキュメントを検索するクエリです。

curl -XGET "http://localhost:9200/logstash-*/_search" -H 'Content-Type: application/json' -d'
{
"query": {
"bool" : {
"filter" : {
"geo_distance" : {
"distance" : "10km",
"geo.coordinates" : {
"lat" : 38.907192,
"lon" : -77.036871
}
}
}
}
}
}'

上記のクエリはワシントンDCの中心地点から10km以内のドキュメントを検索しています。

Geo Bounding Boxクエリ

Geo Bounding Boxクエリは四角形のtop_left, bottom_rightの2点の緯度経度を指定し、その範囲内に位置する緯度経度データをもつドキュメントを検索できます。

curl -XGET "http://localhost:9200/logstash-*/_search" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
"filter": {
"geo_bounding_box": {
"geo.coordinates": {
"top_left": [-78, 39],
"bottom_right": [-77, 38]
}
}
}
}
}
}'

Geo Polygon クエリ

指定した多角形の中に含まれる緯度経度データをもつドキュメントを検索できます。

curl -XGET "http://localhost:9200/logstash-*/_search" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
"must": {
"match_all": {}
},
"filter": {
"geo_polygon": {
"geo.coordinates": {
"points": [
{"lat": 40,"lon": -70},
{"lat": 30,"lon": -80},
{"lat": 20,"lon": -90}
]
}
}
}
}
}
}'

ソート

geo_point型の場合は_geo_distanceクエリで距離でのソートを行うことが可能です。

"sort": [
{
"_geo_distance": {
"geo.coordinates": {
"lat": 38.907192,
"lon": -77.036871
},
"order": "asc"
}
}
]

これで距離の近い順でドキュメントを取得することができます。

geo_shapes

geo_shapesクエリはサンプルにはデータがないので簡単なデータを作成します。
以下でインデックスとドキュメントを作成します。

curl -XPUT "http://localhost:9200/geo" -H 'Content-Type: application/json' -d'
{
"mappings": {
"doc": {
"properties": {
"location": {
"type": "geo_shape"
},
"name": {
"type": "keyword"
}
}
}
}
}'
curl -XPUT "http://localhost:9200/geo/doc/1" -H 'Content-Type: application/json' -d'
{
"location": {
"type": "envelope",
"coordinates": [
[
139.765077,35.68366
],
[
139.769584,35.678814
]
],
"name": "tokyo station"
}
}'

Geo Shapesクエリ

geo_shape型同様点、線、面など図形で範囲を指定し検索を行います。

curl -XGET "http://localhost:9200/geo/_search" -H 'Content-Type: application/json' -d'
{
"query": {
"bool": {
"must": {
"match_all": {}
},
"filter": {
"geo_shape": {
"location": {
"shape": {
"type": "envelope",
"coordinates": [
[139.761601,35.680697],
[139.775635,35.679442]
]
},
"relation":"intersects"
}
}
}
}
}
}'

relationには以下の値が指定できます。

  • intersects — 交差するドキュメントを返す
  • disjoint — 指定範囲外のドキュメントを返す
  • within — 指定した範囲の中にgeo_shapeが収まっているドキュメントを返す
  • contains — 指定した範囲がgeo_shape内に収まっているドキュメントを返す

細かいパラメーターは省きますが、これだけでいろいろなケースの位置情報検索を実現することができます。

最後に

今回3つの検索クエリについて書きました。どれもElasticsearchを使っているならおさえておきたい機能だと思います。
僕自身最初はハードル高く感じていたのですが、実際に触ってみるとクエリがシンプルなので簡単に動かせました。まだ使ったことない方などぜひこの機会に使ってみてください。

明日はBIチームのクールガイ田中くんが担当です。お楽しみに