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
has_children
score_mode
none
score.
不带父级
@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);
}
}
}