Elasticsearchには、Search Templateという、よく使うクエリをElasticsearch自体に登録して、それをテンプレートにして検索クエリの一部を差し替えたような検索ができます。
使い所によって、共通化、タイプ量削減、設計方針の強制、コンセプトの共有、いろいろな捉え方ができると思います。
テンプレートのフォーマット指定は、mustache風です。
私の中では、存在は知っていたものの、(後半に示しているJSONをパラメータに受け取ってそのままプレースホルダーにはめ込みできるというところが特に)再発見でしたので、勉強メモとしてこちらにサンプル例を書き連ねてみます。
確認に使ったElasticsearchのバージョンは ver6.8です。
- もっともシンプルな例
- いろいろな例
- 001(オーソドックス:検索ワードの当てはめ)
- 002 (001と同じだが、複雑なクエリも取り扱えるらしいことの再確認)
- 003 (プロパティ名やクエリDSLのキモになるシンタックス寄りのプロパティ名も外から与えられる)
- 004(mustacheなので、ループっぽいこともできなくない)
- 005(ループのシンタックスシュガー的な例)
- 006(source部分を文字列とすると、もっとトリッキーなことができる。もちろん、その場でエラーに気づきにくくはなる。また、「toJson」というEsオリジナルのマジック命令がある。)
- 006_2(個人的にはこういう「カタチ」のものをkibanaでPoCの時にSearch Template活用するのかなという例)
- 006_3(ループのネスト風)
- 007(大きく骨組みのクエリDSLのカタチを定めておき、後からサブのクエリDSLを差し込む)
もっともシンプルな例
こうやって差し代わるのねという例。
# 「ID123」のテンプレートを登録 GET _scripts/ID123 { "script": { "lang": "mustache", "source": { "query": { "match_all": {} }, "size": "{{my_size}}" } } } ↓ (登録成功) { "acknowledged" : true }
↓ 実際に検索してみる
# 「ID123」のテンプレートを下敷きに、「my_size」プレースホルダーに「5」を設定して、検索する。 GET 検索したいインデックス名/_search/template { "id" : "ID123", "params" : { "my_size" : 5 } } ↓ (これと同等) GET 検索したいインデックス名/_search { "query": { "match_all": {} }, "size": 5 } ↓ ★★★検索結果が戻る★★★
いろいろな例
以下、サーチテンプレートのkibana登録とそれを使った検索クエリの2つを対にして示しています。 (途中、検索シナリオとして見ると、リアリティの無い例も入っています。)
001(オーソドックス:検索ワードの当てはめ)
POST _scripts/my_searchTmpl001 { "script": { "lang": "mustache", "source": { "query": { "match": { "name": "{{query_string}}" } } } } } GET /_search/template { "id":"my_searchTmpl001", "params":{ "query_string": "六本木" } }
002 (001と同じだが、複雑なクエリも取り扱えるらしいことの再確認)
POST _scripts/my_searchTmpl002 { "script": { "lang": "mustache", "source": { "query": { "bool": { "must": { "match": { "name": "{{query_string}}" } } } } } } } GET /_search/template { "id":"my_searchTmpl002", "params":{ "query_string": "六本木" } }
003 (プロパティ名やクエリDSLのキモになるシンタックス寄りのプロパティ名も外から与えられる)
POST _scripts/my_searchTmpl003 { "script": { "lang": "mustache", "source": { "query": { "bool": { "must": { "{{prop}}": { "name": "{{query_string}}" } } } } } } } GET /_search/template { "id":"my_searchTmpl003", "params":{ "query_string": "六本木", "prop":"match" } }
004(mustacheなので、ループっぽいこともできなくない)
POST _scripts/my_searchTmpl004 { "script": { "lang": "mustache", "source": { "query": { "bool": { "must": { "match": { "name": " {{#lp}}{{val}} {{/lp}}" } } } } } } } GET /_search/template { "id":"my_searchTmpl004", "params":{ "lp":[ {"val":"秋葉原"}, {"val":"渋谷"}, {"val":"品川"}, {"val":"新宿"} ] } }
005(ループのシンタックスシュガー的な例)
POST _scripts/my_searchTmpl005 { "script": { "lang": "mustache", "source": { "query": { "bool": { "must": { "{{prop}}": { "name": " {{#lp}}{{.}} {{/lp}}" } } } } } } } GET /ldgourmet/_search/template { "id":"my_searchTmpl005", "params":{ "prop":"match", "lp":[ "秋葉原", "渋谷", "品川", "新宿" ] } }
006(source部分を文字列とすると、もっとトリッキーなことができる。もちろん、その場でエラーに気づきにくくはなる。また、「toJson」というEsオリジナルのマジック命令がある。)
POST _scripts/my_searchTmpl006 { "script": { "lang": "mustache", "source": """{ "query": { "bool": { "must": {{#toJson}}my_match_all{{/toJson}} } } }""" } } GET /ldgourmet/_search/template { "id":"my_searchTmpl006", "params":{ "my_match_all":[{ "match_all":{} }] } }
006_2(個人的にはこういう「カタチ」のものをkibanaでPoCの時にSearch Template活用するのかなという例)
POST _scripts/my_searchTmpl006_2 { "script": { "lang": "mustache", "source": """ { "query": { "bool": { "must": [ { "match": { "address":{ "query": "{{area}}", "operator": "or" } } }, { "match": { "stas":{ "query": "{{area}}", "operator": "or" } } } ] } } } """ } } GET /ldgourmet/_search/template { "id":"my_searchTmpl006_2", "params":{ "area":"大崎 目黒 池袋" } }
006_3(ループのネスト風)
POST _scripts/my_searchTmpl006_3 { "script": { "lang": "mustache", "source": """ { "query": { "bool": { "must": [ {{#my_searchFields}} { "match": { "{{.}}":{ "query": "{{area}}", "operator": "or" } } }, {{/my_searchFields}} { "match_all": {} } ] } } } """ } } GET /ldgourmet/_search/template { "id":"my_searchTmpl006_3", "params":{ "area":"大崎 目黒 池袋", "my_searchFields":[ "name", "address", "stas" ] } }
007(大きく骨組みのクエリDSLのカタチを定めておき、後からサブのクエリDSLを差し込む)
今までよく知らなかったけど、これはプロダクトの実際のクエリに使ってみたい気持ちとなった例。本当はあと一声なんだが、それをEsやmustacheに求めるのは筋違いかも。
POST _scripts/my_searchTmpl007 { "script": { "lang": "mustache", "source": """ { "query": { "bool": { "must": {{#toJson}}arrayOfQueries{{/toJson}} } } } """ } } GET /ldgourmet/_search/template { "id":"my_searchTmpl007", "params":{ "arrayOfQueries":[ {"match": { "address":{ "query": "東京 大阪 名古屋"}}}, {"match": { "stas": { "query": "東京 大阪 名古屋"}}}, {"match": { "name": { "query": "東京 大阪 名古屋"}}} ] } }