pipeline处理 painless script脚本详细总结 更新文档案例
一、前言
Elasticsearch的script脚本是一个非常灵活的一个功能,可能平时用到比较少,但是在一些特殊需求时,script脚本还是非常合适的。
script非常灵活,但也不能滥用,在数据量很大时,动态的script field也非常影响性能。
如果确实有需要,可以用空间换时间的方法,在数据存ES的时候,pipeline语法把值进行解析,存储附加信息,方便搜索。
注意:我当前用的ES7.8,如果是别的老版本,语法可能不同
二、创建测试数据
1. 创建index
PUT pigg_test_store
{
"mappings": {
"properties": {
"name": {
"type": "keyword"
},
"age": {
"type": "integer"
},
"address": {
"type": "text",
"fields": {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"birthday": {
"type": "date"
},
"socres": {
"type": "integer"
},
"chinese": {
"type": "integer"
},
"math":{
"type": "integer"
},
"english":{
"type": "integer"
}
}
}
}
2. 插入数据
PUT pigg_test_store/_doc/1
{
"name": ["王磊","王石"],
"age": 33,
"address": "江苏盐城盐都区",
"birthday": "1989-12-25",
"socres": [90, 98, 88],
"chinese":90,
"match": 98,
"english":88
}
PUT pigg_test_store/_doc/2
{
"name": "朱大珣",
"age": 31,
"address": "江苏徐州睢宁县",
"birthday": "1991-06-05",
"socres": [93, 91, 98],
"chinese":93,
"match": 91,
"english":98
}
三、利用script脚本修改文档
1. 将integer类型的age进行数学计算
修改文档时,通过ctx._source.fieldname来指定某个字段
POST pigg_test_store/_update/1
{
"script": "ctx._source.age += 1"
}
2. 指定integer类型的age为一个新值
POST pigg_test_store/_update/1
{
"script": "ctx._source.age = 34"
}
一种更好的方法如下:
POST pigg_test_store/_update/1
{
"script": {
"source": "ctx._source.age = params.value",
"params": {
"value": 34
}
}
}
第一种方法不带params,每次执行它的脚本,都需要重新编译。
编译好的script是可以缓存的,第二种方法用到params,当要修改值时(比如把34改成37),只需修改params里的value值,而不需要修改"ctx._source.age = params.value"这个脚本,所以第二种方法只需要编译一次。
3. 指定数组添加一个值
POST pigg_test_store/_update/1
{
"script": {
"source": "ctx._source.name.add(params.value)",
"params": {
"value": "王冬"
}
}
}
4. 指定数组删除某个值
POST pigg_test_store/_update/1
{
"script": {
"source": "ctx._source.name.remove(ctx._source.name.indexOf(params.value))",
"params": {
"value": "王冬"
}
}
}
但是如果name不存在指定的值,这么删除会报错,所以得先判断下是否存在
5. 先判断是否存在,然后指定数组删除某个值
POST pigg_test_store/_update/1
{
"script": {
"source": "if(ctx._source.name.indexOf(params.value) >= 0) ctx._source.name.remove(ctx._source.name.indexOf(params.value))",
"params": {
"value": "王冬"
}
}
}
除了用indexOf判断,也可以用contains判断,这么一看感觉painless的语法和Java的还挺像的啊。
POST pigg_test_store/_update/1
{
"script":{
"source": "if(ctx._source.name.contains(params.newname)) {ctx._source.name.remove(params.newname)}",
"lang": "painless",
"params": {
"newname": "王大老板"
}
}
}
6. 字段直接复制值
POST pigg_test_store/_update/1
{
"script": {
"source": "ctx._source.new_name = ctx._source.name"
}
}
但是这里得注意,这里也仅仅是复制值,不复制字段的mapping配置,如果不预先设置new_name的mapping配置,new_name还是会是ES的默认的text。
7. 删除一个字段,不修改mapping
POST pigg_test_store/_update/1
{
"script": "ctx._source.remove('new_name')"
}
四、利用script fields查询文档
script fields给我的感觉就像MySQL里,在SELECT后面加上函数处理,比如拼接2列。
script fields平时并不存在,不占存储空间,属于运行时属性。因为它是运行时的,所以当文档的数据特别大的时候,会比较降低性能。
1. 获取字段值的方式
和上面修改文档的ctx._source.fieldname不同,在查询时,需要用
doc[‘fieldname’].value
doc.fieldname.value
params._source.fieldname。
doc和_source不同:doc会把数据加载到内存中,提高效率,但只是基本的简单类型。
2. 求语数外三门的总分
GET pigg_test_store/_search
{
"script_fields": {
"sum_score": {
"script": {
"lang": "painless",
"source": "def sum = 0; sum = doc['chinese'].value + doc['match'].value + doc['english'].value; return sum; "
}
}
}
}
3. 如果text类型,用doc[‘fieldname’].value会报错
如果text类型,doc[‘fieldname’].value会报错。
当text类型包含keyword子字段时,用doc[‘fieldname.keyword’].value就可以。
GET pigg_test_store/_search
{
"script_fields": {
"new_address": {
"script": {
"source": "'地址是:' + doc['address.keyword'].value"
}
}
}
}
4. 如果text类型,用params._source.fieldname
GET pigg_test_store/_search
{
"script_fields": {
"new_address": {
"script": {
"source": "'地址是:' + params._source.address"
}
}
}
}
5. 用size()或length获取数组的长度
GET pigg_test_store/_search
{
"query": {
"script": {
"script": "doc['name'].size() == 3"
}
}
}
GET pigg_test_store/_search
{
"query": {
"script": {
"script": "doc['name'].length == 3"
}
}
}
6. 获取日期的属性
针对日期类型,还可以获取日期的属性
获取年份
GET pigg_test_store/_search
{
"script_fields": {
"year_of_birth": {
"script": {
"source": "doc.birthday.value.year"
}
}
}
}
获取月份
GET pigg_test_store/_search
{
"script_fields": {
"month_of_birth": {
"script": {
"source": "doc.birthday.value.monthOfYear"
}
}
}
}
获取日期
GET pigg_test_store/_search
{
"script_fields": {
"day_of_birth": {
"script": {
"source": "doc.birthday.value.dayOfMonth"
}
}
}
}
7. 在聚合中使用script
如下统计所有人的总名称个数
GET pigg_test_store/_search
{
"size": 10,
"aggs": {
"name_total_count": {
"sum": {
"script": "doc['name'].size()"
}
}
}
}
五、pipeline处理,提高查询效率
pipeline预处理,在数据入库时,计算好相应的值,这样空间换时间。
比如doc[‘name’].size()在数据巨大时,比较慢,那么可以在保存name时,也多存一个字段length,保存name数据的数据个数。
PUT _ingest/pipeline/calculate_length
{
"description": "Calculate the length of name array",
"processors": [
{
"script": {
"source": "ctx.length = ctx.name.length"
}
}
]
}
插入数据
注意下面的?pipeline=calculate_length
PUT pigg_test_store/_doc/1?pipeline=calculate_length
{
"name": ["王磊","王石"],
"age": 33,
"address": "江苏盐城盐都区",
"birthday": "1989-12-25",
"socres": [90, 98, 88],
"chinese":90,
"match": 98,
"english":88
}
GET pigg_test_store/_doc/1
发现返回结果多了一个length属性,它存储的是name数组的数据个数
"_source" : {
"birthday" : "1989-12-25",
"address" : "江苏盐城盐都区",
"match" : 98,
"length" : 2,
"socres" : [
90,
98,
88
],
"chinese" : 90,
"name" : [
"王磊",
"王石"
],
"english" : 88,
"age" : 33
}
这样只有查询在length上做term查询,就可以查询到数据了,比script脚本"doc[‘name’].size() == 2"效率高。
GET pigg_test_store/_search
{
"query": {
"term": {
"length": {
"value": "2"
}
}
}
}