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); } } }