深入了解ElasticSearch的Nested数据类型

Posted Java鱼仔

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深入了解ElasticSearch的Nested数据类型相关的知识,希望对你有一定的参考价值。

如果你对ES不了解但是现在想知道他是干什么的,可以先查看我的其他几篇文档:

ElasticSearch究竟是个什么东西

通过官方文档高效学习ElasticSearch的JavaAPI实现!

如何在自己的项目中引入ElasticSearch搜索引擎?

ElasticSearch聚合查询Restful语法和JavaApi详解(基于ES7.6)

(一)ES如何存储对象

ElasticSearch中可以将数据以对象的方式存储并查询,但是ES底层的Lucene 没有内部对象的概念,因此如果通过默认的方式往ES中插入对象,ES会将对象层次结构扁平化为字段名称和值的简单列表。
比如下面这一段数据:

PUT my_index/_doc/1

  "group" : "fans",
  "user" : [ 
    
      "first" : "John",
      "last" :  "Smith"
    ,
    
      "first" : "Alice",
      "last" :  "White"
    
  ]

ES内部会将这份数据变成下面这个样子:


  "group" :        "fans",
  "user.first" : [ "alice", "john" ],
  "user.last" :  [ "smith", "white" ]

缺失了first和last之间的关联性。比如这个时候想查询一个first为John,last为White的人,理论上是没有这个人的,但是实际上名为fans的这个组还是被查出来了。

GET my_index/_search

  "query": 
    "bool": 
      "must": [
         "match":  "user.first": "John" ,
         "match":  "user.last":  "White" 
      ]
    
  

从结果可以看到,两条数据都被查询出来了。

(二)Nested类型

这个时候就需要用到nested,nested类型是object数据类型的特殊版本,它允许对象数组以一种可以相互独立查询的方式进行索引。

在Nested内部,每个对象索引其实是一个单独的隐藏文档,这意味着每个嵌套对象都可以独立于其他对象进行查询。

使用Nested需要先创建索引,依旧通过上边的这个例子

DELETE my_index

PUT my_index

  "mappings": 
    "properties": 
      "user": 
        "type": "nested" 
      
    
  


PUT my_index/_doc/1

  "group" : "fans",
  "user" : [
    
      "first" : "John",
      "last" :  "Smith",
      "age" : "23"
    ,
    
      "first" : "Alice",
      "last" :  "White",
      "age":"24"
    
  ]

首先创建my_index索引,设置user的类型为nested,接着在查询时,需要通过es的nested查询语句查询,使用同样的方式查询first为John,last为White的用户,这次的结果是不存在。因为通过nested存储的对象是具有关联性的。

GET my_index/_search

  "query": 
    "nested": 
      "path": "user",
      "query": 
        "bool": 
          "must": [
             "match":  "user.first": "John" ,
             "match":  "user.last":  "White"  
          ]
        
      
    
  

上边的DSL语句用Java API实现如下:

@Test
public void testNested() throws Exception
    //自己封装的一个获取RestHighLevelClient的类
    RestHighLevelClient client=ElasticSearchClient.getClient();
    SearchRequest request = new SearchRequest("my_index");
    SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
    BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
    boolQueryBuilder.must(QueryBuilders.matchQuery("user.first","John"));
    boolQueryBuilder.must(QueryBuilders.matchQuery("user.last","White"));
    searchSourceBuilder.query(QueryBuilders.nestedQuery("user",boolQueryBuilder, ScoreMode.None));
    request.source(searchSourceBuilder);
    SearchResponse search = client.search(request, RequestOptions.DEFAULT);
    SearchHit[] hits = search.getHits().getHits();
    for (int i = 0; i < hits.length; i++) 
        SearchHit hit = hits[i];
        System.out.println(hit.getSourceAsString());
    

(三)使用nested进行聚合查询

除了使用nested进行普通查询外,nested也支持聚合查询,同样是上面的例子,现在做一个对年龄聚合的操作:

GET my_index/_search

  "aggs": 
    "nestedAgg": 
      "nested": 
        "path": "user"
      ,
      "aggs": 
        "ageAgg": 
          "terms": 
            "field": "user.age.keyword",
            "size": 10
          
        
      
    
  

(五)nested中的inner_hits

查询nested对象时,只要查询条件符合这个nested对象里的某一个条件,整个nested对象都会被检索出来。比如上面这个例子中,我只想查询叫做John Smith的这个人,但是通过普通的query查询会把整条记录都查询出来,效果就是这样:

"hits" : [
      
        "_index" : "my_index",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 1.3862942,
        "_source" : 
          "group" : "fans",
          "user" : [
            
              "first" : "John",
              "last" : "Smith",
              "age" : "23"
            ,
            
              "first" : "Alice",
              "last" : "White",
              "age" : "24"
            
          ]
        
      
    ]

如果只想要nested中里的一个对象,就可以使用inner_hits。使用比较简单,只需要在查询语句之后加上inner_hits即可。

GET my_index/_search

  "query": 
    "nested": 
      "path": "user",
      "query": 
        "bool": 
          "must": [
             "match":  "user.first": "John" ,
             "match":  "user.last":  "Smith"  
          ]
        
      ,
      "inner_hits": 
    
  

查询结果里就会多出来一块数据,里面就只会展示具体的nested对象:

"inner_hits" : 
          "user" : 
            "hits" : 
              "total" : 
                "value" : 1,
                "relation" : "eq"
              ,
              "max_score" : 1.3862942,
              "hits" : [
                
                  "_index" : "my_index",
                  "_type" : "_doc",
                  "_id" : "1",
                  "_nested" : 
                    "field" : "user",
                    "offset" : 0
                  ,
                  "_score" : 1.3862942,
                  "_source" : 
                    "last" : "Smith",
                    "first" : "John",
                    "age" : "23"
                  
                
              ]
            
          
        

(六)nested的使用建议

nested可以很好地存储和查询对象类型数据,但是也不能滥用nested。每个nested对象都被索引为一个单独的文档,简单来讲就是如果一个索引里包含 100 个user对象,那么在实际底层将创建 101 个 Lucene 文档,是一个很大的消耗。

nested类型只应在特殊情况下使用,一个索引在创建的时候,nested类型的对象默认不能超过50个,可通过index.mapping.nested_fields.limit修改。

一个具体的文档中,nested类型中包含的嵌套对象的数量默认不能超过10000个,也就是说上面创建的user在一个文档里不能超过10000个,可通过index.mapping.nested_objects.limit修改。

以上是关于深入了解ElasticSearch的Nested数据类型的主要内容,如果未能解决你的问题,请参考以下文章

深入了解ElasticSearch的Nested数据类型

干货 | Elasticsearch Nested类型深入详解

Elasticsearch:从实例中学习 nested 数据类型的 CRUD 及搜索

Elasticsearch:从实例中学习 nested 数据类型的 CRUD 及搜索

[ElasticSearch]ES操作之嵌套查询(nested)与退出嵌套(reverse_nested)操作

转:elasticsearch nested嵌套查询