ES 6.x父子文檔


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類型約束

  1. 每個索引只允許一個Join類型Mapping定義;

  2. 父文檔和子文檔必須在同一個分片上編入索引;這意味着,當進行刪除、更新、查找子文檔時候需要提供相同的路由值。

  3. 一個文檔可以有多個子文檔,但只能有一個父文檔。

  4. 可以為已經存在的Join類型添加新的關系。

  5. 當一個文檔已經成為父文檔后,可以為該文檔添加子文檔。

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

轉載自銘意天下公眾號


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM