ES 基礎用法

LZC發表於2020-05-07
# 建立索引 
# "dynamic": "strict"====》不允許動態新增欄位,如果遇到新的欄位,就丟擲異常。
# "dynamic": "true"===》允許動態新增新的欄位。這是預設值
# "dynamic": "false"===》忽略新的欄位。在原有的對映基礎上,當有新的欄位時,不會主動的新增新的對映關係,只作為查詢結果出現在查詢中。
PUT my_blogs
{
  "mappings": {
    "dynamic": "strict",
    "properties": {
      "author": {
        "type": "keyword"
      },
      "title": {
        "type": "keyword"
      },
      "content": {
        "type": "text"
      },
      "views": {
        "type": "integer"
      },
      "created": {
        "type": "date",
         "format":"yyyy-MM-dd HH:mm:ss"
      }
    }
  }
}

keyword與text的區別:text會被分詞;

date型別的預設格式為:”strict_date_optional_time||epoch_millis”,文件

GET my_blogs/_mapping
DELETE /test_blogs
# 指定文件id為blog1,如果未指定則會自動生成
# 新增
POST my_blogs/_doc/blog1
{
  "author": "lzc",
  "title":"Learning ElasticSearch",
  "content":"學習ElasticSearch技術",
  "views": 0,
   "created": "2020-05-06 08:44:00"
}

POST my_blogs/_doc/blog2
{
  "author": "lzc",
  "title":"Learning Java",
  "content":"學習java技術",
  "views": 0,
   "created": "2020-05-06 08:44:00"
}

POST my_blogs/_doc/
{
  "author": "lzc",
  "title":"Learning RocketMQ",
  "content":"學習RocketMQ技術",
  "views": 0,
   "created": "2020-05-06 08:56:00"
}

# 查詢所有
GET /my_blogs/_search
{
  "query": {
    "match_all": {}
  }
}

term、terms

term查詢、terms查詢會去倒排索引中尋找確切的term,它並不知道分詞器的存在,這種查詢適合keyword、numeric、date等明確值的。

# term查詢:查詢 author 欄位裡有某個關鍵詞的文件
GET /my_blogs/_search
{
  "query": {
    "term": {
      "author": "lzc"
    }
  }
}
# terms 查詢:查詢 author 欄位裡有多個關鍵詞的文件
GET /my_blogs/_search
{
  "query": {
    "terms": {
      "author": [
        "zhangsan",
        "lzc"
      ]
    }
  }
}

match、multi_match

match查詢知道分詞器的存在,會對field進行分詞操作,然後再查詢

# 它和term區別可以理解為term是精確查詢,match是模糊查詢;
# match會對"學習RocketMQ"分詞,分詞規則跟分詞器有關,可能會被分為"學習""RocketMQ"
# term 可以認為這是一個單詞
GET /my_blogs/_search
{
  "query": {
    "match": {
      "content": "學習RocketMQ"
    }
  }
}

# 查詢多個欄位
GET /my_blogs/_search
{
  "query": {
    "multi_match": {
      "query": "RocketMQ",
      "fields": ["content","xxxx"]
    }
  }
}

_source

指定需要返回的欄位

# 只返回author、title欄位
GET /my_blogs/_search
{
  "_source": ["author","title"], 
  "query": {
    "match_all": {}
  }
}

Bool查詢

must

返回的文件必須滿足must子句的條件,並且參與計算分值。

# term查詢:查詢 author 欄位裡有某個關鍵詞的文件
GET /my_blogs/_search
{
  "query": {
    "bool": {
      "must": [
        {"term":{"author":"lzc"}}
      ]
    }
  }
}
# 匹配多個欄位
GET /my_blogs/_search
{
  "query": {
    "bool": {
      "must": [
        {"term":{"title":"java"}},
        {"term":{"author":"zhangsan"}}
      ]
    }
  }
}

# terms 查詢:查詢某個欄位裡有多個關鍵詞的文件
GET /my_blogs/_search
{
  "query": {
    "bool": {
      "must": [
        {"terms":{"author":["lzc","zhangsan"]}}
      ]
    }
  }
}

must_not

返回的文件必須不滿足must_not定義的條件。

GET /my_blogs/_search
{
  "query": {
    "bool": {
      "must_not": [
        {"term":{"author":"zhangsan"}}
      ]
    }
  }
}

should

返回的文件可能滿足should子句的條件。在一個Bool查詢中,如果沒有must或者filter,有一個或者多個should子句,那麼只要滿足一個就可以返回。minimum_should_match引數定義了至少滿足幾個子句。

# 相當於or語句
GET /my_blogs/_search
{
  "query": {
    "bool": {
      "should": [
        {"term":{"author":"lzc"}},
        {"term":{"author":"zhangsan"}}
      ]
    }
  }
}

filter

返回的文件必須滿足filter子句的條件。但是不會像Must一樣,參與計算分值。

# 類似於and語句
GET /my_blogs/_search
{
  "query": {
    "bool": {
      "filter": [
        {"term":{"title":"java"}},
        {"term":{"author":"zhangsan"}}
      ]
    }
  }
}

exists

# 查詢欄位為author的值不為null或者不為[]
GET /my_blogs/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "exists": {
            "field": "author"
          }
        }
      ]
    }
  }
}

高亮查詢

單欄位查詢

GET /my_blogs/_search
{
  "query": {
    "match": {
      "content": "RocketMQ"
    }
  },
  "highlight": {
    "pre_tags": [
      "<tag1>"
    ],
    "post_tags": [
      "</tag1>"
    ],
    "fields": {
      "content": {}
    }
  }
}

查詢結果:

{
    "_index" : "my_blogs",
    "_type" : "_doc",
    "_id" : "Bfh853EBC1bB8l9gL99x",
    "_score" : 1.092264,
    "_source" : {
        "author" : "lzc",
        "title" : "Learning RocketMQ",
        "content" : "學習RocketMQ技術",
        "views" : 0,
        "created" : "2020-05-06 08:56:00"
    },
    "highlight" : {
        "content" : [
            "學習<tag1>RocketMQ</tag1>技術"
        ]
    }
}

多欄位查詢

GET /my_blogs/_search
{
    "query" : {
        "multi_match": { 
          "query": "RocketMQ" ,
          "fields" : [ "content", "title" ]
        }
    },
    "highlight" : {
        "pre_tags" : ["<tag1>"],
        "post_tags" : ["</tag1>"],
        "fields" : {
            "title": {},
            "content" : {}
        }
    }
}
# 透過id刪除
DELETE my_blogs/_doc/blog9

# 透過條件刪除
POST my_blogs/_delete_by_query
{
  "query": {
    "bool": {
      "must": [
        {
          "term": {
            "author": {
              "value": "zhangsan"
            }
          }
        }
      ]
    }
  }
}
# 根據id修改
POST my_blogs/_update/blog1
{
  "doc": {
    "author":"lizc"
  }
}

# doc_as_upsert 設定 true, 如果id不存在就新增
POST my_blogs/_update/blog2
{
  "doc": {
    "author":"lizhencheng",
    "title":"好好學習"
  },
  "doc_as_upsert" : true
}

ElasticSerch 的連線查詢有兩種方式實現

  • nested
  • parent和child關聯查詢

nested

建立索引

# 假設用來儲存部落格與部落格評論
PUT my_blogs
{
  "mappings": {
    "properties": {
      "author": {
        "type": "keyword"
      },
      "title": {
        "type": "keyword"
      },
      "content": {
        "type": "text"
      },
      "views": {
        "type": "integer"
      },
      "created": {
        "type": "date",
         "format":"yyyy-MM-dd HH:mm:ss"
      },
      "comment": {
        "type": "nested",
        "properties": {
          "commentName":{
            "type":"keyword"
          },
          "commentContent":{
            "type":"keyword"
          },
          "commentCreated":{
            "type": "date",
            "format":"yyyy-MM-dd HH:mm:ss"
          }
        }
      }
    }
  }
}

新增資料

POST my_blogs/_doc/blog1
{
  "author": "lzc",
  "title":"Learning ElasticSearch",
  "content":"學習ElasticSearch技術",
  "views": 0,
  "created": "2020-05-06 08:00:00",
  "comment":[
    {
      "commentName":"zhangsan",
      "commentContent":"commentContent1",
      "commentCreated":"2020-05-07 08:00:00"
    },
    {
      "commentName":"lisi",
      "commentContent":"commentContent2",
      "commentCreated":"2020-05-07 10:00:00"
    },
    {
      "commentName":"wangwu",
      "commentContent":"commentContent3",
      "commentCreated":"2020-05-08 08:00:00"
    }
  ]
}

POST my_blogs/_doc/blog2
{
  "author": "xiaoli",
  "title":"Learning Java",
  "content":"學習Java技術",
  "views": 0,
  "created": "2020-05-07 08:00:00",
  "comment":[
    {
      "commentName":"zhangsan",
      "commentContent":"commentContent4",
      "commentCreated":"2020-05-08 08:00:00"
    }
  ]
}

查詢

# 查詢 author = lzc 並且 commentName = zhangsan
GET /my_blogs/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "term": {
            "author": {
              "value": "lzc"
            }
          }
        },
        {
          "nested": {
            "path": "comment",
            "query": {
              "bool": {
                "must": [
                  {
                    "term": {
                      "comment.commentName": {
                        "value": "zhangsan"
                      }
                    }
                  }
                ]
              }
            }
          }
        }
      ]
    }
  }
}

parent-child

建立索引

PUT my_blogs
{
  "mappings": {
    "properties": {
      "blog_comment_relation": {
        "type": "join",
        "relations": {
          "blog": [
            "comment"
          ]
        }
      },
      "author": {
        "type": "keyword"
      },
      "title": {
        "type": "keyword"
      },
      "content": {
        "type": "text"
      },
      "views": {
        "type": "integer"
      },
      "created": {
        "type": "date",
        "format": "yyyy-MM-dd HH:mm:ss"
      },
      "commentName": {
        "type": "keyword"
      },
      "commentContent": {
        "type": "keyword"
      },
      "commentCreated": {
        "type": "date",
        "format": "yyyy-MM-dd HH:mm:ss"
      }
    }
  }
}

新增資料

# 新增父節點
POST my_blogs/_doc/blog1
{
  "author": "lzc",
  "title":"Learning ElasticSearch",
  "content":"學習ElasticSearch技術",
  "views": 0,
   "created": "2020-05-06 08:55:00",
   "blog_comment_relation": {
     "name":"blog"
   }
}
POST my_blogs/_doc/blog2
{
  "author": "zhangsan",
  "title":"Learning Java",
  "content":"學習java技術",
  "views": 0,
   "created": "2020-05-06 08:58:00",
   "blog_comment_relation": {
     "name":"blog"
   }
}

# 新增子節點記錄
# 需要指定routing為父節點的id,使得他們處於同一個分片。無論是子節點還是孫子節點,routing都是指向父節點id
# parent 指定父元素id
PUT my_blogs/_doc/comment1?routing=blog1
{
  "commentUserName": "lili",
  "commentContent": "I am learning ElasticSearch",
  "commentCreated":"2020-05-07 08:00:00",
  "blog_comment_relation": {
    "name": "comment",
    "parent": "blog1"
  }
}
PUT my_blogs/_doc/comment2?routing=blog1
{
  "commentUserName": "jack",
  "commentContent": "I am learning Java",
  "commentCreated":"2020-05-07 09:00:00",
  "blog_comment_relation": {
    "name": "comment",
    "parent": "blog1"
  }
}
PUT my_blogs/_doc/comment3?routing=blog2
{
  "commentUserName": "jackma",
  "commentContent": "so easy",
  "commentCreated":"2020-05-08 09:00:00",
  "blog_comment_relation": {
    "name": "comment",
    "parent": "blog2"
  }
}

查詢

# Has Child查詢,返回父文件
# "inner_hits": {} ==> 加了這個會將子文件一起查詢出來, 預設只會返回三條資料
# "inner_hits"裡面有四個屬性
# from: 返回結果的偏移量,"from": 2 ==> 從索引為2的位置返回size條資料
# size: inner_hits返回的最大數量。預設值為3。
# size: inner_hits => 預設情況最大可設定為100,可透過index.max_inner_result_window來設定
# sort: 如何對內部命中進行排序,如 "sort": [{"commentCreated":"desc"}]
# name: 用於響應中特定內部命中定義的名稱
GET /my_blogs/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "term": {
            "_id": {
              "value": "blog1"
            }
          }
        },
        {
          "has_child": {
            "type": "comment",
            "query": {
              "bool": {
                "must": [
                  {
                    "match_all": {}
                  }
                ]
              }
            },
            "inner_hits": {
              "size": 10,
              "sort": [{"commentCreated":"desc"}]
            }
          }
        }
      ]
    }
  }
}

# Has Parent查詢,返回子文件
# "inner_hits": {} ==> 加了這個會將父文件一起查詢出來
POST /my_blogs/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "has_parent": {
            "parent_type": "blog",
            "query": {
              "match": {
                "_id": "blog1"
              }
            },
            "inner_hits": {}
          }
        }
      ]
    }
  }
}

比較

nested parent/child
優點 讀取效能高 父子文件可以獨立更新
缺點 每次更新需要更新整個文件 關聯關係,需要額外的記憶體,查詢效率相對較差
場景 頻繁查詢 頻繁更新
# size為0則不會返回文件資訊,只會返回聚合資訊
GET /my_blogs/_search
{
    "size" : 0,
    "aggs" : { 
        "author_blog_counts" : { 
            "terms" : { 
              "field" : "author"
            }
        }
    }
}

返回結果

{
  "took" : 6,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 3,
      "relation" : "eq"
    },
    "max_score" : null,
    "hits" : [ ]
  },
  "aggregations" : {
    "author_blog_counts" : {
      "doc_count_error_upper_bound" : 0,
      "sum_other_doc_count" : 0,
      "buckets" : [
        {
          "key" : "lzc",
          "doc_count" : 2
        },
        {
          "key" : "zhangsan",
          "doc_count" : 1
        }
      ]
    }
  }
}

將文件從一個索引複製到另一個索引。

# 將my_blogs中的資料複製到my_blogs_copy
POST _reindex
{
  "source": {
    "index": "my_blogs"
  },
  "dest": {
    "index": "my_blogs_copy"
  }
}

# 設定數量, 預設情況, size為1000
POST _reindex
{
  "size": 1, 
  "source": {
    "index": "my_blogs"
  },
  "dest": {
    "index": "my_blogs_copy"
  }
}

# 根據條件進行復制
POST _reindex
{
  "size": 1000, 
  "source": {
    "index": "my_blogs",
    "query": {
      "bool": {
        "must": [
          {
            "term": {
              "author": {
                "value": "lzc"
              }
            }
          }
        ]
      }
    }
  },
  "dest": {
    "index": "my_blogs_copy"
  }
}

www.elastic.co/guide/en/elasticsea...

scroll

www.elastic.co/guide/en/elasticsea...

www.elastic.co/guide/en/elasticsea...

配置

<dependency>
    <groupId>org.elasticsearch</groupId>
    <artifactId>elasticsearch</artifactId>
    <version>7.1.1</version>
</dependency>
<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-client</artifactId>
    <version>7.1.1</version>
</dependency>
<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-high-level-client</artifactId>
    <version>7.1.1</version>
</dependency>

與SpringBoot進行整合

elasticsearch.ip=localhost:9200

配置類

@Configuration
public class ESConfig {
    /**
     * 超時時間設為5分鐘
     */
    private static final int TIME_OUT = 5 * 60 * 1000;
    private static final int ADDRESS_LENGTH = 2;
    private static final String HTTP_SCHEME = "http";

    @Value("${elasticsearch.ip}")
    String[] ipAddress;

    @Bean
    public RestClientBuilder restClientBuilder() {
        HttpHost[] hosts = Arrays.stream(ipAddress)
                .map(this::makeHttpHost)
                .filter(Objects::nonNull)
                .toArray(HttpHost[]::new);
        return RestClient.builder(hosts);
    }

    @Bean(name = "restHighLevelClient")
    public RestHighLevelClient highLevelClient(@Autowired RestClientBuilder restClientBuilder) {
        restClientBuilder.setRequestConfigCallback(
                new RestClientBuilder.RequestConfigCallback() {
                    @Override
                    public RequestConfig.Builder customizeRequestConfig(
                            RequestConfig.Builder requestConfigBuilder) {
                        return requestConfigBuilder.setSocketTimeout(TIME_OUT);
                    }
                });
        return new RestHighLevelClient(restClientBuilder);
    }

    private HttpHost makeHttpHost(String s) {
        assert !StringUtils.isEmpty(s);
        String[] address = s.split(":");
        if (address.length == ADDRESS_LENGTH) {
            String ip = address[0];
            int port = Integer.parseInt(address[1]);
            System.err.println(ip + "+" + port);
            return new HttpHost(ip, port, HTTP_SCHEME);
        } else {
            return null;
        }
    }
}

建立索引

PUT my_blogs
{
  "mappings": {
    "properties": {
      "blog_comment_relation": {
        "type": "join",
        "relations": {
          "blog": [
            "comment"
          ]
        }
      },
      "author": {
        "type": "keyword"
      },
      "title": {
        "type": "keyword"
      },
      "content": {
        "type": "text"
      },
      "views": {
        "type": "integer"
      },
      "created": {
        "type": "date",
        "format": "yyyy-MM-dd HH:mm:ss"
      },
      "commentName": {
        "type": "keyword"
      },
      "commentContent": {
        "type": "keyword"
      },
      "commentCreated": {
        "type": "date",
        "format": "yyyy-MM-dd HH:mm:ss"
      }
    }
  }
}

編寫程式碼

官方文件:www.elastic.co/guide/en/elasticsea...

新增

www.elastic.co/guide/en/elasticsea...

@Autowired
private RestHighLevelClient restHighLevelClient;

@Test
public void insert() throws IOException {
    Map<String, Object> blog = new HashMap();
    blog.put("author","lzc");
    blog.put("title","Learning ElasticSearch12");
    blog.put("content","學習ElasticSearch技術");
    Map<String, Object> blogCommentRelation1 = new HashMap();
    blogCommentRelation1.put("name","blog");
    blog.put("blog_comment_relation",blogCommentRelation1);
    IndexResponse indexResponse1 = insert("my_blogs", "blog1", null, blog);

    Map<String, Object> comment1 = new HashMap();
    comment1.put("commentName","commentName1");
    comment1.put("commentContent","commentContent1");
    Map<String, Object> blogCommentRelation2 = new HashMap();
    blogCommentRelation2.put("name","comment");
    blogCommentRelation2.put("parent","blog1");
    comment1.put("blog_comment_relation",blogCommentRelation2);
    IndexResponse indexResponse2 = insert("my_blogs", "comment1", "blog1", comment1);

    Map<String, Object> comment2 = new HashMap();
    comment2.put("commentName","commentName2");
    comment2.put("commentContent","commentContent2");
    Map<String, Object> blogCommentRelation3 = new HashMap();
    blogCommentRelation3.put("name","comment");
    blogCommentRelation3.put("parent","blog1");
    comment1.put("blog_comment_relation",blogCommentRelation2);
    IndexResponse indexResponse3 = insert("my_blogs", "comment2", "blog1", comment2);
}

/**
* @param index 索引名
* @param id 主鍵id, 如果傳入null則會自動生成
* @param routing
* @param doc 文件內容
*/
public IndexResponse insert(String index, String id, String routing, Map<String, Object> doc) throws IOException {
    IndexRequest indexRequest = new IndexRequest(index).source(doc);
    if (id != null) {
        indexRequest = indexRequest.id(id);
    }
    if (routing != null) {
        indexRequest = indexRequest.routing(routing);
    }
    IndexResponse indexResponse = restHighLevelClient.index(indexRequest, RequestOptions.DEFAULT);
    return indexResponse;
}

新增or修改

www.elastic.co/guide/en/elasticsea...

@Autowired
private RestHighLevelClient restHighLevelClient;


@Test
public void updateOrInsert() throws IOException {
    Map<String, Object> blog = new HashMap();
    blog.put("author","lzc");
    blog.put("title","Learning ElasticSearch");
    blog.put("content","學習ElasticSearch技術");
    Map<String, Object> blogCommentRelation1 = new HashMap();
    blogCommentRelation1.put("name","blog");
    blog.put("blog_comment_relation",blogCommentRelation1);
    UpdateResponse updateResponse = updateOrInsert("my_blogs", "blog2", null, blog);

    Map<String, Object> comment = new HashMap();
    comment.put("commentName","commentName3");
    comment.put("commentContent","commentContent3");
    Map<String, Object> blogCommentRelation2 = new HashMap();
    blogCommentRelation2.put("name","comment");
    blogCommentRelation2.put("parent","blog2");
    comment.put("blog_comment_relation",blogCommentRelation2);
    IndexResponse indexResponse2 = insert("my_blogs", "comment2", "blog2", comment);
}


/**
* 新增或修改
* @param index 文件索引
* @param id 文件id
* @param routing
* @param doc 文件欄位
*/
public UpdateResponse updateOrInsert(String index, String id, String routing, Map<String, Object> doc) throws IOException {
    UpdateRequest request = new UpdateRequest(index,id);
    if (routing != null) {
        request = request.routing(routing);
    }
    request.doc(doc).docAsUpsert(true).retryOnConflict(5);
    return restHighLevelClient.update(request, RequestOptions.DEFAULT);
}

透過id查詢

www.elastic.co/guide/en/elasticsea...

@Autowired
private RestHighLevelClient restHighLevelClient;

@Test
public void getDocumentById() throws IOException {
    Map<String, Object> blogMap = getDocumentById("my_blogs", "comment1");
}

/**
* @param index 索引名
* @param id 文件id
*/
public Map<String, Object> getDocumentById(String index, String id) throws IOException {
    GetRequest getRequest = new GetRequest(index, id);
    GetResponse getResponse = restHighLevelClient.get(getRequest, RequestOptions.DEFAULT);
    if (getResponse.isExists()) {
        return getResponse.getSourceAsMap();
    }
    return null;
}

透過id刪除

www.elastic.co/guide/en/elasticsea...

@Autowired
private RestHighLevelClient restHighLevelClient;

@Test
public void deleteDocumentById() throws IOException {
    deleteDocumentById("my_blogs", "comment1");
}

/**
* @param index 索引名
* @param id 文件id
*/
public DeleteResponse deleteDocumentById(String index, String id) throws IOException {
    DeleteRequest deleteRequest = new DeleteRequest(index, id);
    DeleteResponse deleteResponse = restHighLevelClient.delete(deleteRequest, RequestOptions.DEFAULT);
    return deleteResponse;
}

條件查詢

www.elastic.co/guide/en/elasticsea...

@Test
public void searchRequest() throws IOException {
    SearchRequest searchRequest = new SearchRequest("my_blogs");
    SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
    QueryBuilder queryBuilder = QueryBuilders.matchAllQuery(); // 構造查詢條件
    searchSourceBuilder.query(queryBuilder);
    searchRequest.source(searchSourceBuilder);
    SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
    System.out.println();
}

QueryBuilders.matchAllQuery()相當於如下查詢

GET /my_blogs/_search
{
  "query": {
    "match_all": {}
  }
}

term查詢

// 構造查詢條件
BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery(); 
// 構造term
queryBuilder.must(QueryBuilders.termQuery("title", "Learning ElasticSearch")); 

相當於如下查詢

GET /my_blogs/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "term": {
            "title": {
              "value": "Learning ElasticSearch"
            }
          }
        }
      ]
    }
  }
}

has_child

@Test
public void searchRequest() throws IOException {
    SearchRequest searchRequest = new SearchRequest("my_blogs");
    SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();

    BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery(); // 構造查詢條件
    QueryBuilder childQueryBuilder = JoinQueryBuilders
        .hasChildQuery(
        "comment",
        QueryBuilders.matchAllQuery(),
        ScoreMode.None).innerHit(new InnerHitBuilder().setSize(100));
    queryBuilder.must(childQueryBuilder);

    searchSourceBuilder.query(queryBuilder);
    searchRequest.source(searchSourceBuilder);
    SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);

    // 下面是對結果進行解析
    SearchHits searchHits = searchResponse.getHits();
    SearchHit[] hits = searchHits.getHits();
    for (SearchHit hit : hits) {
        System.out.println("index = " + hit.getIndex());
        Map<String, Object> blogAsMap = hit.getSourceAsMap();
        System.out.println("id:" + hit.getId());
        for (Map.Entry<String, Object> kv : blogAsMap.entrySet()) {
            System.out.println(kv.getKey() + ":" + kv.getValue().toString());
        }
        System.out.println("[InnerHits]");
        Map<String, SearchHits> innerHits = hit.getInnerHits();
        for (Map.Entry<String, SearchHits> commentHit : innerHits.entrySet()) {
            String key = commentHit.getKey(); // 這個例子裡面, key = comment,因為InnerHits裡面只有comment型別的
            for (SearchHit commentSource : commentHit.getValue().getHits()) {
                if (commentSource != null) {
                    System.out.println("index = " + commentSource.getIndex());
                    System.out.println("id = " + commentSource.getId());
                    Map<String, Object> commentAsMap = commentSource.getSourceAsMap();
                    for (Map.Entry<String, Object> kv2 : commentAsMap.entrySet()) {
                        System.out.println(kv2.getKey() + ":" + kv2.getValue().toString());
                    }
                }
            }
        }
        System.out.println("------------------------------------------------------");
    }
}

相當於如下查詢語句

GET /my_blogs/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "has_child": {
            "query": {
              "match_all": {}
            },
            "type": "comment",
            "score_mode": "none",
            "inner_hits": {
              "size": 100
            }
          }
        }
      ]
    }
  }
}

聚合

@Test
public void aggs() throws IOException {
    SearchRequest searchRequest = new SearchRequest("my_blogs");
    SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
    QueryBuilder queryBuilder = QueryBuilders.matchAllQuery(); // 構造查詢條件
    searchSourceBuilder.query(queryBuilder);
    // 設定為0後只返回聚合結果
    searchSourceBuilder.size(0);

    // 設定聚合欄位
    List<String> aggKeys = new ArrayList<>();
    aggKeys.add("author");
    aggKeys.add("commentName");
    aggKeys.forEach(aggKey -> {
        searchSourceBuilder.aggregation(
            AggregationBuilders.terms(aggKey).field(aggKey)
            .size(10) // 針對這個欄位的聚合結果, 最多返回的個數
        );
    });

    searchRequest.source(searchSourceBuilder);
    SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);

    // 獲取聚合結果
    Aggregations aggregations = searchResponse.getAggregations();

    // 解析聚合結果
    aggKeys.forEach(aggKey -> {
        Terms terms = aggregations.get(aggKey);
        if (terms.getBuckets().size() > 0) {
            for (Terms.Bucket bucket : terms.getBuckets()) {
                String name = bucket.getKeyAsString(); // 聚合欄位
                long count = bucket.getDocCount();
                System.out.println(name + ":" + count);
            }
        }
    });
}

相當於如下查詢語句

GET /my_blogs/_search
{
  "size": 0,
  "query": {
    "match_all": {
      "boost": 1
    }
  },
  "aggregations": {
    "author": {
      "terms": {
        "field": "author",
        "size": 10,
        "order": [
          {
            "_count": "desc"
          },
          {
            "_key": "asc"
          }
        ]
      }
    },
    "commentName": {
      "terms": {
        "field": "commentName",
        "size": 10,
        "order": [
          {
            "_count": "desc"
          },
          {
            "_key": "asc"
          }
        ]
      }
    }
  }
}
本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章