Elasticsearch中的父子关系是单个索引内部文档与文档之间的一种关系,父文档与子文档同属一个索引并通过父文档id建立联系, 类似于关系型数据库中单表内部行与行之间的自关联,比如有层级关系的部门表中记录之间的关联。
在Elasticsearch中并没有外键的概念,文档之间的父子关系通过给索引定义join类型字段实现。
假设在关系型数据库中有这么一张表employes:
ID | name | parent |
---|---|---|
1 | tom | |
2 | smith | 1 |
3 | john | 1 |
下面用Elasticsearch中的文档来表示这些数据之间的关系。
例如创建一个员工索引employes,定义个join类型的managemet字段用于确定员工之间的管理与被管理关系:
PUT employees
{
"mappings": {
"properties": {
"management": {
"type": "join",
"relations": {
"manager": "member"
}
}
}
}
}
在示例中,management字段的数据类型被定义为join,同时在该字段的relations参数中定义父子关系为manager与member,其中manager为父而member为子,它们的名称可由用户自定义。文档在父子关系中的地位,是在添加文档时通过join类型字段指定的。
还是以employes索引为例,在向employees索引中添加父文档时,应该将mangement字段设置为manager;而添加子文档时则应该设置为member。具体如下:
PUT /employees/_doc/1
{
"name": "tom",
"management": {
"name": "manager"
}
}
PUT /employees/_doc/2?routing=1
{
"name": "smith",
"management": {
"name": "member",
"parent": "1"
}
}
PUT /employees/_doc/3?routing=1
{
"name": "john",
"management": {
"name": "member",
"parent": "1"
}
}
在示例中,编号为1的文档其management字段通过name参数设置为manager,即在索引定义父子关系中处于父文档的地位,而编号为2的文档既是member,又是management。
在使用父子关系时,要求父子文档必须要映射到同一分片中,所以在添加子文档时routing参数是必须要设置的。显然父子文档在同一分片可以提升在检索时的性能,可在父子关系中使用的查询方法有has_child、has_parent和parent_id查询,还有parent和children两种聚集。
has_child查询是根据子文档检索父文档的一种方法,它先根据查询条件将满足条件的子文档检索出来,在最终的结果中会返回具有这些子文档的父文档。
例如,如果想检索smith的经理是谁,可以:
POST /employees/_search?filter_path=hits
{
"query": {
"has_child": {
"type": "member",
"query": {
"match": {
"name": "smith"
}
}
}
}
}
输出结果:
{
"hits" : {
"total" : {
"value" : 1,
"relation" : "eq"
},
"max_score" : 1.0,
"hits" : [
{
"_index" : "employees",
"_type" : "_doc",
"_id" : "1",
"_score" : 1.0,
"_source" : {
"name" : "tom",
"management" : {
"name" : "manager"
}
}
}
]
}
}
在示例中,has_child查询的type参数需要设置为父子关系中子文档的名称member,这样has_child查询父子关系时就限定在这种类型中检索;query参数则设置了查询子文档的条件,即名称为smith。最终结果会根据smith所在文档,通过member对应的父子关系检索它的父文档。
has_parent查询与has_child查询正好相反,是通过父文档检索子文档的一种方法。在执行流程上,has_parent查询先将满足查询条件的父文档检索出来,但在最终返回的结果中展示的是具有这些父文档的子文档。
例如,如果想查看tom的所有下属,可以按示例请求:
POST /employees/_search?filter_path=hits
{
"query": {
"has_parent": {
"parent_type": "manager",
"query": {
"match": {
"name": "tom"
}
}
}
}
}
输出结果:
{
"hits" : {
"total" : {
"value" : 2,
"relation" : "eq"
},
"max_score" : 1.0,
"hits" : [
{
"_index" : "employees",
"_type" : "_doc",
"_id" : "2",
"_score" : 1.0,
"_routing" : "1",
"_source" : {
"name" : "smith",
"management" : {
"name" : "member",
"parent" : "1"
}
}
},
{
"_index" : "employees",
"_type" : "_doc",
"_id" : "3",
"_score" : 1.0,
"_routing" : "1",
"_source" : {
"name" : "john",
"management" : {
"name" : "member",
"parent" : "1"
}
}
}
]
}
}
has_parent查询在结构上与has_child查询基本相同,只是在指定父子关系时使用的参数是parent_type而不是type。
parent_id查询与has_parent查询的作用相似,都是根据父文档检索子文档。不同的是,has_parent可以通过query参数设置不同的查询条件;而parent_id查询则只能通过父文档id做检索。
例如,查询父文档id为1的子文档:
POST /employees/_search?filter_path=hits
{
"query": {
"parent_id": {
"type": "member",
"id": 1
}
}
}
输出结果:
{
"hits" : {
"total" : {
"value" : 2,
"relation" : "eq"
},
"max_score" : 0.13353139,
"hits" : [
{
"_index" : "employees",
"_type" : "_doc",
"_id" : "2",
"_score" : 0.13353139,
"_routing" : "1",
"_source" : {
"name" : "smith",
"management" : {
"name" : "member",
"parent" : "1"
}
}
},
{
"_index" : "employees",
"_type" : "_doc",
"_id" : "3",
"_score" : 0.13353139,
"_routing" : "1",
"_source" : {
"name" : "john",
"management" : {
"name" : "member",
"parent" : "1"
}
}
}
]
}
}
如果想通过父文档检索与其关联的所有子文档就可以使用children聚集。
如果想要查看tom的所有下属就可以按示例的方式检索:
POST /employees/_search?filter_path=aggregations
{
"query": {
"term": {
"name": "tom"
}
},
"aggs": {
"members": {
"children": {
"type": "member"
},
"aggs": {
"member_name": {
"terms": {
"field": "name.keyword",
"size": 10
}
}
}
}
}
}
输出结果:
{
"aggregations" : {
"members" : {
"doc_count" : 2,
"member_name" : {
"doc_count_error_upper_bound" : 0,
"sum_other_doc_count" : 0,
"buckets" : [
{
"key" : "john",
"doc_count" : 1
},
{
"key" : "smith",
"doc_count" : 1
}
]
}
}
}
}
在示例中,query参数设置了父文档的查询条件,即名称字段name为tom的文档,而聚集查询members中则使用了children 聚集将它的子文档检索出来,同时还使用了一个嵌套聚集member_name将子文档name字段的词项全部展示出来了。
parent聚集与children聚集正好相反,它是根据子文档查找父文档,parent聚集在Elasrticsearch版本6.6以后才支持。
例如通过name字段为smith的文档,在找该文档的父文档:
POST /employees/_search?filter_path=aggregations
{
"query": {
"match": {
"name": "smith"
}
},
"aggs": {
"who_is_manager": {
"parent": {
"type": "member"
},
"aggs": {
"manager_name": {
"terms": {
"field": "name.keyword",
"size": 10
}
}
}
}
}
}
输出结果:
{
"aggregations" : {
"who_is_manager" : {
"doc_count" : 1,
"manager_name" : {
"doc_count_error_upper_bound" : 0,
"sum_other_doc_count" : 0,
"buckets" : [
{
"key" : "tom",
"doc_count" : 1
}
]
}
}
}
}