使用Elasticsearch搜索模板简化查询
本文介绍Elasticsearch搜索模板,如何定义搜索模板、调用搜索模板,并通过示例进行说明。
1. 管理Elasticsearch搜索模板
Elasticsearch搜索模板与关系型数据库的存储过程类似。实际就是带变量的查询(使用Mustache模板语言),实际查询时使用模板参数替换变量。
下面示例定义搜索模板:
POST _scripts/<templateid>
{
"script": {
"lang": "mustache",
"source": {
"query": {
"match": {
"title": "{{query_string}}"
}
}
}
}
}
<templateid>
为搜索模板ID,{{query_string}}
是参数。
查看已定义的搜索模板:
GET _scripts/<templateid>
执行响应如下:
{
"script" : {
"lang" : "mustache",
"source" : "{\"query\":{\"match\":{\"title\":\"{{query_string}}\"}}}",
"options": {
"content_type" : "application/json; charset=UTF-8"
}
},
"_id": "<templateid>",
"found": true
}
删除搜索模板:
DELETE _scripts/<templateid>
2. 调试Elasticsearch搜索模板
2.1. 使用搜索模板
GET _search/template
{
"id": "<templateid>",
"params": {
"query_string": "search for these words"
}
}
可以在某索引下使用搜索模板,需要指定参数。<templateid>
为之前定义搜索模板ID.
2.2. 验证搜索模板
GET _render/template
{
"source": "{ \"query\": { \"terms\": {{#toJson}}statuses{{/toJson}} }}",
"params": {
"statuses" : {
"status": [ "pending", "published" ]
}
}
}
通过_render服务渲染模板,输出结果如下:
{
"template_output": {
"query": {
"terms": {
"status": [
"pending", "published"
]
}
}
}
}
存储搜索模板也可以使用下面命令进行验证:
GET _render/template/<template_name>
{
"params": {
"..."
}
}
当然也可以在请求体内指定id参数。
2.3. 生成json字符串
使用{{#toJson}}parameter{{/toJson}}
指令可以把参数转成json形式,用于复杂查询:
GET _render/template
{
"source": "{ \"query\": { \"terms\": {{#toJson}}statuses{{/toJson}} }}",
"params": {
"statuses" : {
"status": [ "pending", "published" ]
}
}
}
实际结果应该为:
{
"query": {
"terms": {
"status": [
"pending","published"
]
}
}
}
下面是更复杂的示例:
GET _render/template
{
"source": "{\"query\":{\"bool\":{\"must\": {{#toJson}}clauses{{/toJson}} }}}",
"params": {
"clauses": [
{ "term": { "user" : "foo" } },
{ "term": { "user" : "bar" } }
]
}
}
渲染结果为:
{
"query" : {
"bool" : {
"must" : [
{
"term" : {
"user" : "foo"
}
},
{
"term" : {
"user" : "bar"
}
}
]
}
}
}
2.4. 连接数组值
使用{{#join}}array{{/join}}
指令可以连接数组的元素并使用逗号进行分隔:
GET _render/template
{
"source": {
"query": {
"match": {
"emails": "{{#join}}emails{{/join}}"
}
}
},
"params": {
"emails": [ "username@email.com", "lastname@email.com" ]
}
}
渲染结果为:
{
"query" : {
"match" : {
"emails" : "username@email.com,lastname@email.com"
}
}
}
也可以指定分隔符:
GET _render/template
{
"source": {
"query": {
"range": {
"born": {
"gte" : "{{date.min}}",
"lte" : "{{date.max}}",
"format": "{{#join delimiter='||'}}date.formats{{/join delimiter='||'}}"
}
}
}
},
"params": {
"date": {
"min": "2016",
"max": "31/12/2017",
"formats": ["dd/MM/yyyy", "yyyy"]
}
}
}
渲染结果为:
{
"query" : {
"range" : {
"born" : {
"gte" : "2016",
"lte" : "31/12/2017",
"format" : "dd/MM/yyyy||yyyy"
}
}
}
}
2.5. 指定缺省值
可以使用{{var}}{{^var}}default{{/var}}
指令指定缺失值:
{
"source": {
"query": {
"range": {
"line_no": {
"gte": "{{start}}",
"lte": "{{end}}{{^end}}20{{/end}}"
}
}
}
},
"params": { ... }
}
当params参数值为{ "start": 10, "end": 15 }
时解析结果为:
{
"range": {
"line_no": {
"gte": "10",
"lte": "15"
}
}
}
当params参数值为{ "start": 10 }
时解析结果为:
{
"range": {
"line_no": {
"gte": "10",
"lte": "20"
}
}
}
2.6. 条件表达式
条件子句不能使用json形式表达,模板必须作为字符串进行传输。假设我们需要在line字段上执行match查询,其中start和end作为行号进行过滤是可选的。参数如下:
{
"params": {
"text": "words to search for",
"line_no": {
"start": 10,
"end": 20
}
}
}
对应查询模板:
{
"query": {
"bool": {
"must": {
"match": {
"line": "{{text}}"
}
},
"filter": {
{{#line_no}}
"range": {
"line_no": {
{{#start}}
"gte": "{{start}}"
{{#end}},{{/end}}
{{/start}}
{{#end}}
"lte": "{{end}}"
{{/end}}
}
}
{{/line_no}}
}
}
}
}
{{#line_no}}
表示如果line_no存在,则解析{{/line_no}}
之间的内容。块内的模板语法也类似,都是实现了条件判断功能,伪代码表达如下:
if 条件 then
表达式
end if
实际应用可能会用到if else的功能
,在模板中的语法为:{{^line_no}}
。
2.7. URL编码
{{#url}}value{{/url}}
指令能以HTML编码形式对字符串值进行编码,下面示例对url编码:
GET _render/template
{
"source" : {
"query" : {
"term": {
"http_access_log": "{{#url}}{{host}}/{{page}}{{/url}}"
}
}
},
"params": {
"host": "https://www.elastic.co/",
"page": "learn"
}
}
响应结果如下:
{
"template_output" : {
"query" : {
"term" : {
"http_access_log" : "https%3A%2F%2Fwww.elastic.co%2F%2Flearn"
}
}
}
}
3. Java Api 调用模板
SearchTemplateRequest request = new SearchTemplateRequest();
request.setRequest(new SearchRequest("posts"));
request.setScriptType(ScriptType.STORED);
request.setScript("title_search");
Map<String, Object> params = new HashMap<>();
params.put("field", "title");
params.put("value", "elasticsearch");
params.put("size", 5);
request.setScriptParams(params);
首先定义SearchTemplateRequest,然后设置查询索引,关联模板脚本,最后给查询指定参数。模板脚本也可以通过low-level REST client进行添加,当然也可以在kibana中直接添加。
也可以指定内联脚本,这是脚本自行管理,最终以INLINE方式指定即可。
SearchTemplateRequest request = new SearchTemplateRequest();
request.setRequest(new SearchRequest("posts"));
request.setScriptType(ScriptType.INLINE);
request.setScript(
"{" +
" \"query\": { \"match\" : { \"{{field}}\" : \"{{value}}\" } }," +
" \"size\" : \"{{size}}\"" +
"}");
Map<String, Object> scriptParams = new HashMap<>();
scriptParams.put("field", "title");
scriptParams.put("value", "elasticsearch");
scriptParams.put("size", 5);
request.setScriptParams(scriptParams);
4. 总结
本文介绍Elasticsearch搜索模板的定义、解析,以及如何利用搜索模板进行简化查询,也提供Java api示例进行说明。