ES Scripts腳本相關 ES 聚合查詢 ES 聚合查詢

Scripting是ES提供的一種支持自定義編程的用於複雜查詢的腳本語言.主要用於複雜的計算,其類型主要有Painlessexpressions等等,下面開始分析,運行數據在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腳本一樣.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章