在我們使用es的開發(fā)過程中可能會遇到這么一種情況,比如我們的線路名稱字段lineName字段在設置mapping的時候使用的是text類型,但是后期發(fā)現(xiàn)需要使用這個字段來進行聚合操作,那么我們除了對索引進行reindex操作外,還有什么辦法可以解決這個問題呢?
1、背景
在我們使用es的開發(fā)過程中可能會遇到這么一種情況,比如我們的線路名稱字段lineName字段在設置mapping的時候使用的是text類型,但是后期發(fā)現(xiàn)需要使用這個字段來進行聚合操作,那么我們除了對索引進行reindex操作外,還有什么辦法可以解決這個問題呢?此處我們通過runtime field來解決。
2、runtime field介紹
2.1 runtime field可以實現(xiàn)的功能
運行時字段是在查詢時評估的字段。是在es7.11之后增加的運行時字段使您能夠:
- 將字段添加到現(xiàn)有文檔,而無需重新索引數(shù)據(jù)
- 在不了解數(shù)據(jù)結構的情況下開始處理數(shù)據(jù)
- 在查詢時覆蓋從索引字段返回的值
- 定義特定用途的字段,而不修改原始mappingruntime field 的作用
2.2 runtime field優(yōu)缺點
- runtime field是運行時增加的字段,不會被索引和存儲,不會增加索引的大小。
- runtime field 可以像普通字段一樣使用,可以進行查詢,排序,聚合等操作。
- 可以動態(tài)的添加字段。
- 可以在查詢時覆蓋字段的值。即fields中和_source中可以返回同名的字段,但是值可能不一樣。
- 阻止mapping爆炸,可以先使用后定義。
- 針對經(jīng)常被搜索或聚合等操作的字段,不適合使用runtime field,而應該定義在mapping中。
- runtime field不會出現(xiàn)在_source中,需要通過fields api來獲取。
3、創(chuàng)建runtime field的方式
3.1 通過mapping的方式創(chuàng)建
3.1.1、添加runtime field
PUT /index_script_fields
{
"mappings": {
"runtime": {
"aggLineName": {
"type": "keyword",
"script": {
"source": "emit(doc['lineName'].value)"
}
}
},
"properties": {
"lineId": {
"type": "keyword"
},
"lineName": {
"type": "text"
}
}
}
}
3.1.2、更新 runtime field
POST /index_script_fields/_mapping
{
"runtime": {
"aggLineName": {
"type": "keyword",
"script": {
"source": "emit(doc['lineName'].value)"
}
}
}
}
3.1.3、刪除runtime field
POST /index_script_fields/_mapping
{
"runtime": {
"aggLineName": null
}
}
3.2 通過search request定義runtime field
GET /index_script_fields/_search
{
"runtime_mappings": {
"lineName": {
"type": "keyword",
"script": "emit(params['_source']['lineName']+'new')"
}
},
"query": {
"match_all": {}
},
"fields": [
"lineName"
]
}

通過search request定義runtime field
4、需求
我們存在一個線路mapping,其中lineName在設計的使用使用了text類型,現(xiàn)在我們需要根據(jù)這個字段來進行聚合操作,那么使用runtime field該如何操作呢?
5、實現(xiàn)
5.1 mapping
PUT /index_script_fields
{
"mappings": {
"properties": {
"lineId": {
"type": "keyword"
},
"lineName": {
"type": "text"
}
}
}
}
注意此時的lineName的類型是text
5.2 插入數(shù)據(jù)
PUT /index_script_fields/_bulk
{"index":{"_id":1}}
{"lineId":"line-01","lineName":"線路A"}
{"index":{"_id":2}}
{"lineId":"line-01","lineName":"線路A"}
{"index":{"_id":3}}
{"lineId":"line-02","lineName":"線路C"}
5.3、根據(jù)線路來進行聚合
從上方的mapping中可以lineName是text類型,是不可進行聚合操作的,那么此時我們想進行聚合操作,就可以使用runtime field來實現(xiàn)。
5.3.1 不使用runtime field

不使用runtime field
5.3.2 使用runtime field
5.3.2.1 dsl
GET /index_script_fields/_search
{
"runtime_mappings": {
"aggLineName": {
"type": "keyword",
"script": "emit(params['_source']['lineName']+'new')"
}
},
"query": {
"match_all": {}
},
"fields": [
"lineName"
],
"aggs": {
"agg_line_name": {
"terms": {
"field": "aggLineName",
"size": 10
}
}
}
}
5.3.2.2 java代碼
@Test
@DisplayName("lineName字段是text類型,無法進行聚合操作,定義一個runtime field來進行聚合操作")
public void test01() throws IOException {
SearchRequest request = SearchRequest.of(searchRequest ->
searchRequest.index(INDEX_NAME)
// 查詢所有數(shù)據(jù)
.query(query -> query.matchAll(matchAll -> matchAll))
// runtime field字段不會出現(xiàn)在 _source中,需要使用使用 fields api來獲取
.fields(fields -> fields.field("lineName"))
// 創(chuàng)建一個 runtime filed 字段類型是 keyword
.runtimeMappings("aggLineName", runtime ->
runtime
// 此處給字段類型為keyword
.type(RuntimeFieldType.Keyword)
.script(script ->
script.inline(inline ->
// runtime field中如果使用 painless腳本語言,需要使用emit
inline.lang(ScriptLanguage.Painless)
.source("emit(params['_source']['lineName']+'new')")
)
)
)
// 進行聚合操作
.aggregations("agg_line_name", agg ->
// 此處的 aggLineName即為上一步runtime field的字段
agg.terms(terms -> terms.field("aggLineName").size(10))
)
.size(100)
);
System.out.println("request: " + request);
SearchResponse<Object> response = client.search(request, Object.class);
System.out.println("response: " + response);
5.3.3.3 運行結果

聚合
6、完整代碼
https://gitee.com/huan1993/spring-cloud-parent/blob/master/es/es8-api/src/main/java/com/huan/es8/runtimefield/RuntimeFieldCorrectMappingError.java
7、參考鏈接
1、https://www.elastic.co/guide/en/elasticsearch/reference/8.6/runtime.html?