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"
}
}
}
}
}
}
注意:
-
hobbies
其实是一个数组类型 -
address
是一个Object
类型,即是一个复杂类型
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"}}
注意:
- 需要注意一下
id=3
的数据是没有sex
属性的,那么在painless
脚本中如何保证不报错。
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 运行结果
4、doc[..]和params[_source][..]有何不同
通过上面的案例,我们发现,我们有些时候是通过doc[..]
来访问属性的,有些时候是通过params['_source'][..]
来访问,那么这2种访问方式有何不同呢?
doc[..]
:使用doc关键字,将导致该字段的术语被加载到内存(缓存),这将导致更快的执行,但更多的内存消耗。此外,doc[…]表示法只允许简单的值字段(您不能从中返回json对象),并且仅对非分析或基于单个术语的字段有意义。然而,如果可能的话,使用doc仍然是访问文档值的推荐方法。
params[_source][..]
: 每次使用_source都必须加载和解析, 因此使用_source会相对而言要慢点。
虽然访问_source
比访问doc values
要慢,但是script_fields
只对需要返回文档执行脚本,因此也不会太影响性能,除非返回的数据特别多。
5、完整代码
6、参考文档
1、https://www.elastic.co/guide/en/elasticsearch/reference/8.6/search-fields.html#script-fields