參考:
es中索引對象包含數組子節點時, 查詢和聚合的統計需要注意的問題。
https://es.xiaoleilu.com/402_Nested/30_Nested_objects.html
嵌套對象
事實上在Elasticsearch中,創建丶刪除丶修改一個文檔是是原子性的,因此我們可以在一個文檔中儲存密切關聯的實體。
舉例來說,我們可以在一個文檔中儲存一筆訂單及其所有內容,或是儲存一個Blog文章及其所有回應,藉由傳遞一個comments陣列:

PUT /my_index/blogpost/1 { "title": "Nest eggs", "body": "Making your money work...", "tags": [ "cash", "shares" ], "comments": [ { "name": "John Smith", "comment": "Great article", "age": 28, "stars": 4, "date": "2014-09-01" }, { "name": "Alice White", "comment": "More like this please", "age": 31, "stars": 5, "date": "2014-10-22" } ] }
<1> 如果我們依靠動態映射,comments欄位會被自動建立為一個object欄位。
因為所有內容都在同一個文檔中,使搜尋時並不需要連接(join)blog文章與回應,因此搜尋表現更加優異。
問題在於以上的文檔可能會如下所示的匹配一個搜尋:

GET my_index/_search { "query": { "bool": { "must": [ { "match": { "comments.age": 31 } }, { "match": { "comments.name": "John Smith" } } ] } } }
Alice是31歲,而不是28歲!
造成跨對象配對的原因如同我們在對象陣列中所討論到,在於我們優美結構的JSON文檔在索引中被扁平化為下方的 鍵-值 形式:

{ "title": [ eggs, nest ], "body": [ making, money, work, your ], "tags": [ cash, shares ], "comments.name": [ alice, john, smith, white ], "comments.comment": [ article, great, like, more, please, this ], "comments.age": [ 28, 31 ], "comments.stars": [ 4, 5 ], "comments.date": [ 2014-09-01, 2014-10-22 ] }
Alice與31 以及 John與2014-09-01 之間的關聯已經無法挽回的消失了。 當object類型的欄位用於儲存單一對象是非常有用的。 從搜尋的角度來看,對於排序一個對象陣列來說關聯是不需要的東西。
這是嵌套對象被設計來解決的問題。 藉由映射commments欄位為nested類型而不是object類型, 每個嵌套對象會被索引為一個隱藏分割文檔,例如:

{ <1> "comments.name": [ john, smith ], "comments.comment": [ article, great ], "comments.age": [ 28 ], "comments.stars": [ 4 ], "comments.date": [ 2014-09-01 ] } { <2> "comments.name": [ alice, white ], "comments.comment": [ like, more, please, this ], "comments.age": [ 31 ], "comments.stars": [ 5 ], "comments.date": [ 2014-10-22 ] } { <3> "title": [ eggs, nest ], "body": [ making, money, work, your ], "tags": [ cash, shares ] }
<1> 第一個嵌套對象
<2> 第二個嵌套對象
<3> 根或是父文檔
藉由分別索引每個嵌套對象,對象的欄位中保持了其關聯。 我們的查詢可以只在同一個嵌套對象都匹配時才回應。
不僅如此,因嵌套對象都被索引了,連接嵌套對象至根文檔的查詢速度非常快--幾乎與查詢單一文檔一樣快。
這些額外的嵌套對象被隱藏起來,我們無法直接訪問他們。 為了要新增丶修改或移除一個嵌套對象,我們必須重新索引整個文檔。 要牢記搜尋要求的結果並不是只有嵌套對象,而是整個文檔。
示例
包含嵌套對象的文檔創建
創建映射

PUT devicelog_22 { "mappings" : { "log" : { "properties" : { "Items" : { "type":"nested", "properties" : { "name" : { "type" : "text", "fields" : { "keyword" : { "type" : "keyword", "ignore_above" : 256 } } }, "unit" : { "type" : "text", "fields" : { "keyword" : { "type" : "keyword", "ignore_above" : 256 } } }, "value" : { "type" : "double" } } }, "OperationDateTime" : { "type" : "date", "format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis", "ignore_malformed":false }, "systemId" : { "type" : "text", "fields" : { "keyword" : { "type" : "keyword", "ignore_above" : 256 } } } } } } }
批量插入數據

POST /devicelog_22/log/_bulk {"index":{"_id":1}} {"systemId":"001","OperationDateTime":"2020-03-02 10:34:03","Items":[{"name":"k1","value": 11},{"name":"k2","value": 12},{"dd":"k3","value":33}]} {"index":{"_id":2}} {"systemId":"001","OperationDateTime":"2020-03-02 11:34:03","Items":[{"name":"k1","value": 22},{"name":"k2","value": 33},{"dd":"k3","value":44}]} {"index":{"_id":3}} {"systemId":"001","OperationDateTime":"2020-03-02 16:34:03","Items":[{"name":"k1","value": 45.3},{"name":"k2","value": 89.333},{"dd":"k3","value":18.33}]} {"index":{"_id":4}} {"systemId":"001","OperationDateTime":"2020-03-03 15:34:03","Items":[{"name":"k1","value": 45.3},{"name":"k2","value": 89.333},{"dd":"k3","value":18.33}]} {"index":{"_id":5}} {"systemId":"001","OperationDateTime":"2020-03-03 18:34:03","Items":[{"name":"k1","value": 222.3},{"name":"k2","value": 33.333},{"dd":"k3","value":55.33}]}
聚合分析

GET devicelog_22/_search { "query": { "match_all": {} }, "aggs": { "item.name": { "nested": { "path": "Items" }, "aggs": { "terms": { "terms": { "field": "Items.name.keyword", "size": 10 }, "aggs": { "sum-aggs": { "sum": { "field": "Items.value" } } } } } } } }
對於嵌套對象的過濾查詢

GET devicelog_22/_search { "query": { "nested": { "path": "Items", "query": { "bool": { "must": [ {"term": { "Items.dd": { "value": "k3" } }} ] } } } } }
java查詢

@Test public void test2(){ try { SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); QueryBuilder queryBuilderHave=QueryBuilders.termQuery("Items.name","k1"); NestedQueryBuilder nestedQueryBuilder=QueryBuilders.nestedQuery("Items",queryBuilderHave, ScoreMode.None); QueryBuilder queryBuilder=QueryBuilders.boolQuery().must(nestedQueryBuilder); TopHitsAggregationBuilder aggregation1= AggregationBuilders.topHits("details").size(1). sort(SortBuilders.fieldSort("OperationDateTime").order(SortOrder.DESC)).fetchSource(true); DateHistogramAggregationBuilder aggregation= AggregationBuilders.dateHistogram("agg"). keyed(true).format("yyyy-MM-dd").field("OperationDateTime").dateHistogramInterval(DateHistogramInterval.DAY). timeZone(DateTimeZone.forOffsetHours(8)).subAggregation(aggregation1); searchSourceBuilder.size(0).fetchSource(false). timeout(new TimeValue(1000, TimeUnit.SECONDS)).query(queryBuilder).aggregation(aggregation); SearchRequest searchRequest=new SearchRequest(); searchRequest.source(searchSourceBuilder); SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT); if(searchResponse.status()== RestStatus.OK){ Histogram histogram= searchResponse.getAggregations().get("agg"); EsDocumentList esDocumentList=new EsDocumentList(); for (Histogram.Bucket entry : histogram.getBuckets()){ TopHits topHits=entry.getAggregations().get("details"); for (SearchHit hit : topHits.getHits().getHits()) { Map<String, Object> mapResult = hit.getSourceAsMap(); esDocumentList.add(mapResult); } } System.out.println("11"); } }catch (Exception ex){ System.out.println(ex); }