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中,我們可以將密切相關的實體存儲在單個文檔中。 例如,我們可以通過傳遞一系列評論來存儲博客文章及其所有評論。
舉例:
1{ 2 "title": "Invest Money", 3 "body": "Please start investing money as soon...", 4 "tags": ["money", "invest"], 5 "published_on": "18 Oct 2017", 6 "comments": [ 7 { 8 "name": "William", 9 "age": 34, 10 "rating": 8, 11 "comment": "Nice article..", 12 "commented_on": "30 Nov 2017" 13 }, 14 { 15 "name": "John", 16 "age": 38, 17 "rating": 9, 18 "comment": "I started investing after reading this.", 19 "commented_on": "25 Nov 2017" 20 }, 21 { 22 "name": "Smith", 23 "age": 33, 24 "rating": 7, 25 "comment": "Very good post", 26 "commented_on": "20 Nov 2017" 27 } 28 ] 29}
如上所示,所以我們有一個文檔描述了一個帖子和一個包含帖子上所有評論的內部對象評論。
但是Elasticsearch搜索中的內部對象並不像我們期望的那樣工作。
2.2 問題出現
現在假設我們想查找用戶{name:john,age:34}評論過的所有博客帖子。 讓我們再看一下上面的示例文檔,找到評論過的用戶。
name | age |
---|---|
William | 34 |
John | 38 |
Smith | 33 |
從列表中我們可以清楚地看到,沒有34歲的用戶John。
爲簡單起見,我們在elasticsearch索引中只有1個文檔。
讓我們通過查詢索引來驗證它:
1GET /blog/_search?pretty 2{ 3 "query": { 4 "bool": { 5 "must": [ 6 { 7 "match": { 8 "comments.name": "John" 9 } 10 }, 11 { 12 "match": { 13 "comments.age": 34 14 } 15 } 16 ] 17 } 18 } 19}
我們的示例文檔作爲回覆返回。 很驚訝,這是爲什麼呢?
2.3 原因分析
這就是爲什麼我說:elasticsearch中的內部對象無法按預期工作。
這裏的問題是elasticsearch(lucene)使用的庫沒有內部對象的概念,因此內部對象被扁平化爲一個簡單的字段名稱和值列表。
我們的文檔內部存儲爲:
1{ 2 "title": [ invest, money ], 3 "body": [ as, investing, money, please, soon, start ], 4 "tags": [ invest, money ], 5 "published_on": [ 18 Oct 2017 ] 6 "comments.name": [ smith, john, william ], 7 "comments.comment": [ after, article, good, i, investing, nice, post, reading, started, this, very ], 8 "comments.age": [ 33, 34, 38 ], 9 "comments.rating": [ 7, 8, 9 ], 10 "comments.commented_on": [ 20 Nov 2017, 25 Nov 2017, 30 Nov 2017 ] 11}
如上,您可以清楚地看到,comments.name和comments.age之間的關係已丟失。
這就是爲什麼我們的文檔匹配john和34的查詢。
2.4 如何解決呢?
要解決這個問題,我們只需要對elasticsearch的映射進行一些小改動。
如果您查看索引的映射,您會發現comments字段的類型是object。 我們需要更新它的類型爲nested。
我們可以通過運行以下查詢來簡單地更新索引的映射:
1PUT /blog_new 2{ 3 "mappings": { 4 "blog": { 5 "properties": { 6 "title": { 7 "type": "text" 8 }, 9 "body": { 10 "type": "text" 11 }, 12 "tags": { 13 "type": "keyword" 14 }, 15 "published_on": { 16 "type": "keyword" 17 }, 18 "comments": { 19 "type": "nested", 20 "properties": { 21 "name": { 22 "type": "text" 23 }, 24 "comment": { 25 "type": "text" 26 }, 27 "age": { 28 "type": "short" 29 }, 30 "rating": { 31 "type": "short" 32 }, 33 "commented_on": { 34 "type": "text" 35 } 36 } 37 } 38 } 39 } 40 } 41}
將映射更改爲Nested類型後,我們可以查詢索引的方式略有變化。 我們需要使用Nested查詢。
下面給出了Nested查詢示例:
1GET /blog_new/_search?pretty 2{ 3 "query": { 4 "bool": { 5 "must": [ 6 { 7 "nested": { 8 "path": "comments", 9 "query": { 10 "bool": { 11 "must": [ 12 { 13 "match": { 14 "comments.name": "john" 15 } 16 }, 17 { 18 "match": { 19 "comments.age": 34 20 } 21 } 22 ] 23 } 24 } 25 } 26 } 27 ] 28 } 29 } 30}
由於用戶{name:john,age:34}沒有匹配,上面的查詢將不返回任何文檔。
再次感到驚訝? 只需一個小小的改變即可解決問題。
這可能是我們理解的一個較小的變化,但是在elasticsearch存儲我們的文檔的方式上有很多變化。
在內部,嵌套對象將數組中的每個對象索引爲單獨的隱藏文檔,這意味着可以獨立於其他對象查詢每個嵌套對象。
下面給出了更改映射後樣本文檔的內部表示:
1{ 2 { 3 "comments.name": [ john ], 4 "comments.comment": [ after i investing started reading this ], 5 "comments.age": [ 38 ], 6 "comments.rating": [ 9 ], 7 "comments.date": [ 25 Nov 2017 ] 8 }, 9 { 10 "comments.name": [ william ], 11 "comments.comment": [ article, nice ], 12 "comments.age": [ 34 ], 13 "comments.rating": [ 8 ], 14 "comments.date": [ 30 Nov 2017 ] 15 }, 16 { 17 "comments.name": [ smith ], 18 "comments.comment": [ good, post, very], 19 "comments.age": [ 33 ], 20 "comments.rating": [ 7 ], 21 "comments.date": [ 20 Nov 2017 ] 22 }, 23 { 24 "title": [ invest, money ], 25 "body": [ as, investing, money, please, soon, start ], 26 "tags": [ invest, money ], 27 "published_on": [ 18 Oct 2017 ] 28 } 29}
如您所見,每個內部對象都在內部存儲爲單獨的隱藏文檔。 這保持了他們的領域之間的關係。
3、Nested類型的作用?
從上一小節,可以清晰的看出nested類型的特別之處。
nested類型是對象數據類型的專用版本,它允許對象數組以可以彼此獨立查詢的方式進行索引。
4、Nested類型的適用場景
圖片來自:rockybean教程
5、Nested類型的增、刪、改、查、聚合操作詳解
還是以第2節的blog_new索引示例,Nested類型的增、刪、改、查操作。
5.1 Nested類型——增
新增blog和評論
1POST blog_new/blog/2 2{ 3 "title": "Hero", 4 "body": "Hero test body...", 5 "tags": ["Heros", "happy"], 6 "published_on": "6 Oct 2018", 7 "comments": [ 8 { 9 "name": "steve", 10 "age": 24, 11 "rating": 18, 12 "comment": "Nice article..", 13 "commented_on": "3 Nov 2018" 14 } 15 ] 16}
5.2 Nested類型——刪
序號爲1的評論原來有三條,現在刪除John的評論數據,刪除後評論數爲2條。
1POST blog_new/blog/1/_update 2{ 3 "script": { 4 "lang": "painless", 5 "source": "ctx._source.comments.removeIf(it -> it.name == 'John');" 6 } 7}
5.3 Nested類型——改
將steve評論內容中的age值調整爲25,同時調整了評論內容。
1POST blog_new/blog/2/_update 2{ 3 "script": { 4 "source": "for(e in ctx._source.comments){if (e.name == 'steve') {e.age = 25; e.comment= 'very very good article...';}}" 5 } 6}
5.4 Nested類型——查
如前所述,查詢評論字段中評論姓名=William並且評論age=34的blog信息。
1GET /blog_new/_search?pretty 2{ 3 "query": { 4 "bool": { 5 "must": [ 6 { 7 "nested": { 8 "path": "comments", 9 "query": { 10 "bool": { 11 "must": [ 12 { 13 "match": { 14 "comments.name": "William" 15 } 16 }, 17 { 18 "match": { 19 "comments.age": 34 20 } 21 } 22 ] 23 } 24 } 25 } 26 } 27 ] 28 } 29 } 30}
5.5 Nested類型——聚合
認知前提:nested聚合隸屬於聚合分類中的Bucket聚合分類。
聚合blog_new 中評論者年齡最小的值。
1GET blog_new/_search 2{ 3 "size": 0, 4 "aggs": { 5 "comm_aggs": { 6 "nested": { 7 "path": "comments" 8 }, 9 "aggs": { 10 "min_age": { 11 "min": { 12 "field": "comments.age" 13 } 14 } 15 } 16 } 17 } 18}
6、小結
- 如果您在索引中使用內部對象並做查詢操作,請驗證內部對象的類型是否爲nested類型。 否則查詢可能會返回無效的結果文檔。
- 更新認知是非常痛苦的,不確定的問題只有親手實踐才能檢驗真知。
參考: [1]http://t.cn/Evwh0uW [2]官網6.x+:http://t.cn/Ehltakr