查看原文
其他

Elasticsearch 6.X 新类型Join深入详解

铭毅天下 铭毅天下 2019-04-16

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

但手敲一遍,翻译一遍,的的确确会更新认知,加深理解。

加入知识星球,更短时间习得更多干货!


    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存