elasticsearch 学习之父子关联查询 parent/child

本文详细介绍了Elasticsearch中parent-child关系的建立与查询,包括如何通过parent找到children和通过children找到parents,以及使用min_children和max_children参数限制查询范围。还展示了如何在查询中使用score_mode优化性能,并给出了Java API的示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

parent-child 关系
关联关系,可以为两个完全分开的文档,可以将一种文档类型以一对多的关系关联到另一个上

优点:

1.parent文档的更新不需要重新为子文档重建索引

2.对子文档进行添加,更改,删除操作室不会影响父文档或者其他子文档

3.子文档可以被当做查询请求的结果返回

Elasticsearch 维护了一个父文档和子文档的映射关系,得益于这个映射,父-子文档关联查询操作非常快。但是这个映射也对父-子文档关系有个限制条件:父文档和其所有子文档,都必须要存储在同一个分片中。

parent-child映射
为了建立parent-child关系,需要在索引创建的时候指定父文档

建立索引
PUT /company
{
“mappings”: {
“dept”: {},
“user”: {
“_parent”: {
“type”: “dept”
}
}
}
}  
通过 child 找到 parents
查询child返回的是parents文档

查询child uname为 "里斯"的员工 部门
GET company/dept/_search
{
“query”: {
“has_child”: {
“type”: “user”,
“query”: {
“match”: {
“uname”:“里斯”
}
},“inner_hits”:{} //inner_hits可以将子文档带出 默认只查3条 可以自己设置 size,from
} } }

has_child 查询可以匹配多个 child 文档,每个都有相应的相关分数。这些分数如何化归为针对 parent 文档的单独分数取决于 score_mode 参数。默认设置是 none,这会忽视 child 分数并给 parents 分配了 1.0 的分值,不过这里也可以使用 avg,min,max 和 sum

开发部的下的员工王五评分更高,会更好匹配

GET company/dept/_search
{
“query”: {
   “has_child”: {
   “type”: “user”,
“score_mode”: “max”, //默认为none 此时明显快于其他模式,因为不需要计算每个child文档的分值,只有在乎分值的时候才需要设置
“query”: {
“match”: {
“uname”:“王五”
    }
  },“inner_hits”:{}
}}}

不带子级查询
@Test
public void test1(){
QueryBuilder qb = JoinQueryBuilders.hasChildQuery(
“user”, //要查询的子类型
QueryBuilders.matchQuery(“uname”,“张”),
ScoreMode.None
);
SearchQuery searchQuery = new NativeSearchQueryBuilder()
.withQuery(qb)
.build();
List depts =elasticsearchTemplate.queryForList(searchQuery,Dept.class);
System.out.println(depts);
}

带子级查询
@Test
public void test1(){
QueryBuilder qb = JoinQueryBuilders.hasChildQuery(
“user”, //要查询的子类型
QueryBuilders.matchQuery(“uname”,“张”),
ScoreMode.None
).innerHit(new InnerHitBuilder);
SearchQuery searchQuery = new NativeSearchQueryBuilder()
.withQuery(qb)
.build();
List depts= elasticsearchTemplate.query(searchQuery, searchResponse -> {
SearchHits hits = searchResponse.getHits();
List list = new ArrayList<>();
Arrays.stream(hits.getHits()).forEach(h -> {
Map<String, Object> source = h.getSource();
SearchHits innerHitsMap=h.getInnerHits().get(“user”);//获取子级数据
List users=Arrays.stream(innerHitsMap.getHits()).map(innerH -> {
Map<String, Object> innerSource = innerH.getSource();
return new User(innerSource.get(“uname”).toString(),Integer.valueOf(innerSource.get(“age”).toString()));
}).collect(Collectors.toList());
list.add(new Dept(source.get(“dname”).toString(),users));
});
return list;
});
System.out.println(depts);
}

min_children 和 max_children
has_child 查询和过滤器都接受 min_children 和 max_children 参数,仅当匹配 children 的数量在指定的范围内会返回 parent 文档。

查询至少有三个员工的部门

GET company/dept/_search
{
“query”: {
“has_child”: {
“type”: “user”,
“min_children”: 4,
“max_children”:10,
“query”: {
“match_all”: {}
}
}
}
}
@Test
public void test1() {
QueryBuilder qb = JoinQueryBuilders.hasChildQuery(
“user”,
QueryBuilders.matchAllQuery(),
ScoreMode.None
).minMaxChildren(4,10);
SearchQuery searchQuery = new NativeSearchQueryBuilder()
.withQuery(qb)
.build();
List depts = elasticsearchTemplate.queryForList(searchQuery, Dept.class);
}

通过 parents 找到 child
parent-child 文档本身是独立的,每个可以独立地进行查询。has_child 查询允许我们返回基于在其 children 的数据上 parents 文档,has_parent 查询则是基于 parents 的数据返回 children。
has_children 查询也支持 score_mode,但是仅仅会接受两个设置:none(默认)和 score.

查询部门名称有"开"的员工
GET company/user/_search
{
“query”: {
“has_parent”: {
“parent_type”: “dept”,
“query”: {
“match”: {
“dname”:“开”
}
}
}
}
}
不带父级
@Test
public void test2() {
QueryBuilder qb = JoinQueryBuilders.hasParentQuery(
“dept”,
QueryBuilders.matchQuery(“dname”, “开”),
true
);
SearchQuery searchQuery = new NativeSearchQueryBuilder()
.withQuery(qb)
.build();
List depts =elasticsearchTemplate.queryForList(searchQuery,User.class);
System.out.println(depts);
}

带父级

@Test
public void test2() {
    QueryBuilder qb = JoinQueryBuilders.hasParentQuery(
            "dept",
            QueryBuilders.matchQuery("dname", "开"),
            true
    ).innerHit(new InnerHitBuilder());
    SearchQuery searchQuery = new NativeSearchQueryBuilder()
            .withQuery(qb)
            .build();
    List<User> depts = elasticsearchTemplate.query(searchQuery, searchResponse -> {
        SearchHits hits = searchResponse.getHits();
        List<User> list = new ArrayList<>();
        Arrays.stream(hits.getHits()).forEach(h -> {
            Map<String, Object> source = h.getSource();
            SearchHits innerHitsMap = h.getInnerHits().get("dept");
            List<Dept> users = Arrays.stream(innerHitsMap.getHits()).map(innerH -> {
                Map<String, Object> innerSource = innerH.getSource();
                return new Dept(innerSource.get("dname").toString());
            }).collect(Collectors.toList());
            list.add(new User(source.get("uname").toString(), Integer.valueOf(source.get("age").toString()), users));
        });
        return list;
    });
    System.out.println(depts);
}

children 聚合
parent-child 支持 children 聚合,parent 聚合不支持。
按员工名称分组,年龄的和
GET company/user/_search
{
“size”: 0,
“aggs”: {
“name”: {
“terms”: {
“field”: “uname.keyword”,
“size”: 2
},
“aggs”: {
“sum”: {
“sum”: {
“field”: “age”
}
}
}
}
}
}

@Test
public void test3() {
    TermsAggregationBuilder tb = AggregationBuilders.terms("name").field("uname.keyword");
    SumAggregationBuilder sumAggregationBuilder = AggregationBuilders.sum("sum").field("age");
    tb.subAggregation(sumAggregationBuilder);
    SearchQuery searchQuery = new NativeSearchQueryBuilder()
            .addAggregation(tb)
            .build();
    Aggregations aggregations = elasticsearchTemplate.query(searchQuery, searchResponse -> {
        return searchResponse.getAggregations();
    });
    Terms terms = aggregations.get("name");
    if (terms.getBuckets().size() > 0) {
        for (Terms.Bucket bucket : terms.getBuckets()) {
            long ss = bucket.getDocCount();
            Sum sum = (Sum) bucket.getAggregations().asMap().get("sum");
            System.out.println(ss + "   " + sum);
        }
    }
}

parent-child 关系
关联关系,可以为两个完全分开的文档,可以将一种文档类型以一对多的关系关联到另一个上

优点:

1.parent文档的更新不需要重新为子文档重建索引

2.对子文档进行添加,更改,删除操作室不会影响父文档或者其他子文档

3.子文档可以被当做查询请求的结果返回

Elasticsearch 维护了一个父文档和子文档的映射关系,得益于这个映射,父-子文档关联查询操作非常快。但是这个映射也对父-子文档关系有个限制条件:父文档和其所有子文档,都必须要存储在同一个分片中。

parent-child映射
为了建立parent-child关系,需要在索引创建的时候指定父文档

建立索引
PUT /company
{
“mappings”: {
“dept”: {},
“user”: {
“_parent”: {
“type”: “dept”
}
}
}
}  
通过 child 找到 parents
查询child返回的是parents文档

查询child uname为 "里斯"的员工 部门
GET company/dept/_search
{
“query”: {
“has_child”: {
“type”: “user”,
“query”: {
“match”: {
“uname”:“里斯”
}
},“inner_hits”:{} //inner_hits可以将子文档带出 默认只查3条 可以自己设置 size,from
} } }

has_child 查询可以匹配多个 child 文档,每个都有相应的相关分数。这些分数如何化归为针对 parent 文档的单独分数取决于 score_mode 参数。默认设置是 none,这会忽视 child 分数并给 parents 分配了 1.0 的分值,不过这里也可以使用 avg,min,max 和 sum

开发部的下的员工王五评分更高,会更好匹配

GET company/dept/_search
{
“query”: {
   “has_child”: {
   “type”: “user”,
“score_mode”: “max”, //默认为none 此时明显快于其他模式,因为不需要计算每个child文档的分值,只有在乎分值的时候才需要设置
“query”: {
“match”: {
“uname”:“王五”
    }
  },“inner_hits”:{}
}}}

不带子级查询
@Test
public void test1(){
QueryBuilder qb = JoinQueryBuilders.hasChildQuery(
“user”, //要查询的子类型
QueryBuilders.matchQuery(“uname”,“张”),
ScoreMode.None
);
SearchQuery searchQuery = new NativeSearchQueryBuilder()
.withQuery(qb)
.build();
List depts =elasticsearchTemplate.queryForList(searchQuery,Dept.class);
System.out.println(depts);
}

带子级查询
@Test
public void test1(){
QueryBuilder qb = JoinQueryBuilders.hasChildQuery(
“user”, //要查询的子类型
QueryBuilders.matchQuery(“uname”,“张”),
ScoreMode.None
).innerHit(new InnerHitBuilder);
SearchQuery searchQuery = new NativeSearchQueryBuilder()
.withQuery(qb)
.build();
List depts= elasticsearchTemplate.query(searchQuery, searchResponse -> {
SearchHits hits = searchResponse.getHits();
List list = new ArrayList<>();
Arrays.stream(hits.getHits()).forEach(h -> {
Map<String, Object> source = h.getSource();
SearchHits innerHitsMap=h.getInnerHits().get(“user”);//获取子级数据
List users=Arrays.stream(innerHitsMap.getHits()).map(innerH -> {
Map<String, Object> innerSource = innerH.getSource();
return new User(innerSource.get(“uname”).toString(),Integer.valueOf(innerSource.get(“age”).toString()));
}).collect(Collectors.toList());
list.add(new Dept(source.get(“dname”).toString(),users));
});
return list;
});
System.out.println(depts);
}

min_children 和 max_children
has_child 查询和过滤器都接受 min_children 和 max_children 参数,仅当匹配 children 的数量在指定的范围内会返回 parent 文档。

查询至少有三个员工的部门

GET company/dept/_search
{
“query”: {
“has_child”: {
“type”: “user”,
“min_children”: 4,
“max_children”:10,
“query”: {
“match_all”: {}
}
}
}
}
@Test
public void test1() {
QueryBuilder qb = JoinQueryBuilders.hasChildQuery(
“user”,
QueryBuilders.matchAllQuery(),
ScoreMode.None
).minMaxChildren(4,10);
SearchQuery searchQuery = new NativeSearchQueryBuilder()
.withQuery(qb)
.build();
List depts = elasticsearchTemplate.queryForList(searchQuery, Dept.class);
}

通过 parents 找到 child
parent-child 文档本身是独立的,每个可以独立地进行查询。has_child 查询允许我们返回基于在其 children 的数据上 parents 文档,has_parent 查询则是基于 parents 的数据返回 children。
has_children 查询也支持 score_mode,但是仅仅会接受两个设置:none(默认)和 score.

查询部门名称有"开"的员工
GET company/user/_search
{
“query”: {
“has_parent”: {
“parent_type”: “dept”,
“query”: {
“match”: {
“dname”:“开”
}
}
}
}
}
不带父级
@Test
public void test2() {
QueryBuilder qb = JoinQueryBuilders.hasParentQuery(
“dept”,
QueryBuilders.matchQuery(“dname”, “开”),
true
);
SearchQuery searchQuery = new NativeSearchQueryBuilder()
.withQuery(qb)
.build();
List depts =elasticsearchTemplate.queryForList(searchQuery,User.class);
System.out.println(depts);
}

带父级

@Test
public void test2() {
    QueryBuilder qb = JoinQueryBuilders.hasParentQuery(
            "dept",
            QueryBuilders.matchQuery("dname", "开"),
            true
    ).innerHit(new InnerHitBuilder());
    SearchQuery searchQuery = new NativeSearchQueryBuilder()
            .withQuery(qb)
            .build();
    List<User> depts = elasticsearchTemplate.query(searchQuery, searchResponse -> {
        SearchHits hits = searchResponse.getHits();
        List<User> list = new ArrayList<>();
        Arrays.stream(hits.getHits()).forEach(h -> {
            Map<String, Object> source = h.getSource();
            SearchHits innerHitsMap = h.getInnerHits().get("dept");
            List<Dept> users = Arrays.stream(innerHitsMap.getHits()).map(innerH -> {
                Map<String, Object> innerSource = innerH.getSource();
                return new Dept(innerSource.get("dname").toString());
            }).collect(Collectors.toList());
            list.add(new User(source.get("uname").toString(), Integer.valueOf(source.get("age").toString()), users));
        });
        return list;
    });
    System.out.println(depts);
}

children 聚合
parent-child 支持 children 聚合,parent 聚合不支持。
按员工名称分组,年龄的和
GET company/user/_search
{
“size”: 0,
“aggs”: {
“name”: {
“terms”: {
“field”: “uname.keyword”,
“size”: 2
},
“aggs”: {
“sum”: {
“sum”: {
“field”: “age”
}
}
}
}
}
}

@Test
public void test3() {
    TermsAggregationBuilder tb = AggregationBuilders.terms("name").field("uname.keyword");
    SumAggregationBuilder sumAggregationBuilder = AggregationBuilders.sum("sum").field("age");
    tb.subAggregation(sumAggregationBuilder);
    SearchQuery searchQuery = new NativeSearchQueryBuilder()
            .addAggregation(tb)
            .build();
    Aggregations aggregations = elasticsearchTemplate.query(searchQuery, searchResponse -> {
        return searchResponse.getAggregations();
    });
    Terms terms = aggregations.get("name");
    if (terms.getBuckets().size() > 0) {
        for (Terms.Bucket bucket : terms.getBuckets()) {
            long ss = bucket.getDocCount();
            Sum sum = (Sum) bucket.getAggregations().asMap().get("sum");
            System.out.println(ss + "   " + sum);
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值