0、ES6.X 一對多、多對多的數據該如何存儲和實現呢?
引出問題:
“某頭條新聞APP”新聞內容和新聞評論是1對多的關系?
在ES6.X該如何存儲、如何進行高效檢索、聚合操作呢?
相信閱讀本文,你就能得到答案!
1、ES6.X 新類型Join 產生背景
-
Mysql中多表關聯,我們可以通過left join 或者Join等實現;
-
ES5.X版本,借助父子文檔實現多表關聯,類似數據庫中Join的功能;實現的核心是借助於ES5.X支持1個索引(index)下多個類型(type)。
-
ES6.X版本,由於每個索引下面只支持單一的類型(type)。
-
所以,ES6.X版本如何實現Join成為大家關注的問題。
幸好,ES6.X新推出了Join類型,主要解決類似Mysql中多表關聯的問題。
2、ES6.X Join類型介紹
仍然是一個索引下,借助父子關系,實現類似Mysql中多表關聯的操作。
3、ES6.X Join類型實戰
3.1 ES6.X Join類型 Mapping定義
Join類型的Mapping如下:
核心
-
1) "my_join_field"為join的名稱。
-
2)"question": "answer" 指:qustion為answer的父類。
1PUT my_join_index
2{
3 "mappings": {
4 "_doc": {
5 "properties": {
6 "my_join_field": {
7 "type": "join",
8 "relations": {
9 "question": "answer"
10 }
11 }
12 }
13 }
14 }
15}
3.2 ES6.X join類型定義父文檔
直接上以下簡化的形式,更好理解些。
如下,定義了兩篇父文檔。
文檔類型為父類型:"question"。
1PUT my_join_index/_doc/1?refresh
2{
3 "text": "This is a question",
4 "my_join_field": "question"
5}
6PUT my_join_index/_doc/2?refresh
7{
8 "text": "This is another question",
9 "my_join_field": "question"
10}
3.3 ES6.X join類型定義子文檔
路由值是強制性的,因為父文件和子文件必須在相同的分片上建立索引。
"answer"是此子文檔的加入名稱。
指定此子文檔的父文檔ID:1。
1PUT my_join_index/_doc/3?routing=1&refresh
2{
3 "text": "This is an answer",
4 "my_join_field": {
5 "name": "answer",
6 "parent": "1"
7 }
8}
9PUT my_join_index/_doc/4?routing=1&refresh
10{
11 "text": "This is another answer",
12 "my_join_field": {
13 "name": "answer",
14 "parent": "1"
15 }
16}
4、ES6.X Join類型約束
每個索引只允許一個Join類型Mapping定義;
父文檔和子文檔必須在同一個分片上編入索引;這意味着,當進行刪除、更新、查找子文檔時候需要提供相同的路由值。
一個文檔可以有多個子文檔,但只能有一個父文檔。
可以為已經存在的Join類型添加新的關系。
當一個文檔已經成為父文檔后,可以為該文檔添加子文檔。
5、ES6.X Join類型檢索與聚合
5.1 ES6.X Join全量檢索
1GET my_join_index/_search
2{
3 "query": {
4 "match_all": {}
5 },
6 "sort": ["_id"]
7}
返回結果如下:
1{
2 "took": 1,
3 "timed_out": false,
4 "_shards": {
5 "total": 5,
6 "successful": 5,
7 "skipped": 0,
8 "failed": 0
9 },
10 "hits": {
11 "total": 4,
12 "max_score": null,
13 "hits": [
14 {
15 "_index": "my_join_index",
16 "_type": "_doc",
17 "_id": "1",
18 "_score": null,
19 "_source": {
20 "text": "This is a question",
21 "my_join_field": "question"
22 },
23 "sort": [
24 "1"
25 ]
26 },
27 {
28 "_index": "my_join_index",
29 "_type": "_doc",
30 "_id": "2",
31 "_score": null,
32 "_source": {
33 "text": "This is another question",
34 "my_join_field": "question"
35 },
36 "sort": [
37 "2"
38 ]
39 },
40 {
41 "_index": "my_join_index",
42 "_type": "_doc",
43 "_id": "3",
44 "_score": null,
45 "_routing": "1",
46 "_source": {
47 "text": "This is an answer",
48 "my_join_field": {
49 "name": "answer",
50 "parent": "1"
51 }
52 },
53 "sort": [
54 "3"
55 ]
56 },
57 {
58 "_index": "my_join_index",
59 "_type": "_doc",
60 "_id": "4",
61 "_score": null,
62 "_routing": "1",
63 "_source": {
64 "text": "This is another answer",
65 "my_join_field": {
66 "name": "answer",
67 "parent": "1"
68 }
69 },
70 "sort": [
71 "4"
72 ]
73 }
74 ]
75 }
76}
5.2 ES6.X 基於父文檔查找子文檔
1GET my_join_index/_search
2{
3 "query": {
4 "has_parent" : {
5 "parent_type" : "question",
6 "query" : {
7 "match" : {
8 "text" : "This is"
9 }
10 }
11 }
12 }
13}
返回結果:
1{
2 "took": 0,
3 "timed_out": false,
4 "_shards": {
5 "total": 5,
6 "successful": 5,
7 "skipped": 0,
8 "failed": 0
9 },
10 "hits": {
11 "total": 2,
12 "max_score": 1,
13 "hits": [
14 {
15 "_index": "my_join_index",
16 "_type": "_doc",
17 "_id": "3",
18 "_score": 1,
19 "_routing": "1",
20 "_source": {
21 "text": "This is an answer",
22 "my_join_field": {
23 "name": "answer",
24 "parent": "1"
25 }
26 }
27 },
28 {
29 "_index": "my_join_index",
30 "_type": "_doc",
31 "_id": "4",
32 "_score": 1,
33 "_routing": "1",
34 "_source": {
35 "text": "This is another answer",
36 "my_join_field": {
37 "name": "answer",
38 "parent": "1"
39 }
40 }
41 }
42 ]
43 }
44}
5.3 ES6.X 基於子文檔查找父文檔
1GET my_join_index/_search
2{
3"query": {
4 "has_child" : {
5 "type" : "answer",
6 "query" : {
7 "match" : {
8 "text" : "This is question"
9 }
10 }
11 }
12 }
13}
返回結果:
1{
2 "took": 0,
3 "timed_out": false,
4 "_shards": {
5 "total": 5,
6 "successful": 5,
7 "skipped": 0,
8 "failed": 0
9 },
10 "hits": {
11 "total": 1,
12 "max_score": 1,
13 "hits": [
14 {
15 "_index": "my_join_index",
16 "_type": "_doc",
17 "_id": "1",
18 "_score": 1,
19 "_source": {
20 "text": "This is a question",
21 "my_join_field": "question"
22 }
23 }
24 ]
25 }
26}
5.4 ES6.X Join聚合操作實戰
以下操作含義如下:
-
1)parent_id是特定的檢索方式,用於檢索屬於特定父文檔id=1的,子文檔類型為answer的文檔的個數。
-
2)基於父文檔類型question進行聚合;
-
3)基於指定的field處理。
1GET my_join_index/_search
2{
3 "query": {
4 "parent_id": {
5 "type": "answer",
6 "id": "1"
7 }
8 },
9 "aggs": {
10 "parents": {
11 "terms": {
12 "field": "my_join_field#question",
13 "size": 10
14 }
15 }
16 },
17 "script_fields": {
18 "parent": {
19 "script": {
20 "source": "doc['my_join_field#question']"
21 }
22 }
23 }
24}
返回結果:
1{
2 "took": 1,
3 "timed_out": false,
4 "_shards": {
5 "total": 5,
6 "successful": 5,
7 "skipped": 0,
8 "failed": 0
9 },
10 "hits": {
11 "total": 2,
12 "max_score": 0.13353139,
13 "hits": [
14 {
15 "_index": "my_join_index",
16 "_type": "_doc",
17 "_id": "3",
18 "_score": 0.13353139,
19 "_routing": "1",
20 "fields": {
21 "parent": [
22 "1"
23 ]
24 }
25 },
26 {
27 "_index": "my_join_index",
28 "_type": "_doc",
29 "_id": "4",
30 "_score": 0.13353139,
31 "_routing": "1",
32 "fields": {
33 "parent": [
34 "1"
35 ]
36 }
37 }
38 ]
39 },
40 "aggregations": {
41 "parents": {
42 "doc_count_error_upper_bound": 0,
43 "sum_other_doc_count": 0,
44 "buckets": [
45 {
46 "key": "1",
47 "doc_count": 2
48 }
49 ]
50 }
51 }
52}
6、ES6.X Join 一對多實戰
6.1 一對多定義
如下,一個父文檔question與多個子文檔answer,comment的映射定義。
1PUT join_ext_index
2{
3 "mappings": {
4 "_doc": {
5 "properties": {
6 "my_join_field": {
7 "type": "join",
8 "relations": {
9 "question": ["answer", "comment"]
10 }
11 }
12 }
13 }
14 }
15}
6.2 一對多對多定義
實現如下圖的祖孫三代關聯關系的定義。
1question
2 / \
3 / \
4comment answer
5 |
6 |
7 vote
1PUT join_multi_index
2{
3 "mappings": {
4 "_doc": {
5 "properties": {
6 "my_join_field": {
7 "type": "join",
8 "relations": {
9 "question": ["answer", "comment"],
10 "answer": "vote"
11 }
12 }
13 }
14 }
15 }
16}
孫子文檔導入數據,如下所示:
1PUT join_multi_index/_doc/3?routing=1&refresh
2{
3 "text": "This is a vote",
4 "my_join_field": {
5 "name": "vote",
6 "parent": "2"
7 }
8}
注意:
1- 孫子文檔所在分片必須與其父母和祖父母相同
2- 孫子文檔的父代號(必須指向其父親answer文檔)
7、小結
雖然ES官方文檔已經很詳細了,詳見:
http://t.cn/RnBBLgp
轉載自銘意天下公眾號