文章目录
- 0. 前言
- 1. 基本使用
- 2. 读请求中访问文档字段
- 2.1 遍历 List
- 2.2 判断对象存不存在
- 2.3 判断值是否为空
- 2.4 总结
- 3. 写请求中访问文档字段
- 3.1 数字相加
- 3.2 字符串相加
- 3.3 将字符串转为数组
0. 前言
在前面部分,我们介绍了 ES 的基本使用和要掌握的基础性读写原理。
从本章开始,会开始介绍 ES 进阶使用。
本章要介绍的内容是 Script
的基本使用。
在日常开发中,读请求很少会用,写请求会使用比较多。
在读请求、写请求中,Script
访问文档值的方式有所不同,下面我将对 Script
在读写请求中使用进行介绍。
1. 基本使用
我们来看一个修改算分的例子。
写入文档
PUT test_18/_doc/1
{
"counter": 1,
"tags": ["red"],
"scores": [1,2,3,4,5]
}
查询时,修改算分
GET test_18/_search
{
"query": {
"script_score": {
"query": {
"term": {
"tags": "red"
}
},
"script": {
"lang": "painless",
"source": "Math.log(_score * 2) + params['my_modifier'] * doc['counter'].value",
"params": {
"my_modifier": 2
}
}
}
}
}
script_score
是 ES 用于修改算分的查询,当然这不是我们的重点。
script
中包含 3 个属性
- lang: 默认为
painless
,非必填 - source: 脚本,必填
- params: 传递给脚本的参数,没有定义参数时,不用填
可以看到,在 Script
中我们可以使用 JAVA Api
, 可以访问自定义的参数, 可以访问 ES 文档字段值。
2. 读请求中访问文档字段
painless script language
其实跟 JAVA
语法大相径庭。你可以简单认为就是在写 JAVA
代码。
在读请求中,ES 将文档映射为名为 doc
的 map。可以通过 doc['字段名']
访问对应字段的对象。
2.1 遍历 List
GET test_18/_search
{
"query": {
"script_score": {
"query": {
"match_all": {}
},
"script": {
"lang": "painless",
"source": """
int total = 0;
for (int i = 0; i < doc['scores'].length; ++i) {
total += doc['scores'][i];
}
return total;
"""
}
}
}
}
代码解释
doc['scores']
获取到文档对象,即scores 字段
.length
是JAVA
数组的方法doc['socere'][i]
访问数组中具体的值
script 中不允许访问 text 字段
GET test_18/_search
{
"query": {
"function_score": {
"script_score": {
"script": {
"lang": "painless",
"source": """
int total = 0;
for (int i = 0; i < doc['tags.keyword'].length; ++i) {
total += doc['counter'].value;
}
return total;
"""
}
}
}
}
}
代码解释
doc['tags.keyword']
,拿到tags.keyword
对象。需要特别注意,这里不能使用doc['tags']
。因为 ES 在 script 中不允许访问 text 字段doc['counter']
,获取 counter 对象。.value
获取 counter 对象的值
2.2 判断对象存不存在
doc.containsKey['field']
GET test_18/_search
{
"query": {
"script_score": {
"query": {
"match_all": {}
},
"script": {
"lang": "painless",
"source": """
int total = 0;
if (doc.containsKey('goals')) {
return doc['goals'].value;
} else {
return (int)_score;
}
"""
}
}
}
}
代码解释
doc.containsKey('goals')
: 判断是否包含goals
key。之前我们说过的。文档会被映射为一个 map。因此 doc 具备 map 的函数。return (int)_score
:_score
在script_score
中是一个特殊字段。(int) 是类型强转,和JAVA
语法一致
2.3 判断值是否为空
doc['field'].size()
GET test_18/_search
{
"query": {
"script_score": {
"query": {
"match_all": {}
},
"script": {
"lang": "painless",
"source": """
int total = 0;
if (doc['counter'].size() == 0) {
return 3;
} else {
return 2;
}
"""
}
}
}
}
代码解释
doc['counter'].size()
获取counter
对象长度
2.4 总结
- 读请求中,可以使用
doc['字段名']
访问对应字段的对象 - script 读请求中,无法访问
text
字段 painless
语法与JAVA
语法类似- 关于
painless
更多可阅读 ES painless 官网 painless
中可以使用哪些 API,可以参考 painless 支持的 API 文档
3. 写请求中访问文档字段
在写请求中使用脚本,我们大部分情况下是结合 Ingest Pipeline
一起使用。
在 Ingest pipeline
中,通过 ctx.xxx
就可以访问到文档字段。
注:Ingest pipeline:可以在文档写入前对文档进行预处理。
下面,我们来看几个例子
3.1 数字相加
将 math_score + verbal_score 赋值给 total_score
创建索引
PUT test_18_write_01
{
"mappings": {
"properties": {
"math_score": {
"type": "integer"
},
"verbal_score": {
"type": "integer"
},
"total_score": {
"type": "integer"
}
}
}
}
创建 ingest pipeline
PUT _ingest/pipeline/test_18_write_01_pipeline
{
"description": "Calculates the total test score",
"processors": [
{
"script": {
"source": "ctx.total_score = (ctx.math_score + ctx.verbal_score)"
}
}
]
}
写入时,指定 pipeline
PUT test_18_write_01/_doc/1?pipeline=test_18_write_01_pipeline
{
"math_score": 99,
"verbal_score": 89
}
查看结果
GET test_18_write_01/_search
已经为我们自动写入了 total_score
3.2 字符串相加
将 lastName、firstName 相加,赋值给 fullName
创建 ingest pipeline
PUT _ingest/pipeline/test_18_write_02_pipeline
{
"description": "String concatenation test",
"processors": [
{
"script": {
"source": """
if (ctx.containsKey('lastName') && ctx.containsKey('firstName')) {
ctx.fullName = ctx.lastName + ' ' + ctx.firstName;
}
"""
}
}
]
}
写入文档,并指定 pipeline
PUT test_18_write_02/_doc/1?pipeline=test_18_write_02_pipeline
{
"firstName": "hello",
"lastName": "elasticsearch"
}
3.3 将字符串转为数组
将 name 转换为数组,并赋值给 names
创建 pipeline
PUT _ingest/pipeline/test_18_write_03_pipeline
{
"description": "string to array",
"processors": [
{
"script": {
"source": """
ctx.names = ctx.name.splitOnToken(',')
"""
}
}
]
}
写入数据,并指定 pipeline
PUT test_18_write_03/_doc/1?pipeline=test_18_write_03_pipeline
{
"name": "hello,elasticsearch"
}