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<Dept> 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<Dept> depts= elasticsearchTemplate.query(searchQuery, searchResponse -> { SearchHits hits = searchResponse.getHits(); List<Dept> list = new ArrayList<>(); Arrays.stream(hits.getHits()).forEach(h -> { Map<String, Object> source = h.getSource(); SearchHits innerHitsMap=h.getInnerHits().get("user");//获取子级数据 List<User> 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<Dept> 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<User> 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); } } }