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"
}
}
}
}
}
}
注意:
- 1. hobbies其實是一個數組類型
- 2. 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"}}
注意:
- 1. 需要注意一下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 運作結果
![運作結果![](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