在日常开发中,常遇到父子关系的对象,它们通常是1:N的关系:
parent
可以有多个child
child
只能有一个parent
如果我们用关系型数据库(如MySQL),我们还是挺熟悉如何设计表的。一个主表,一个子表,两个表可以用join
、left join
、right join
做关联查询。
而在ES中,不能像MySQL那样设计表了,ES提供了2种表达父子关系的方案:
nested
,子属于父的某一个nested字段里
,子属于父的一部分,父和子在同一文档里。join
,子和父在同一index里,但不在同一文档里,父和子都是独立的文档。join
类型父子文档必须在同一分片上nested
类型,之前的博客Mapping字段类型之nested已经讲过了,本篇博客主要讲join
类型。
join
类型是一种用来创建父子关系的特殊类型,且父子文档都存放在同一index里。
父子关系,不能是多对多,也就是说一个父亲可以有多个儿子,但一个儿子只能有一个父亲。
下面我要创建《王者荣耀》里两个阵营(group)
以及其英雄(hero)
的信息,举例说明join类型。
阵营(group)
是父文档,英雄(hero)
是子文档group
和hero
,后面的查询都少不了它们ID | name | 关系类型 | 父ID |
---|---|---|---|
group1 | 稷下学院 | group | 无 |
group2 | 长安 | group | 无 |
hero1 | 老夫子 | hero | group1 |
hero2 | 墨子 | hero | group1 |
hero3 | 李白 | hero | group2 |
hero4 | 李元芳 | hero | group2 |
PUT pigg_test_join
{
"mappings": {
"properties": {
"name": { # 第1个字段叫name,是keyword类型
"type": "keyword"
},
"join_field": { # 第2个字段叫join_field,是join类型
"type": "join",
"relations": { # relations是固定关键字,表示父子关系
"group": "hero" # 冒号:的左边是父类型名称,右边是子类型名称
}
}
}
}
}
插入id=group1
、name=稷下学院
的父文档:
PUT pigg_test_join/_doc/group1
{
"name": "稷下学院",
"join_field": {
"name": "group" # 指定文档为父类型group
}
}
插入id=group2
、name=长安
的父文档,指定父子类型可以用如下简化写法:
PUT pigg_test_join/_doc/group2
{
"name": "长安",
"join_field": "group" # 简化写法,指定文档为父类型group
}
routing
为父ID创建、更新、删除、获取
子文档时,都得加上routing
参数parent
指定父ID插入稷下学院
阵营的2个子文档
PUT pigg_test_join/_doc/hero1?routing=group1 # 指定routing
{
"name": "老夫子",
"join_field": {
"name": "hero", # 指定该文档为子文档
"parent": "group1" # 指定父ID
}
}
PUT pigg_test_join/_doc/hero2?routing=group1
{
"name": "墨子",
"join_field": {
"name": "hero",
"parent": "group1"
}
}
插入长安
阵营的2个子文档
PUT pigg_test_join/_doc/hero3?routing=group2
{
"name": "李白",
"join_field": {
"name": "hero",
"parent": "group2"
}
}
PUT pigg_test_join/_doc/hero4?routing=group2
{
"name": "李元芳",
"join_field": {
"name": "hero",
"parent": "group2"
}
}
parent_id
根据父ID查询儿子parent_id
查询是根据父亲ID查询其儿子文档parent_id.type
是指定儿子文档的类型,因为子类型可以有多个,所以要指定下GET pigg_test_join/_search
{
"query": {
"parent_id": { # 关键字
"type": "hero", # 子文档的类型
"id": "group1" # 父ID
}
}
}
上面查询返回的是老夫子
和墨子
这2个文档
has_parent
根据父查子has_parent
是固定关键字has_parent.parent_type
是指定父类型query
查询条件体应该是对对父亲的描述比如查询name=长安
的阵营有哪些英雄?
GET pigg_test_join/_search
{
"query": {
"has_parent": {
"parent_type": "group",
"query": {
"term": {
"name": "长安"
}
}
}
}
}
上面查询返回的是李元芳
和李白
这两个文档
has_child
根据子查父has_child.type
指定子类型query
查询体里面是描述儿子比如查询哪些阵营
有姓李的英雄?
GET pigg_test_join/_search
{
"query": {
"has_child": {
"type": "hero",
"query": {
"prefix": {
"name": "李"
}
}
}
}
}
nested | join | |
---|---|---|
优点 | 父子文档存储一起,读取性能高 | 父子文档可以独立更新 |
缺点 | 更新嵌套的子文档时,更新nested字段的全部文档 | 需要额外的内存维护关系,读取性能相对差 |
适用场景 | 子文档偶尔更新,以查询为主 | 子文档更新频繁 |
在我实际工作中用nested
更多些,join
类型设计的相当复杂,对人员的水平要求较高些(你得足够细心),而且在父子文档分别
存储时,我们也可以使用nested
类型来维护父子关系,而不一定用join
。
具体的可以参考我之前的博客:
ES 存储树形结构 整合Spring Data Elasticsearch
Elasticsearch工具类 支持树形结构