1、背景
在我们使用es时,有些时候需要动态返回一些字段,而这些字段是通过动态计算得出的,那么此时该如何操作呢? 比如:我们索引中有一个sex字段,保存的是1或0,而在页面上需要展示男或女,那么这个时候就可以使用script_fields来解决。可能有些人说,我通过后台进行格式化一下不就行了吗,但是假设我们需要在kibana等可视化工具上展示呢?
2、准备数据
2.1 mapping
PUT /index_script_fields { "mappings": { "properties": { "name":{ "type": "keyword" }, "sex":{ "type": "integer" }, "hobbies":{ "type":"keyword" }, "address":{ "properties": { "province":{ "type":"keyword" }, "city":{ "type":"keyword" } } } } } }注意:
2.2 插入数据
PUT /index_script_fields/_bulk {"index":{"_id":1}} {"name":"张三","sex":1,"hobbies":["足球","篮球"],"address":{"province":"湖北","city":"city01"}} {"index":{"_id":2}} {"name":"张三","sex":2,"address":{"province":"北京","city":"city01"}} {"index":{"_id":3}} {"name":"张三","hobbies":["足球"],"address":{"province":"湖北","city":"city01"}}注意:
3、案例
3.1 格式化性别 1-男 2-女 -1-未知 如果不存在sex字段,则显示-- 其余的显示 **
3.1.1 dsl
GET /index_script_fields/_search { "query": { "match_all": {} }, "_source": ["*"], "script_fields": { "sex_format": { "script": { "lang": "painless", "source": """ // 判断 sex 字段是否存在 if(doc['sex'].size() == 0){ return "--"; } if(doc['sex'].value == 1){ return "男"; }else if(doc['sex'].value == 2){ return "女"; }else if(doc['sex'].value == -1){ return "未知"; }else{ return "**"; } """ } } } }需要注意 sex 字段不存在,该如何判断,见上方的代码
3.1.2 java代码
@Test @DisplayName("格式化性别 1-男 2-女 -1-未知 如果不存在sex字段,则显示-- 其余的显示 **") public void test01() throws IOException { SearchRequest request = SearchRequest.of(searchRequest -> searchRequest.index(INDEX_NAME) .query(query -> query.matchAll(matchAll -> matchAll)) // 不加这句,则 _source 不会返回,值返回 fields .source(config -> config.filter(filter -> filter.includes("*"))) .scriptFields("sex_format", field -> field.script(script -> script.inline(inline -> inline.lang(ScriptLanguage.Painless) .source(" // 判断 sex 字段是否存在\n" + " if(doc['sex'].size() == 0){\n" + " return \"--\";\n" + " }\n" + " \n" + " if(doc['sex'].value == 1){\n" + " return \"男\";\n" + " }else if(doc['sex'].value == 2){\n" + " return \"女\";\n" + " }else if(doc['sex'].value == -1){\n" + " return \"未知\";\n" + " }else{\n" + " return \"**\";\n" + " }") ) ) ) .size(100) ); System.out.println("request: " + request); SearchResponse<Object> response = client.search(request, Object.class); System.out.println("response: " + response); }3.1.3 运行结果
3.2 判断用户是否有某个爱好
3.2.1 dsl
GET /index_script_fields/_search { "_source": ["*"], "query": {"match_all": {}}, "script_fields": { "has_hobby": { "script": { "lang": "painless", "source": """ // 没有hobbies字段,直接返回 false if(doc['hobbies'].size() == 0){ return false; } return doc['hobbies'].indexOf(params.hobby) > -1; """, "params": { "hobby":"篮球" } } } } }3.2.2 java代码
@Test @DisplayName("判断用户是否有某个爱好") public void test02() throws IOException { SearchRequest request = SearchRequest.of(searchRequest -> searchRequest.index(INDEX_NAME) .query(query -> query.matchAll(matchAll -> matchAll)) // 不加这句,则 _source 不会返回,值返回 fields .source(config -> config.filter(filter -> filter.includes("*"))) .scriptFields("has_hobby", field -> field.script(script -> script.inline(inline -> inline.lang(ScriptLanguage.Painless) .source(" // 没有hobbies字段,直接返回 false\n" + " if(doc['hobbies'].size() == 0){\n" + " return false;\n" + " }\n" + " return doc['hobbies'].indexOf(params.hobby) > -1;") .params("hobby", JsonData.of("篮球")) ) ) ) .size(100) ); System.out.println("request: " + request); SearchResponse<Object> response = client.search(request, Object.class); System.out.println("response: " + response); }3.2.3 运行结果
3.3 统计湖北的用户有几个
3.3.1 dsl
GET /index_script_fields/_search { "query": {"match_all": {}}, "aggs": { "agg_province": { "sum": { "script": { "lang": "painless", "source": """ // 因为 address 是一个复杂类型,因此不可直接通过 doc 来访问 if(params['_source']['address']['province'] == '湖北'){ return 1; } return 0; """ } } } } }因为 address 是一个复杂类型,因此不可直接通过 doc 来访问,只能通过 params[_source]来访问
3.3.2 java代码
@Test @DisplayName("统计湖北省下的用户有几个") public void test03() throws IOException { SearchRequest request = SearchRequest.of(searchRequest -> searchRequest.index(INDEX_NAME) .query(query -> query.matchAll(matchAll -> matchAll)) // 不加这句,则 _source 不会返回,值返回 fields .source(config -> config.filter(filter -> filter.includes("*"))) .aggregations("agg_province", agg-> agg.sum(sum -> sum.script(script -> script.inline(inline -> inline.lang(ScriptLanguage.Painless) // 因为 address 是一个复杂类型,因此不可直接通过 doc 来访问, 只可通过 params['_source']来访问 .source("// 因为 address 是一个复杂类型,因此不可直接通过 doc 来访问\n" + " if(params['_source']['address']['province'] == '湖北'){\n" + " return 1;\n" + " }\n" + " return 0;") ) ) ) ) .size(100) ); System.out.println("request: " + request); SearchResponse<Object> response = client.search(request, Object.class); System.out.println("response: " + response); }3.3.3 运行结果
![运行结果![](https://img-blog.csdnimg.cn/5910495ac0814db393125dae96934e38.png)
4、doc[..]和params[_source][..]有何不同
通过上面的案例,我们发现,我们有些时候是通过doc[..]来访问属性的,有些时候是通过params['_source'][..]来访问,那么这2种访问方式有何不同呢?
doc[..]:使用doc关键字,将导致该字段的术语被加载到内存(缓存),这将导致更快的执行,但更多的内存消耗。此外,doc[…]表示法只允许简单的值字段(您不能从中返回json对象),并且仅对非分析或基于单个术语的字段有意义。然而,如果可能的话,使用doc仍然是访问文档值的推荐方法。 params[_source][..]: 每次使用_source都必须加载和解析, 因此使用_source会相对而言要慢点。
虽然访问_source比访问doc values要慢,但是script_fields只对需要返回文档执行脚本,因此也不会太影响性能,除非返回的数据特别多。
5、完整代码
https://gitee.com/huan1993/spring-cloud-parent/blob/master/es/es8-api/src/main/java/com/huan/es8/script/ScriptFieldApi.java
6、参考文档
1、https://www.elastic.co/guide/en/elasticsearch/reference/8.6/search-fields.html#script-fields