Elasticsearch 有沒有陣列型別?有哪些坑?

語言: CN / TW / HK

1、Elasticsearch 陣列常見問題清單

近期 Elasticsearch 陣列問題被問到的比較多,為了方便大家對陣列建立全域性認知, 我把陣列相關實戰問題梳理出來,讓更多後來人遇到類似問題少走不必要的彎路。

精簡幾個核心問題列舉如下:

  • Elasticsearch 是否有陣列型別?

  • Elasticsearch 資料選型有沒有坑?

  • Elasticsearch 如何獲取陣列中第i號位置的值?

  • Elasticsearch 如何獲取陣列最後一個元素的值?

2、Elasticsearch 是否有陣列型別?

瞭解 Elasticsearch 版本歷史的讀者會知道 1.X 版本中沒有獨立模組強調陣列 Array 型別。

Elasticsearch 2.X 版本之後,陣列型別才單獨被拎出來。

在 Elasticsearch 中,沒有專門的陣列資料型別。預設情況下,任何欄位都可以包含零個或多個值,但是,陣列中的所有值必須具有 相同的 資料型別。什麼意思呢?

  • long 型別儲存一個值是long型別,儲存多個自然就成為 long 陣列型別;

  • keyword 型別儲存一個值是 keyword 型別,儲存多個值就成為 keyword 陣列型別。

實戰一把:

PUT my-index-1230
{
  "mappings": {
    "properties": {
      "horry": {
        "type": "keyword"
      }
    }
  }
}

# 單值 keyword 型別
POST my-index-1230/_doc
{
  "horry": "pingpang"
}

# keyword 陣列型別
POST my-index-1230/_doc
{
  "horry": [
    "pingpang",
    "basketball",
    "football"
  ]
}

GET my-index-1230/_search

如上示例,可以更加清晰的看出,當我們選型使用陣列時,和平時 Mapping 設定型別一樣,不需要額外的修改 Mapping 任何內容,只需要匯入相同資料型別的資料即可。

這時候,讀者可能會問,我不小心寫入了不同型別的資料咋辦?

繼續實戰一把,探個究竟。

# 匯入不合規資料,資料依然可以寫入
POST my-index-1230/_doc
{"horry":["11111",22222,33333]}

# 資料依然可以被召回
GET my-index-1230/_search
{
  "query": {
    "term": {
      "horry": {
        "value": "22222"
      }
    }
  }
}

由於 Elasticsearch 做了弱型別匹配校驗檢查,導致資料依然可以寫入,我們肉眼看到的整形型別,實際本質儲存為keyword 型別,這也是為什麼“22222”能被檢索召回的原因。

3、Elasticsearch 資料選型有沒有坑?

3.1 動態匯入陣列型別資料,第一次寫入資料的型別決定了陣列的型別。

# 預設檢測指定為 long 型別
POST my-index-1230-01/_doc
{
  "horry": [
    11111,
    22222,
    33333
  ]
}

如上,寫入的是 long 型別的陣列。long 咋來的,動態型別匹配 date_detection 得到的!

https://www.elastic.co/guide/en/elasticsearch/reference/current/dynamic-mapping.html

3.2 不能獨立的檢索陣列中的單個欄位

如下示例,即便僅檢索“basketball”不能獨立只召回“basketball”,陣列資料會一起返回。

# 陣列資料會作為一個整體被召回
GET my-index-1230/_search
{
  "query": {
    "match": {
      "horry":  "basketball"
    }
  }
}

如果非要實現單獨召回資料,需要藉助:nested 巢狀型別實現。

4、Elasticsearch 如何獲取陣列中第i號位置的值?

舉例,僅檢索召回 “basketball”,需要藉助 script_field 或者 runtime field 實現。

實現參考如下:

POST my-index-1230/_doc
{
  "horry": [
    "all",
    "pingpang",
    "basketball",
    "football"
  ]
}

GET my-index-1230/_search

注意:儲存陣列資料順序是:

序號
0 all
1 pingpang
2 basketball
3 football
GET my-index-1203/_search
{
  "query": {
    "match_all": {}
  },
  "script_fields": {
    "test1": {
      "script": {
        "lang": "painless",
        "source": "doc['horry'][2]"
      }
    }
  }
}

如上示例中的2,可以換成:0,1,2,3。

召回結果 如下:

序號
0 all
1 basketball
2 football
3 pingpang

後臺按照字母順序做了處理,返回結果資料。

也就是說:我們以指令碼的方式 無法精準獲取對應位次上的資料

這塊目前看,沒有最優的獲取方式。如果大家有,歡迎留言交流。

4、Elasticsearch 如何獲取陣列最後一個元素的值?

通過 ingest pipeline 預處理方式實現如下:

DELETE my-index-123001
POST my-index-123001/_doc
{
  "horry": [
    "pingpang",
    "basketball",
    "football"
  ]
}

PUT _ingest/pipeline/my_pipeline
{
  "processors": [
    {
      "script": {
        "source": """
             if(ctx.horry!=null)
             {
               List list = ctx.horry; 
              List ListOfLast = list.subList(list.size() - 1, list.size()); 
              String lastval=ListOfLast.toString(); 
              lastval=lastval.substring(1, lastval.length() - 1);
              ctx.lastval=lastval;
             }
          """
      }
    }
  ]
}


POST my-index-123001/_update_by_query?pipeline=my_pipeline
{
  "query": {
    "match_all": {}
  }
}

GET my-index-123001/_search

中間核心指令碼不是最優實現方式,歡迎大家留言反饋精簡處理方式。

  • 第一步:陣列轉 list;

  • 第二步:取 list 最後一個元素值,結果仍然為 list;

  • 第三步:list 轉 string;

  • 第四步:string 取核心元素,去頭、去尾。

  • 第五步:將中間結果賦值給新欄位。

返回結果如下:

{
      "_index" : "my-index-123001",
      "_type" : "_doc",
      "_id" : "RxLlC34BuPkjCUZU5-XW",
      "_score" : 1.0,
      "_source" : {
        "lastval" : "football",
        "horry" : [
          "pingpang",
          "basketball",
          "football"
        ]
      }
    }

上面的預處理方式對陣列而言,也是僅適用於獲取特定值的場景。

5、小結

Elasticsearch 陣列選型需要結合業務場景需要。

資料相關的問題還有很多很多,比如:

  1. 陣列高亮問題

  2. 陣列聚合問題

  3. 陣列召回資料問題 

  4. .....

大家在選型或者實踐的過程中,如果涉及大量指令碼的時候,要多考慮能否通過寫入前 ingest 預處理方式。本質是:以空間換時間,最大化提升檢索效率。

歡迎大家就陣列問題留言討論。

短時間 快習得 多幹貨!

已帶領 81 球友 通過 Elastic 官方認證!

比同事 搶先 一步學習進階乾貨