您好,登錄后才能下訂單哦!
如何探索Elasticsearch中的父子文檔,相信很多沒有經驗的人對此束手無策,為此本文總結了問題出現的原因和解決方法,通過這篇文章希望你能解決這個問題。
官網地址:
2.x中文版本
7.9版本
父-子關系文檔 在實質上類似于 nested model :允許將一個對象實體和另外一個對象實體關聯起來。而這兩種類型的主要區別是:在 nested
objects 文檔中,所有對象都是在同一個文檔中,而在父-子關系文檔中,父對象和子對象都是完全獨立的文檔。
父-子關系的主要作用是允許把一個 type 的文檔和另外一個 type 的文檔關聯起來,構成一對多的關系:一個父文檔可以對應多個子文檔 。與 nested
objects 相比,父-子關系的主要優勢有:
更新父文檔時,不會重新索引子文檔。
創建,修改或刪除子文檔時,不會影響父文檔或其他子文檔。這一點在這種場景下尤其有用:子文檔數量較多,并且子文檔創建和修改的頻率高時。
子文檔可以作為搜索結果獨立返回。
Elasticsearch 維護了一個父文檔和子文檔的映射關系,得益于這個映射,父-子文檔關聯查詢操作非常快。但是這個映射也對父-子文檔關系有個限制條件:父文檔和其所有子文檔,都必須要存儲在同一個分片中。
父-子文檔ID映射存儲在 Doc Values 中。當映射完全在內存中時, Doc Values 提供對映射的快速處理能力,另一方面當映射非常大時,可以通過溢出到磁盤提供足夠的擴展能力
因為has_child執行聯接,所以它與其他查詢相比速度較慢。 隨著指向唯一父文檔的匹配子文檔數量的增加,其性能會下降。 搜索中的每個has_child查詢都會大大增加查詢時間。
如果您關心查詢性能,請不要使用此查詢。 如果需要使用has_child查詢,請盡可能少使用。
要使用has_child查詢,您的索引必須包含一個聯接字段映射。 例如: PUT /my-index-000001 { "mappings": { "properties": { "my-join-field": { "type": "join", "relations": { "parent": "child" } } } } } GET /_search { "query": { "has_child": { "type": "child", "query": { "match_all": {} }, "max_children": 10, "min_children": 2, "score_mode": "min" } } }
type:(必需,字符串)為聯接字段映射的子關系的名稱。
query:(必需的查詢對象)要在type字段的子文檔上運行的查詢。 如果子文檔與搜索匹配,則查詢返回父文檔。
ignore_unmapped:(可選,布爾值)指示是否忽略未映射的類型并且不返回任何文檔而不是返回錯誤。 默認為false。
如果為false,則在未映射類型的情況下,Elasticsearch返回錯誤。您可以使用此參數查詢可能不包含該類型的多個索引。
max_children:(可選,整數)與返回的父文檔允許的查詢相匹配的子文檔的最大數量。 如果父文檔超出此限制,則將其從搜索結果中排除。
min_children:(可選,整數)與查詢相匹配的子文檔的最小數量,該查詢與為返回的父文檔的查詢匹配所需。 如果父文檔不符合此限制,則將其從搜索結果中排除。
score_mode:(可選,字符串)指示匹配子文檔的分數如何影響根父文檔的相關性分數。 有效值為:
none (Defaul不要使用匹配的子文檔的相關性分數。 該查詢將父文檔分配為0分。
avg:使用所有匹配的子文檔的平均相關性得分。
max:使用所有匹配的子文檔中的最高相關性得分。
min:使用所有匹配的子文檔中最低的相關性得分。
sum:將所有匹配的子文檔的相關性得分相加。
您不能使用標準排序選項對has_child查詢的結果進行排序。如果需要按子文檔中的字段對返回的文檔進行排序,請使用function_score查詢并按_score進行排序。 例如,以下查詢按其子文檔的click_count字段對返回的文檔進行排序。
GET /_search { "query": { "has_child": { "type": "child", "query": { "function_score": { "script_score": { "script": "_score * doc['click_count'].value" } } }, "score_mode": "max" } } }
返回其子級父文檔與提供的查詢匹配的子文檔。 您可以使用聯接字段映射在同一索引中的文檔之間創建父子關系。
因為執行連接,所以has_parent查詢比其他查詢慢。 隨著匹配父文檔數量的增加,其性能會下降。 搜索中的每個has_parent查詢都會大大增加查詢時間。
要使用has_parent查詢,您的索引必須包含一個聯接字段映射。 例如: PUT /my-index-000001 { "mappings": { "properties": { "my-join-field": { "type": "join", "relations": { "parent": "child" } }, "tag": { "type": "keyword" } } } } GET /my-index-000001/_search { "query": { "has_parent": { "parent_type": "parent", "query": { "term": { "tag": { "value": "Elasticsearch" } } } } } }
parent_type:(必需,字符串)為聯接字段映射的父級關系的名稱。
query:(必需的查詢對象)要在parent_type字段的父文檔上運行的查詢。 如果父文檔與搜索匹配,則查詢返回其子文檔。
score:(可選,布爾值)指示是否將匹配的父文檔的相關性分數匯總到其子文檔中。 默認為false。
如果為false,Elasticsearch將忽略父文檔的相關性得分。 Elasticsearch還會為每個子文檔分配一個關聯分數,該關聯分數等于查詢的提升值,默認為1。
如果為true,則將匹配的父文檔的相關性分數匯總到其子文檔的相關性分數中。
ignore_unmapped:(可選,布爾值)指示是否忽略未映射的parent_type而不返回任何文檔而不是錯誤。 默認為false。
如果為false,則在未映射parent_type的情況下,Elasticsearch返回錯誤。
您可以使用此參數查詢可能不包含parent_type的多個索引。
您不能使用標準排序選項對has_parent查詢的結果進行排序。
如果需要按返回文檔的父文檔中的字段對它們進行排序,請使用function_score查詢并按_score進行排序。 例如,以下查詢按其父文檔的view_count字段對返回的文檔進行排序。
GET /_search { "query": { "has_parent": { "parent_type": "parent", "score": true, "query": { "function_score": { "script_score": { "script": "_score * doc['view_count'].value" } } } } } }
返回加入特定父文檔的子文檔。 您可以使用聯接字段映射在同一索引中的文檔之間創建父子關系。
要使用parent_id查詢,您的索引必須包含一個聯接字段映射。 若要查看如何為parent_id查詢設置索引,請嘗試以下示例。
創建具有聯接字段映射的索引。 PUT /my-index-000001 { "mappings": { "properties": { "my-join-field": { "type": "join", "relations": { "my-parent": "my-child" } } } } } 索引ID為1的父文檔。 PUT /my-index-000001/_doc/1?refresh { "text": "This is a parent document.", "my-join-field": "my-parent" } 索引父文檔的子文檔。 PUT /my-index-000001/_doc/2?routing=1&refresh { "text": "This is a child document.", "my_join_field": { "name": "my-child", "parent": "1" } } 以下搜索返回ID為1的父文檔的子文檔。 GET /my-index-000001/_search { "query": { "parent_id": { "type": "my-child", "id": "1" } } }
type:(必需,字符串)為聯接字段映射的子關系的名稱。
id:(必需,字符串)父文檔的ID。 查詢將返回此父文檔的子文檔。
ignore_unmapped:(可選,布爾值)指示是否忽略未映射的類型并且不返回任何文檔而不是返回錯誤。 默認為false。
如果為false,則在未映射類型的情況下,Elasticsearch返回錯誤。
您可以使用此參數查詢可能不包含該類型的多個索引。
跟低版本的”_parent”的方式不一樣,說明Es在后期高版本做了語法上的修改
父子文檔在理解上來說,可以理解為一個關聯查詢,有些類似MySQL中的JOIN查詢,通過某個字段關系來關聯。父子文檔與嵌套文檔主要的區別在于,父子文檔的父對象和子對象都是獨立的文檔,而嵌套文檔中都在同一個文檔中存儲,如下圖所示:
新建Setting: PUT /test_doctor { "settings": { "number_of_shards": 1, "analysis": { "analyzer": { "index_ansj_analyzer": { "type": "custom", "tokenizer": "index_ansj", "filter": [ "my_synonym", "asciifolding" ] }, "comma": { "type": "pattern", "pattern": "," }, "shingle_analyzer": { "type": "custom", "tokenizer": "standard", "filter": [ "lowercase", "shingle_filter" ] } }, "filter": { "my_synonym": { "type": "synonym", "synonyms_path": "analysis/synonym.txt" }, "shingle_filter": { "type": "shingle", "min_shingle_size": 2, "max_shingle_size": 2, "output_unigrams": false } } } } } 新建Mapping: PUT /test_doctor/_mapping/_doc { "_doc": { "properties": { "date": { "type": "date" }, "name": { "type": "text", "fields": { "keyword": { "type": "keyword" } } }, "comment": { "type": "text", "fields": { "keyword": { "type": "keyword" } } }, "age": { "type": "long" }, "body": { "type": "text", "analyzer":"index_ansj_analyzer" "fields": { "keyword": { "type": "keyword" } } }, "title": { "type": "text", "analyzer":"index_ansj_analyzer", "fields": { "keyword": { "type": "keyword" } } }, "relation": { # 這個relation相當于一個普通的字段名 "type": "join", "relations": { # 該relations部分定義了文檔內的一組可能的關系,每個關系是父名和子名 "question": "answer" } } } } } 這段代碼建立了一個test_doctor的索引,其中relation是一個用于join的字段,type為join,關系relations為:父為question, 子為answer。 至于建立一父多子關系,只需要改為數組即可:"question": ["answer", "comment"] 備注:question和answer是自定義的一種關系
插入父文檔數據,需要指定上文索引結構中的relation為question PUT test_doctor/_doc/1 { "title":"這是一篇文章", "body":"這是一篇文章,從哪里說起呢? ... ...", "relation":"question" # 這個relation是一個普通的字段,value值為question表示為父文檔 } PUT test_doctor/_doc/2 { "title":"這是一篇小說", "body":"這是一篇小說,從哪里說起呢? ... ...", "relation":"question" # 這個relation是一個普通的字段,value值為question表示為父文檔 } 注意也可以寫成這樣"relation":{"name":"question"} 插入子文檔,需要在請求地址上使用routing參數指定是誰的子文檔,并且指定索引結構中的relation關系 PUT test_doctor/_doc/3?routing=1 { "name":"張三", "comment":"寫的不錯", "age":28, "date":"2020-05-04", "relation":{ # 這個relation是一個普通的字段,value值為answer表示為子文檔 "name":"answer", "parent":1 } } PUT test_doctor/_doc/4?routing=1 { "name":"李四", "comment":"寫的很好", "age":20, "date":"2020-05-04", "relation":{ # 這個relation是一個普通的字段,value值為answer表示為子文檔 "name":"answer", "parent":1 } } PUT test_doctor/_doc/5?routing=2 { "name":"王五", "comment":"這是一篇非常棒的小說", "age":31, "date":"2020-05-01", "relation":{ # 這個relation是一個普通的字段,value值為answer表示為子文檔 "name":"answer", "parent":2 } } PUT test_doctor/_doc/6?routing=2 { "name":"小六", "comment":"這是一篇非常棒的小說", "age":31, "date":"2020-05-01", "relation":{ # 這個relation是一個普通的字段,value值為answer表示為子文檔 "name":"answer", "parent":2 } } 父文檔: Map drugMap = Maps.newHashMap(); drugMap.put("id", "2"); // drugMap.put("title", "這是一篇小說"); // drugMap.put("body", "這是一篇小說,從哪里說起呢? ... ..."); drugMap.put("relation", "question");// 固定寫法 子文檔: Map maps = Maps.newHashMap(); maps.put("name", "answer"); // 固定寫法 maps.put("parent", "2"); // 這里的1是指的父文檔所綁定的id Map doctorTeamMap = Maps.newHashMap(); doctorTeamMap.put("id", "6"); doctorTeamMap.put("name", "小六"); doctorTeamMap.put("comment", "這是一篇非常棒的小說"); doctorTeamMap.put("age", "31"); doctorTeamMap.put("date", "2020-05-01"); doctorTeamMap.put("relation", maps); // 固定寫法 Java代碼實現: /** * 使用BulkProcessor批量更新數據 * @param indexName 索引名稱 * @param jsonString 索引的document數據 */ public boolean addIndexBulk(String indexName, Map<String, Object> jsonString, String id) { IndexRequest request = new IndexRequest(indexName, "_doc", id); request.source(jsonString, XContentType.JSON); dataBulkProcessor.add(request); return true; } /** * 添加路由 * @param indexName * @param jsonString * @param id * @param routing * @return */ public boolean addIndexBulk(String indexName, Map<String, Object> jsonString, String id, String routing) { IndexRequest request = new IndexRequest(indexName, "_doc", id); request.source(jsonString, XContentType.JSON); request.routing(routing); dataBulkProcessor.add(request); return true; }
es會自動生成一個額外的用于表示關系的字段:field#question 我們可以通過以下方式查詢: POST test_doctor/_search { "script_fields": { "parent": { "script": { "source": "doc['relation#question']" } } } } 響應結果: { "took" : 124, "timed_out" : false, "_shards" : { "total" : 1, "successful" : 1, "skipped" : 0, "failed" : 0 }, "hits" : { "total" : 7, "max_score" : 1.0, "hits" : [ { "_index" : "test_doctor", "_type" : "_doc", "_id" : "1", "_score" : 1.0, "fields" : { "parent" : [ "1" ] } }, { "_index" : "test_doctor", "_type" : "_doc", "_id" : "2", "_score" : 1.0, "_routing" : "1", "fields" : { "parent" : [ "1" ] } }, { "_index" : "test_doctor", "_type" : "_doc", "_id" : "3", "_score" : 1.0, "_routing" : "1", "fields" : { "parent" : [ "1" ] } }, { "_index" : "test_doctor", "_type" : "_doc", "_id" : "4", "_score" : 1.0, "_routing" : "1", "fields" : { "parent" : [ "1" ] } }, { "_index" : "test_doctor", "_type" : "_doc", "_id" : "5", "_score" : 1.0, "fields" : { "parent" : [ "5" ] } }, { "_index" : "test_doctor", "_type" : "_doc", "_id" : "6", "_score" : 1.0, "_routing" : "5", "fields" : { "parent" : [ "5" ] } }, { "_index" : "test_doctor", "_type" : "_doc", "_id" : "7", "_score" : 1.0, "_routing" : "1", "fields" : { "parent" : [ "1" ] } } ] } } 有_routing字段的說明是子文檔,它的parent字段是父文檔id,如果沒有_routing就是父文檔,它的parent指向當前id
通過parent_id query傳入父文檔id即可 POST test_doctor/_search { "query": { "parent_id": { "type": "answer", "id": "5" } } } Java API: //子文檔名 String child_type = "answer"; //父文檔ID String id = "5"; //ParentId查詢 ParentIdQueryBuilder parentIdQueryBuilder = new ParentIdQueryBuilder(child_type, id); builder.query(parentIdQueryBuilder); builder.from(0); builder.size(10); 通過ID和routing ,訪問子文檔(不加routing查不到) GetRequest getRequest = new GetRequest(indexName, child_type); //必須指定路由(父ID) getRequest.routing(id);
使用has_child來根據子文檔內容查詢父文檔,其實type就是創建文檔時,子文檔的標識。
查詢包含特定子文檔的父文檔,這是一種很耗性能的查詢,盡量少用。它的查詢標準格式如下 POST test_doctor/_search { "query": { "has_child": { "type": "answer", "query": { "match": { "name": "張三" } }, "inner_hits": {} # 同時返回父子數據 } } } POST test_doctor/_search { "query": { "has_child" : { "type" : "answer", "query" : { "match_all" : {} }, "max_children": 10, //可選,符合查詢條件的子文檔最大返回數 "min_children": 2, //可選,符合查詢條件的子文檔最小返回數 "score_mode" : "min" } } } 如果也想根據父文檔的字段進行過濾,采用后置過濾器的方法 POST test_doctor/_search { "query": { "has_child": { "type": "answer", "query": { "match": { "name": "張三" } }, "inner_hits": {} } }, "post_filter": { "bool": { "must": [ { "term": { "title": { "value": "文章", "boost": 1 } } } ] } } } Java API: // 子文檔查詢條件 QueryBuilder matchQuery = QueryBuilders.termQuery("name", "張三"); // 是否計算評分 ScoreMode scoreMode = ScoreMode.Total; HasChildQueryBuilder childQueryBuilder = new HasChildQueryBuilder("answer", matchQuery, scoreMode); childQueryBuilder.innerHit(new InnerHitBuilder()); builder.query(childQueryBuilder); builder.postFilter(boolQueryBuilder);
根據父文檔查詢子文檔 has_parent。
{ "query": { "has_parent": { "parent_type":"question", "query": { "match": { "title": "這是一篇文章" } } } } } // 是否計算評分 score = true; HasParentQueryBuilder hasParentQueryBuilder = new HasParentQueryBuilder("question", boolQueryBuilder, score); builder.query(hasParentQueryBuilder); builder.postFilter(QueryBuilders.termQuery("indextype", "answer")); // 子文檔的過濾條件
看完上述內容,你們掌握如何探索Elasticsearch中的父子文檔的方法了嗎?如果還想學到更多技能或想了解更多相關內容,歡迎關注億速云行業資訊頻道,感謝各位的閱讀!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。