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、Script函數式編程 官方文檔
函數式編程主要解決的是業務邏輯比較複雜(需要編寫多行),腳本無法通過少量代碼來解決,這時需要用到函數式腳本。關於函數式腳本支持的語法參閱官方文檔,支持的語法還是慢多了,和java編程語言類似.可以說是非常靈活.
6.1 現在需要操作一條食品記錄,新增一個標籤信息,並調整其價格
POST food/_update/2 { "script": { "lang": "painless", "source": """ ctx._source.Tags.add(params.AddTag); ctx._source.Price+=params.AddPrice; """, "params": { "AddTag":"新增的標籤變量值", "AddPrice":66 } } }
執行成功,就完成了對id爲2的食品新增了一個標籤,其值爲新增的標籤變量值,對其價格上調了66.
6.2 正則匹配食品記錄中帶"菜"字的,價格上調1元
POST food/_update/2 { "script": { "lang": "painless", "source": """ if(ctx._source.Name ==~ /[\s\S]*菜[\s\S]*/) { ctx._source.Price+=params.AddPrice }else{ ctx.op="noop"; } """, "params": { "AddPrice":1 } } }
注:java正則[\s\S] 代表匹配所有字符.
6.3 統計價格在100到300之間所有食品的標籤總數.
GET food/_search { "query": { "constant_score": { "filter": { "range": { "Price": { "gte": 100, "lte": 300 } } } } }, "aggs": { "tags_agg": { "sum": { "script": { "lang": "painless", "source": """ int result=0; for(int i=0;i<doc['Tags.keyword'].length;i++) { result++; } return result; """ } } } } }
這裏的聚合查詢不在像ES 聚合查詢操作的是字段而是直接編寫腳本,通過doc可以拿到所有可操作的字段,再通過painless 腳本實現自定義的分桶查詢,非常的靈活.
這裏需要注意keyword類型,該加的必須要加.
7、expression 腳本
expression腳本有多種用處,這裏分析其在計算字段的用途,計算字段不能使用ctx,而是要用doc
注意:
7.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腳本一樣.