Scripting是ES提供的一种支持自定义编程的用于复杂查询的脚本语言.主要用于复杂的计算,其类型主要有Painless、expressions等等,下面开始分析,运行数据在ES 聚合查询中,自行查找.
1、Reindex 数据备份
reindex 常用于数据备份,类似关系型数据库中的select * from tab1 into tab2,代码如下:
POST _reindex { "source": { "index": "food" }, "dest": { "index": "food_20220815" } }
2、Painless
Painless脚本语言语法相对简单,灵活度高(一般情况下,基本上能解决任务问题,但是DSL也有其优势),安全性高,性能高(相对于其他脚本,但是其性能比DSL要低).不适用于非复杂业务,一般DSL能解决大部分的问题.解决不了的用类似Painless等脚本语言.
2.1 调整一条食物的价格,下调1元.
这里有两种方式,第一种是查到指定食物的当前价格,然后进行减1操作,通过update接口实现,代码如下:
POST food/_update/1 { "doc":{ "Price":"11" //计算过后的值 } }
第二种就是通过script来解决了.类似于关系型数据库中的update table set field=field-1,代码如下:
POST food/_update/1 { "script": { "source": "ctx._source.Price-=1" } }
这里ctx代表查询上下文,_source就是查询结果及hits中的_source.
这里第二种方式还有简写模式,代码如下:
POST food/_update/1 { "script": "ctx._source.Price-=1" }
2.2 像Tags数组新增一个标签数据
和1.1中一样除了标准的dsl如下:
POST food/_update/1 { "doc": { "Tags":["性价比","营养","绿色蔬菜","新增的标签"] } }
结果如下:
{ "_index" : "food", "_id" : "1", "_score" : 1.0, "_source" : { "CreateTime" : "2022-06-06 11:11:11", "Desc" : "青菜 yyds 营养价值很高,很好吃", "Level" : "普通蔬菜", "Name" : "青菜", "Price" : 9.11, "Tags" : [ "性价比", "营养", "绿色蔬菜", "新增的标签" ], "Type" : "蔬菜" } }
也可以通过以下Painless脚本来实现,代码如下:
POST food/_update/1 { "script": { "lang": "painless", "source": "ctx._source.Tags.add('新增的标签')" } }
结果和DSL的结果一致.
2.3 删除一条数据
DSL代码如下:
DELETE food/_doc/66
painless脚本如下:
POST food/_update/66 { "script": { "lang": "painless", "source": "ctx.op='delete'" } }
2.4 upsert 如果操作的数据存在则按照指定的脚本进行修改,如果不存在则新增一条数据
POST food/_update/66 { "script": { "lang": "painless", "source": "ctx._source.Price+=100" }, "upsert": { "CreateTime": "2022-07-09 13:11:11", "Desc": "榴莲 非常好吃 很贵 吃一个相当于吃一只老母鸡", "Level": "高级水果", "Name": "榴莲", "Price": 100.11, "Tags": [ "贵", "水果", "营养" ], "Type": "水果" } }
自行构造数据,这里第一次执行,应为id为66的数据不存在所以走的新增操作,当第二次执行过后,执行的是painless脚本,对价格进行了100的追加.
4、Painless参数化脚本
参数化脚本类似于.Net程序中类似Dapper这类的Orm,在指定执行sql的同时在sql中定义查询参数,防止sql注入,在painless脚本中,参数化可以有效的解决脚本编译的问题,如2.2例子中,如果标签的内容发生变化,那es每次会编译执行脚本造成一定的性能影响。而如果使用参数化技术,则只会编译一次,避免性能浪费.
4.1 单个参数计算查询
还是商品折扣的例子,params.param1就是折扣参数
GET food/_search { "script_fields": { "c_price": { "script": { "lang": "painless", "source": "doc['Price'].value * params.param1", "params": { "param1": 0.1 } } } } }
4.2 多个参数计算查询
GET food/_search { "script_fields": { "c_price": { "script": { "lang": "painless", "source": "[doc['Price'].value * params.param1,doc['Price'].value * params.param2]", "params": { "param1": 0.1, "param2": 0.2 } } } } }
5、Painless 脚本模板
脚本模板类似于关系型数据库的存储过程,如果某些脚本需要查询功能需要在多个业务场景中使用,就可以使用脚本模板功能来满足需求.
POST _scripts/test_script_template { "script":{ "lang": "painless", "source": "doc.Price.value * params.param1" } }
如上代码创建了一个功能为按照指定折扣返回价格的参数模板.下面是调用代码:
GET food/_search { "script_fields": { "c_price": { "script": { "id":"test_script_template", "params": { "param1":0.1 } } } } }
6、expression 脚本
expression脚本有多种用处,这里分析其在计算字段的用途,计算字段不能使用ctx,而是要用doc
注意:
3.1 现在商场需要统计所有商品打八折之后的价格
GET food/_search { "script_fields": { "custom_price": { "script": { "lang": "expression", "source": "doc['Price'] * 0.8" } } } }
这里可以也可以用painless脚本来实现,代码如下:
GET food/_search { "script_fields": { "custom_price": { "script": { "lang": "painless", "source": "doc['Price'].value * 0.8" } } } }
执行结果包含如下错误如下:
"failures" : [ { "shard" : 2, "index" : "food", "node" : "gfPxwY2JT9KPcf9J2uhszw", "reason" : { "type" : "script_exception", "reason" : "runtime error", "script_stack" : [ "org.elasticsearch.index.fielddata.ScriptDocValues$Doubles.get(ScriptDocValues.java:209)", "org.elasticsearch.index.fielddata.ScriptDocValues$Doubles.getValue(ScriptDocValues.java:203)", "doc['Price'].value * 0.8", " ^---- HERE" ], "script" : "doc['Price'].value * 0.8", "lang" : "painless", "position" : { "offset" : 12, "start" : 0, "end" : 24 }, "caused_by" : { "type" : "illegal_state_exception", "reason" : "A document doesn't have a value for a field! Use doc[<field>].size()==0 to check if a document is missing a field!" } } } ]
这是因为数据中包含Price为空的信息所以,才会报错.那么需要找到为空的相关信息,并更新掉.这里可以通过如下代码快速检索到Price为空的相关记录
GET food/_search { "script_fields": { "custom_price": { "script": { "lang": "painless", "source": "doc['Price'].size()<=0" } } } }
结果中如下记录:
{ "_index" : "food", "_id" : "8", "_score" : 1.0, "fields" : { "custom_price" : [ true ] } }
说明id为8的记录价格为空.更新完毕之后,再次执行painless计算字段脚本,结果和expression脚本一样.