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