父-子關系文檔
父-子關系文檔 在實質上類似於 nested model :允許將一個對象實體和另外一個對象實體關聯起來。 而這兩種類型的主要區別是:在 nested objects 文檔中,所有對象都是在同一個文檔中,而在父-子關系文檔中,父對象和子對象都是完全獨立的文檔。
父-子關系的主要作用是允許把一個 type 的文檔和另外一個 type 的文檔關聯起來,構成一對多的關系:一個父文檔可以對應多個子文檔 。與 nested objects 相比,父-子關系的主要優勢有:
更新父文檔時,不會重新索引子文檔。
創建,修改或刪除子文檔時,不會影響父文檔或其他子文檔。這一點在這種場景下尤其有用:子文檔數量較多,並且子文檔創建和修改的頻率高時。
子文檔可以作為搜索結果獨立返回。
Elasticsearch 維護了一個父文檔和子文檔的映射關系,得益於這個映射,父-子文檔關聯查詢操作非常快。但是這個映射也對父-子文檔關系有個限制條件:父文檔和其所有子文檔,都必須要存儲在同一個分片中。
我使用的是es5.6,es6.0版本以上一個索引對應一個type
1.在es中查詢父子級關系
創建父子級關系
PUT user_test { "mappings": { "dept": { "properties": { "dname":{ "type": "text" } } }, "user":{ "_parent": { "type": "dept" }, "properties": { "deptId":{ "type": "text" }, "username":{ "type": "text" }, "age":{ "type": "integer" } } } } }
創建了一個公司的索引 包括部門和用戶兩個類型
新增一個父級數據
POST company/dept/2 { "dname":"開發部" }
新增一個子級數據
POST company/user?parent=2 { "uid":"1", "uname":"王五", "age":10 }
以子級查
GET company/user/_search { "query": { "has_parent": { "parent_type": "dept", "query": { "term": { "dname":"開" } },"inner_hits":{} } } }
以父級查
GET company/dept/_search { "query": { "has_child": { "type": "user", "query": { "match": { "uname":"里斯" } },"inner_hits":{} } } }
用inner_hits則可以把父子文檔同時返回——既返回,不加inner_hits只返回一個type里的數據。inner_hits默認只查詢3條數據,可以自定義設置from 和size。
如果父子級同時有查詢條件,用bool作為復合查詢
GET company/dept/_search { "query": { "bool": { "must": [ { "term": { "dname": { "value": "開" } } },{ "has_child": { "type": "user", "query": { "match": { "uname":"張" } },"inner_hits":{} } } ] } } }
2.java項目中查詢
實體類
1.部門
@Document(indexName = "user_test",type = "dept") public class Department { @Id private String id; private String dname; }
2.用戶
@Document(indexName = "user_test",type = "user") public class User { @Id private String id; @Parent(type = "dept") private String deptId; private String uname; private Integer age; }
使用ElasticsearchTemplate
1查詢父級數據
QueryBuilder qb=JoinQueryBuilders.hasChildQuery("user",QueryBuilders.matchAllQuery(),ScoreMode.None);
SearchQuery searchQuery = new NativeSearchQueryBuilder()
.withIndices("user_test")
.withQuery(qb)
.build();
List<Department> depts= elasticsearchTemplate.queryForList(searchQuery,Department.class);
ScoreMode:評分模式min,max,sum,avg或none
2.查詢子級數據
QueryBuilder qb=JoinQueryBuilders.hasParentQuery("dept",QueryBuilders.matchQuery("dname","開"),false);
SearchQuery searchQuery = new NativeSearchQueryBuilder()
.withIndices("user_test")
.withQuery(qb)
.build();
List<User> users= elasticsearchTemplate.queryForList(searchQuery,User.class);
評分功能:這has_parent也有得分支持。默認值是false忽略父文檔的分數
官方參考:https://www.elastic.co/guide/en/elasticsearch/reference/5.6/query-dsl-has-parent-query.html
查詢父子級全部數據
QueryBuilder qb = JoinQueryBuilders.hasChildQuery( "user", //要查詢的子類型 QueryBuilders.matchQuery("uname.keyword","張三"), ScoreMode.None ).innerHit(new InnerHitBuilder()); SearchQuery searchQuery = new NativeSearchQueryBuilder() .withIndices("user_test") .withQuery(qb) .build(); List<DeptVO> depts= elasticsearchTemplate.query(searchQuery, searchResponse -> { SearchHits hits = searchResponse.getHits(); List<DeptVO> list = new ArrayList<>(); Arrays.stream(hits.getHits()).forEach(h -> { Map<String, Object> source = h.getSource(); SearchHits innerHitsMap=h.getInnerHits().get("user");//獲取子級數據 List<User> user1s=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 DeptVO(source.get("dname").toString(),user1s)); }); return list; });
JoinQueryBuilders.hasChildQuery().innerHit(new InnerHitBuilder())的.innerHit(new InnerHitBuilder())與es查詢一樣
