Elasticsearch關聯關係-嵌套

嵌套-對象

嵌套對象

事實上在Elasticsearch中,建立丶刪除丶修改一個文檔是是原子性的,所以咱們能夠在一個文檔中儲存密切關聯的實體。舉例來講,咱們能夠在一個文檔中儲存一筆訂單及其全部內容,或是儲存一個Blog文章及其全部迴應,藉由傳遞一個comments陣列: 緩存

PUT /my_index/blogpost/1 { "title": "Nest eggs", "body": "Making your money work...", "tags":  [ "cash", "shares" ], "comments": [ <1> { "name": "John Smith", "comment": "Great article", "age": 28, "stars": 4, "date": "2014-09-01" }, { "name": "Alice White", "comment": "More like this please", "age": 31, "stars": 5, "date": "2014-10-22" } ] } 

<1> 若是咱們依靠動態映射,comments欄位會被自動創建爲一個object欄位。 app

由於全部內容都在同一個文檔中,使搜尋時並不須要鏈接(join)blog文章與迴應,所以搜尋表現更加優異。 post

問題在於以上的文檔可能會以下所示的匹配一個搜尋: ui

GET /_search
{ "query": { "bool": { "must": [
        { "match": { "name": "Alice" }},
        { "match": { "age": 28 }} <1>
      ]
    }
  }
}

<1> Alice是31歲,而不是28歲! this

形成跨對象配對的緣由如同咱們在對象陣列中所討論到,在於咱們優美結構的JSON文檔在索引中被扁平化爲下方的 鍵-值 形式: spa

{ "title":            [ eggs, nest ], "body":             [ making, money, work, your ], "tags":             [ cash, shares ], "comments.name":    [ alice, john, smith, white ], "comments.comment": [ article, great, like, more, please, this ], "comments.age":     [ 28, 31 ], "comments.stars":   [ 4, 5 ], "comments.date":    [ 2014-09-01, 2014-10-22 ]
}

Alice與31 以及 John與2014-09-01 之間的關聯已經沒法挽回的消失了。 當object類型的欄位用於儲存單一對象是很是有用的。 從搜尋的角度來看,對於排序一個對象陣列來講關聯是不須要的東西。 設計

這是嵌套對象被設計來解決的問題。 藉由映射commments欄位爲nested類型而不是object類型, 每一個嵌套對象會被索引爲一個隱藏分割文檔,例如: code

{ <1> "comments.name": [ john, smith ], "comments.comment": [ article, great ], "comments.age": [ 28 ], "comments.stars": [ 4 ], "comments.date": [ 2014-09-01 ] } { <2> "comments.name": [ alice, white ], "comments.comment": [ like, more, please, this ], "comments.age": [ 31 ], "comments.stars": [ 5 ], "comments.date": [ 2014-10-22 ] } { <3> "title": [ eggs, nest ], "body": [ making, money, work, your ], "tags": [ cash, shares ] } 

<1> 第一個嵌套對象 orm

<2> 第二個嵌套對象 對象

<3> 根或是父文檔

藉由分別索引每一個嵌套對象,對象的欄位中保持了其關聯。 咱們的查詢能夠只在同一個嵌套對象都匹配時纔回應。

不只如此,因嵌套對象都被索引了,鏈接嵌套對象至根文檔的查詢速度很是快--幾乎與查詢單一文檔同樣快。

這些額外的嵌套對象被隱藏起來,咱們沒法直接訪問他們。 爲了要新增丶修改或移除一個嵌套對象,咱們必須從新索引整個文檔。 要牢記搜尋要求的結果並非只有嵌套對象,而是整個文檔。



嵌套-映射

嵌套對象映射

設定一個nested欄位很簡單--在你會設定爲object類型的地方,改成nested類型:

PUT /my_index
{ "mappings": { "blogpost": { "properties": { "comments": { "type": "nested", <1> "properties": { "name": { "type": "string" }, "comment": { "type": "string" }, "age": { "type": "short" }, "stars": { "type": "short" }, "date": { "type": "date" } } } } } } } 

<1> 一個nested欄位接受與object類型相同的參數。

所需僅此而已。 任何comments對象會被索引爲分離嵌套對象。 參考更多 nested type reference docs

嵌套-查詢

查詢嵌套對象

因嵌套對象(nested objects)會被索引爲分離的隱藏文檔,咱們不能直接查詢它們。而是使用 nested查詢nested 過濾器來存取它們:

GET /my_index/blogpost/_search
{ "query": { "bool": { "must": [
        { "match": { "title": "eggs" }}, <1> { "nested": { "path": "comments", <2> "query": { "bool": { "must": [ <3> { "match": { "comments.name": "john" }}, { "match": { "comments.age": 28 }} ] }}}} ] }}} 

<1> title條件運做在根文檔上

<2> nested條件深刻嵌套的comments欄位。它不會在存取根文檔的欄位,或是其餘嵌套文檔的欄位。

<3> comments.name以及comments.age運做在相同的嵌套文檔。

TIP

一個nested欄位能夠包含其餘nested欄位。 相同的,一個nested查詢能夠包含其餘nested查詢。 嵌套階層會如同你預期的運做。

固然,一個nested查詢能夠匹配多個嵌套文檔。 每一個文檔的匹配會有各自的關聯分數,但多個分數必須減小至單一分數才能應用至根文檔。

在預設中,它會平均全部嵌套文檔匹配的分數。這能夠藉由設定score_mode參數爲avg, max, sum或甚至none(爲了防止根文檔永遠得到1.0的匹配分數時)來控制。

GET /my_index/blogpost/_search
{ "query": { "bool": { "must": [
        { "match": { "title": "eggs" }},
        { "nested": { "path": "comments", "score_mode": "max", <1> "query": { "bool": { "must": [ { "match": { "comments.name": "john" }}, { "match": { "comments.age": 28 }} ] }}}} ] }}} 

<1> 從最匹配的嵌套文檔中給予根文檔的_score值。

注意

nested過濾器相似於nested查詢,除了沒法使用score_mode參數。 只能使用在filter context—例如在filtered查詢中--其做用相似其餘的過濾器: 包含或不包含,但不評分。

nested過濾器的結果自己不會緩存,一般緩存規則會被應用於nested過濾器之中的過濾器。


嵌套排序

以嵌套欄位排序

咱們能夠依照嵌套欄位中的值來排序,甚至藉由分離嵌套文檔中的值。爲了使其結果更加有趣,咱們加入另外一個記錄:

PUT /my_index/blogpost/2 { "title": "Investment secrets", "body": "What they don't tell you ...", "tags":  [ "shares", "equities" ], "comments": [
    { "name": "Mary Brown", "comment": "Lies, lies, lies", "age": 42, "stars": 1, "date": "2014-10-18" },
    { "name": "John Smith", "comment": "You're making it up!", "age": 28, "stars": 2, "date": "2014-10-16" }
  ]
}

想像咱們要取回在十月中有收到迴應的blog文章,並依照所取回的各個blog文章中最少stars數量的順序做排序。 這個搜尋請求以下:

GET /_search
{ "query": { "nested": { <1> "path": "comments", "filter": { "range": { "comments.date": { "gte": "2014-10-01", "lt": "2014-11-01" } } } } }, "sort": { "comments.stars": { <2> "order": "asc", <2> "mode": "min", <2> "nested_filter": { <3> "range": { "comments.date": { "gte": "2014-10-01", "lt": "2014-11-01" } } } } } } 

<1> nested查詢限制告終果爲十月份收到迴應的blog文章。

<2> 結果在全部匹配的迴應中依照comment.stars欄位的最小值(min)做遞增(asc)的排序。

<3> 排序條件中的nested_filter與主查詢query條件中的nested查詢相同。 於下一個下方解釋。

爲何咱們要在nested_filter重複寫上查詢條件? 緣由是排序在於執行查詢後才發生。 此查詢匹配了在十月中有收到迴應的blog文章,回傳blog文章文檔做爲結果。 若是咱們不加上nested_filter條件,咱們最後會依照任何blog文章曾經收到過的迴應做排序,而不是在十月份收到的。

嵌套-集合

嵌套-集合

如同咱們在查詢時須要使用nested查詢來存取嵌套對象,專門的nested集合使咱們能夠取得嵌套對象中欄位的集合:

GET /my_index/blogpost/_search?search_type=count
{ "aggs": { "comments": { <1> "nested": { "path": "comments" }, "aggs": { "by_month": { "date_histogram": { <2> "field": "comments.date", "interval": "month", "format": "yyyy-MM" }, "aggs": { "avg_stars": { "avg": { <3> "field": "comments.stars" } } } } } } } } 

<1> nested集合深刻嵌套對象的comments欄位

<2> 評論基於comments.date欄位被分至各個月份分段

<3> 每月份分段單獨計算星號的平均數

結果顯示集合發生於嵌套文檔層級:

... "aggregations": { "comments": { "doc_count": 4, <1> "by_month": { "buckets": [ { "key_as_string": "2014-09", "key": 1409529600000, "doc_count": 1, <1> "avg_stars": { "value": 4 } }, { "key_as_string": "2014-10", "key": 1412121600000, "doc_count": 3, <1> "avg_stars": { "value": 2.6666666666666665 } } ] } } } ... 

<1> 此處總共有四個comments: 一個在九月以及三個在十月

反向-嵌套-集合

反向嵌套-集合

一個nested集合只能存取嵌套文檔中的欄位,而沒法看見根文檔或其餘嵌套文檔中的欄位。 然而,咱們能夠跳出嵌套區塊,藉由reverse_nested集合回到父階層。

舉例來講,咱們能夠發現使用評論者的年齡爲其加上tags頗有趣。 comment.age是在嵌套欄位中,可是tags位於根文檔:

GET /my_index/blogpost/_search?search_type=count
{ "aggs": { "comments": { "nested": { <1> "path": "comments" }, "aggs": { "age_group": { "histogram": { <2> "field": "comments.age", "interval": 10 }, "aggs": { "blogposts": { "reverse_nested": {}, <3> "aggs": { "tags": { "terms": { <4> "field": "tags" } } } } } } } } } } 

<1> nested集合深刻comments對象

<2> histogram集合以comments.age欄位彙集成每十年一個的分段

<3> reverse_nested集合跳回到根文檔

<4> terms集合計算每一個年齡分段的火紅詞語

簡略的結果顯示以下:

.. "aggregations": { "comments": { "doc_count": 4, <1> "age_group": { "buckets": [ { "key": 20, <2> "doc_count": 2, <2> "blogposts": { "doc_count": 2, <3> "tags": { "doc_count_error_upper_bound": 0, "buckets": [ <4> { "key": "shares", "doc_count": 2 }, { "key": "cash", "doc_count": 1 }, { "key": "equities", "doc_count": 1 } ] } } }, ... 

<1> 共有四個評論

<2> 有兩個評論的發表者年齡介於20至30之間

<3> 兩個blog文章與這些評論相關

<4> 這些blog文章的火紅標籤是shares丶cash丶equities

什麼時候要使用嵌套對象

嵌套對象對於當有一個主要實體(如blogpost),加上有限數量的緊密相關實體(如comments)是很是有用的。 有辦法能以評論內容找到blog文章頗有用,且nested查詢及過濾器提供短查詢時間鏈接(fast query-time joins)。

嵌套模型的缺點以下:

  • 如欲新增丶修改或刪除一個嵌套文檔,則必須從新索引整個文檔。所以越多嵌套文檔形成越多的成本。

  • 搜尋請求回傳整個文檔,而非只有匹配的嵌套文檔。 雖然有個進行中的計畫要支持只回傳根文檔及最匹配的嵌套文檔,但目前並未支持。

有時你須要完整分離主要文檔及其關連實體。 父-子關係提供這一個功能。

相關文章
相關標籤/搜索