本文以Elasticsearch 6.8.4版本為例,介紹Elasticsearch父子文檔的使用。
上一篇文章介紹了Elasticsearch的嵌套文檔,這一篇來介紹另外一種關系文檔,父子文檔。
1、父子文檔
父子文檔在了解上來說,可以了解為一個關聯查詢,有些類似MySQL中的JOIN查詢,通過某個字段關系來關聯。
父子文檔與嵌套文檔主要的差別在于,父子文檔的父對象和子對象都是獨立的文檔,而嵌套文檔中都在同一個文檔中存儲,如下圖所示:
這裡引用官網的話,對比嵌套文檔來說,父-子關系的主要優勢有:
- 更新父文檔時,不會重新索引子文檔。
- 建立,修改或删除子文檔時,不會影響父文檔或其他子文檔。這一點在這種場景下尤其有用:子文檔數量較多,并且子文檔建立和修改的頻率高時。
- 子文檔可以作為搜尋結果獨立傳回。
1.1 建立索引
這裡還是以嵌套文檔的資料為例,假設資料如下:
[
{
"title":"這是一篇文章",
"body":"這是一篇文章,從哪裡說起呢? ... ..."
},
{
"name":"張三",
"comment":"寫的不錯",
"age":28,
"date":"2020-05-04"
},
{
"name":"李四",
"comment":"寫的很好",
"age":20,
"date":"2020-05-04"
},
{
"name":"王五",
"comment":"這是一篇非常棒的文章",
"age":31,
"date":"2020-05-01"
}
]
建立索引名和type均為blog的索引,從上面資料可以看出,其實父文檔(部落格内容)與子文檔分别用不同的字段來存儲對應的資料,不過在建立索引文檔的時候需要指定父子文檔的關系,即文章為parent,留言為child,建立索引語句如下:
PUT
http://localhost:9200/blog/{
"mappings": {
"blog": {
"properties": {
"date": {
"type": "date"
},
"name": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword"
}
}
},
"comment": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword"
}
}
},
"age": {
"type": "long"
},
"body": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword"
}
}
},
"title": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword"
}
}
},
"relation": {
"type": "join",
"relations": {
"parent": "child"
}
}
}
}
}
}
如下圖所示
1.2 插入資料
插入父文檔資料,需要指定上文索引結構中的relation為parent,如下:
POST
http://localhost:9200/blog/blog/1/{
"title":"這是一篇文章",
"body":"這是一篇文章,從哪裡說起呢? ... ...",
"relation":"parent"
}
插入子文檔,需要在請求位址上使用routing參數指定是誰的子文檔,并且指定索引結構中的relation關系,如下:
http://localhost:9200/blog/blog/2?routing=1{
"name":"張三",
"comment":"寫的不錯",
"age":28,
"date":"2020-05-04",
"relation":{
"name":"child",
"parent":1
}
}
http://localhost:9200/blog/blog/3?routing=1 {
"name":"李四",
"comment":"寫的很好",
"age":20,
"date":"2020-05-04",
"relation":{
"name":"child",
"parent":1
}
}
http://localhost:9200/blog/blog/4?routing=1 {
"name":"王五",
"comment":"這是一篇非常棒的文章",
"age":31,
"date":"2020-05-01",
"relation":{
"name":"child",
"parent":1
}
}
插入完成後,如下圖所示。
從這裡其實可以很明顯的看出與嵌套文檔的差別了,嵌套文檔隻有一個文檔,而這裡是有四個文檔。
1.3 查詢
普通查詢這裡不進行贅述,關系查詢的話其實很好了解,大緻分為兩種特殊情況:
- 根據父文檔查詢子文檔 has_child
- 根據子文檔查詢父文檔 has_parent
接下來我們來看如何進行關系查詢,首先看一下通過子文檔查詢父文檔,比如這樣的場景,查詢名稱是張三的人留言的文章,查詢語句如下:
{
"query": {
"has_child": {
"type":"child",
"query": {
"match": {
"name": "張三"
}
}
}
}
}
查詢結果如下:
使用has_child來根據子文檔内容查詢父文檔,其實type就是建立文檔時,子文檔的辨別。
在使用子查父的時候,可以添加一些篩選條件來增強比對的結果,比如最大比對max_children和最小比對min_children,這裡有點類似should查詢的minimum_should_match,感興趣的可以去官網了解更多的細節。
到這裡,其實對Elasticsearch特性了解的讀者就會知道如何根據父文檔查詢子文檔了,隻需要注意一點,父查子type需要修改成parent_type,其餘都與自查父類似,比如查詢标題為“這是一篇文章”的資料的留言内容,查詢語句如下:
{
"query": {
"has_parent": {
"parent_type":"parent",
"query": {
"match": {
"title": "這是一篇文章"
}
}
}
}
}
由于隻有一組父子文檔,效果不是很明顯,感興趣可以多造一些資料去體驗
聚合查詢與嵌套文檔類似,比較簡單,這裡在說明另外一種場景:祖輩和孫輩可以建立嗎?比如本文中的留言如果它也有子文檔,那麼可以根據文章查詢孫輩嗎?答案是可以的,隻需要在has_child裡面在嵌套一層has_child查詢即可。
1.4 使用建議
- 父子文檔都可以獨立傳回,對于某些場景很适用,比如主表資訊是一些基本不變的資料,而子表資訊經常增删改,并且子表資訊經常有查詢場景,這樣就很适合使用父子文檔。
- 父子文檔需要在同一分片上,當然,我們無需做特殊處理,預設就會為我放入同一個分片,其實原理是這樣的,Elasticsearch會根據routing中的參數去看父文檔所在分片在哪,然後将對應文檔存儲進去。
- 父子文檔查詢效率相對嵌套文檔較低,官網說是5-10倍左右。
其餘官網也給定了一些建議,具體可以檢視官方文檔,位址: https://www.elastic.co/guide/cn/elasticsearch/guide/current/parent-child-performance.html