当前位置: 首页 > 工具软件 > namesearch > 使用案例 >

Elasticsearch文档内部的父子关系

段干楚青
2023-12-01

Elasticsearch中的父子关系是单个索引内部文档与文档之间的一种关系,父文档与子文档同属一个索引并通过父文档id建立联系, 类似于关系型数据库中单表内部行与行之间的自关联,比如有层级关系的部门表中记录之间的关联。

join类型

在Elasticsearch中并没有外键的概念,文档之间的父子关系通过给索引定义join类型字段实现。

假设在关系型数据库中有这么一张表employes:

IDnameparent
1tom
2smith1
3john1

下面用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查询

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_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查询

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聚集

如果想通过父文档检索与其关联的所有子文档就可以使用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聚集

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
          }
        ]
      }
    }
  }
}
 类似资料: