1. 程式人生 > >乾貨 | Elasticsearch Nested型別深入詳解

乾貨 | Elasticsearch Nested型別深入詳解

0、概要

在Elasticsearch實戰場景中,我們或多或少會遇到巢狀文件的組合形式,反映在ES中稱為父子文件。
父子文件的實現,至少包含以下兩種方式:
1)父子文件
父子文件在5.X版本中通過parent-child父子type實現,即:1個索引對應多個type;
6.X+版本已經不再支援一個索引多個type,6.X+的父子索引的實現改成Join。
2)Nested巢狀型別

本文通過一個例子將Nested型別適合解決的問題、應用場景、使用方法串起來,
文中所有的DSL都在Elasticsearch6.X+驗證通過。

1、Elasticsearch 資料型別全景概覽

在這裡插入圖片描述

2、從一個例子說起吧

在這裡插入圖片描述

2.1 問題背景

在elasticsearch中,我們可以將密切相關的實體儲存在單個文件中。 例如,我們可以通過傳遞一系列評論來儲存部落格文章及其所有評論。
舉例:

{
  "title": "Invest Money",
  "body": "Please start investing money as soon...",
  "tags": ["money", "invest"],
  "published_on": "18 Oct 2017",
  "comments": [
    {
      "name": "William",
      "age": 34,
      "rating": 8,
      "comment": "Nice article..",
      "commented_on": "30 Nov 2017"
    },
    {
      "name": "John",
      "age": 38,
      "rating": 9,
      "comment": "I started investing after reading this.",
      "commented_on": "25 Nov 2017"
    },
    {
      "name": "Smith",
      "age": 33,
      "rating": 7,
      "comment": "Very good post",
      "commented_on": "20 Nov 2017"
    }
  ]
}

如上所示,所以我們有一個文件描述了一個帖子和一個包含帖子上所有評論的內部物件評論。
但是Elasticsearch搜尋中的內部物件並不像我們期望的那樣工作。

2.2 問題出現

現在假設我們想查詢使用者{name:john,age:34}評論過的所有部落格帖子。 讓我們再看一下上面的示例文件,找到評論過的使用者。

name age
William 34
John 38
Smith 33

從列表中我們可以清楚地看到,沒有34歲的使用者John。
為簡單起見,我們在elasticsearch索引中只有1個文件。
讓我們通過查詢索引來驗證它:

GET /blog/_search?pretty
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "comments.name": "John"
          }
        },
        {
          "match": {
            "comments.age": 34
          }
        }
      ]
    }
  }
}

我們的示例文件作為回覆返回。 很驚訝,這是為什麼呢?

2.3 原因分析

這就是為什麼我說:
elasticsearch中的內部物件無法按預期工作
這裡的問題是elasticsearch(lucene)使用的庫沒有內部物件的概念,因此內部物件被扁平化為一個簡單的欄位名稱和值列表。
我們的文件內部儲存為:

{
  "title":                    [ invest, money ],
  "body":                     [ as, investing, money, please, soon, start ],
  "tags":                     [ invest, money ],
  "published_on":             [ 18 Oct 2017 ]
  "comments.name":            [ smith, john, william ],
  "comments.comment":         [ after, article, good, i, investing, nice, post, reading, started, this, very ],
  "comments.age":             [ 33, 34, 38 ],
  "comments.rating":          [ 7, 8, 9 ],
  "comments.commented_on":    [ 20 Nov 2017, 25 Nov 2017, 30 Nov 2017 ]
}

如上,您可以清楚地看到,comments.name和comments.age之間的關係已丟失。
這就是為什麼我們的文件匹配john和34的查詢。

2.4 如何解決呢?

要解決這個問題,我們只需要對elasticsearch的對映進行一些小改動。
如果您檢視索引的對映,您會發現comments欄位的型別是object。
我們需要更新它的型別為nested。
我們可以通過執行以下查詢來簡單地更新索引的對映:

PUT /blog_new
{
  "mappings": {
    "blog": {
      "properties": {
        "title": {
          "type": "text"
        },
        "body": {
          "type": "text"
        },
        "tags": {
          "type": "keyword"
        },
        "published_on": {
          "type": "keyword"
        },
        "comments": {
          "type": "nested",
          "properties": {
            "name": {
              "type": "text"
            },
            "comment": {
              "type": "text"
            },
            "age": {
              "type": "short"
            },
            "rating": {
              "type": "short"
            },
            "commented_on": {
              "type": "text"
            }
          }
        }
      }
    }
  }
}

將對映更改為Nested型別後,我們可以查詢索引的方式略有變化。 我們需要使用Nested查詢。
下面給出了Nested查詢示例:

GET /blog_new/_search?pretty
{
  "query": {
    "bool": {
      "must": [
        {
          "nested": {
            "path": "comments",
            "query": {
              "bool": {
                "must": [
                  {
                    "match": {
                      "comments.name": "john"
                    }
                  },
                  {
                    "match": {
                      "comments.age": 34
                    }
                  }
                ]
              }
            }
          }
        }
      ]
    }
  }
}

由於使用者{name:john,age:34}沒有匹配,上面的查詢將不返回任何文件。

再次感到驚訝? 只需一個小小的改變即可解決問題。
這可能是我們理解的一個較小的變化,但是在elasticsearch儲存我們的文件的方式上有很多變化。
在內部,巢狀物件將陣列中的每個物件索引為單獨的隱藏文件,這意味著可以獨立於其他物件查詢每個巢狀物件。
下面給出了更改對映後樣本文件的內部表示:

{
  {
    "comments.name":    [ john ],
    "comments.comment": [ after i investing started reading this ],
    "comments.age":     [ 38 ],
    "comments.rating":  [ 9 ],
    "comments.date":    [ 25 Nov 2017 ]
  },
  {
    "comments.name":    [ william ],
    "comments.comment": [ article, nice ],
    "comments.age":     [ 34 ],
    "comments.rating":   [ 8 ],
    "comments.date":    [ 30 Nov 2017 ]
  },
  {
    "comments.name":    [ smith ],
    "comments.comment": [ good, post, very],
    "comments.age":     [ 33 ],
    "comments.rating":   [ 7 ],
    "comments.date":    [ 20 Nov 2017 ]
  },
  {
    "title":            [ invest, money ],
    "body":             [ as, investing, money, please, soon, start ],
    "tags":             [ invest, money ],
    "published_on":     [ 18 Oct 2017 ]
  }
}

如您所見,每個內部物件都在內部儲存為單獨的隱藏文件。 這保持了他們的領域之間的關係。

3、Nested型別的作用?

從上一小節,可以清晰的看出nested型別的特別之處。
nested型別是物件資料型別的專用版本,它允許物件陣列以可以彼此獨立查詢的方式進行索引。

4、Nested型別的適用場景

圖片來自:rockybean教程
——圖片來自:rockybean教程

5、Nested型別的增、刪、改、查、聚合操作詳解

還是以第2節的blog_new索引示例,Nested型別的增、刪、改、查操作。

5.1 Nested型別——增

新增blog和評論

POST blog_new/blog/2
{
  "title": "Hero",
  "body": "Hero test body...",
  "tags": ["Heros", "happy"],
  "published_on": "6 Oct 2018",
  "comments": [
    {
      "name": "steve",
      "age": 24,
      "rating": 18,
      "comment": "Nice article..",
      "commented_on": "3 Nov 2018"
    }
  ]
}

5.2 Nested型別——刪

序號為1的評論原來有三條,現在刪除John的評論資料,刪除後評論數為2條。

POST  blog_new/blog/1/_update
{
 "script": {
    "lang": "painless",
    "source": "ctx._source.comments.removeIf(it -> it.name == 'John');"
 }
}

5.3 Nested型別——改

將steve評論內容中的age值調整為25,同時調整了評論內容。

POST blog_new/blog/2/_update
{
  "script": {
    "source": "for(e in ctx._source.comments){if (e.name == 'steve') {e.age = 25; e.comment= 'very very good article...';}}" 
  }
}

5.4 Nested型別——查

如前所述,查詢評論欄位中評論姓名=William並且評論age=34的blog資訊。

GET /blog_new/_search?pretty
{
  "query": {
    "bool": {
      "must": [
        {
          "nested": {
            "path": "comments",
            "query": {
              "bool": {
                "must": [
                  {
                    "match": {
                      "comments.name": "William"
                    }
                  },
                  {
                    "match": {
                      "comments.age": 34
                    }
                  }
                ]
              }
            }
          }
        }
      ]
    }
  }
}

5.5 Nested型別——聚合

認知前提:nested聚合隸屬於聚合分類中的Bucket聚合分類。
聚合blog_new 中評論者年齡最小的值。

GET blog_new/_search
{
  "size": 0,
  "aggs": {
    "comm_aggs": {
      "nested": {
        "path": "comments"
      },
      "aggs": {
        "min_age": {
          "min": {
            "field": "comments.age"
          }
        }
      }
    }
  }
}

6、小結

如果您在索引中使用內部物件並做查詢操作,請驗證內部物件的型別是否為nested型別。 否則查詢可能會返回無效的結果文件。
更新認知是非常痛苦的,不確定的問題只有親手實踐才能檢驗真知。