《ElasticSearch6.x實戰教程》之父-子關係文件

OKevin發表於2019-07-21

第七章-父-子關係文件

打虎親兄弟,上陣父子兵。

本章作為複雜搜尋的鋪墊,介紹父子文件是為了更好的介紹複雜場景下的ES操作。

在非關係型資料庫資料庫中,我們常常會有表與表的關聯查詢。例如學生表和成績表的關聯查詢就能查出學會的資訊和成績資訊。在ES中,父子關係文件就類似於表的關聯查詢。

背景

ES5.x開始藉助父子關係文件實現多表關聯查詢,核心是一個索引Index下可以建立多個型別Type。但ES6.x開始只允許一個索引Index下建立一個型別Type,甚至在未來的版本中將會移除建立型別Type。為了繼續支援多表關聯查詢,ES6.x推出了join新型別來支援父子關係文件的建立。

問題

假設現在有這樣的需求場景:一個部落格有多篇文章,文章有標題、內容、作者、日期等資訊,同時一篇文章中會有評論,評論有評論的內容、作者、日期等資訊,通過ES來儲存部落格的文章及評論資訊。

此時文章本身就是"父",而評論就是"子",這類問題也可以通過nested巢狀物件實現,大部分情況下netsted巢狀物件和parent-child父子物件能夠互相替代,但他們仍然不同的優缺點。下面將介紹這兩種資料結構。

nested巢狀物件

一篇文章的資料結構如下圖所示:

{
    "title":"ElasticSearch6.x實戰教程",
    "author":"OKevin",
    "content":"這是一篇水文",
    "created":1562141626000,
    "comments":[{
        "name":"張三",
        "content":"寫的真菜",
        "created":1562141689000
    },{
        "name":"李四",
        "content":"辣雞",
        "created":1562141745000
    }]
}

通過RESTful API建立索引及定義對映結構:

PUT http://localhost:9200/blog
{
    "mappings":{
        "article":{
            "properties":{
                "title":{
                    "type":"text",
                    "analyzer":"ik_smart",
                    "fields":{
                        "keyword":{
                            "type":"keyword",
                            "ignore_above":256
                        }
                    }
                },
                "author":{
                    "type":"text",
                    "analyzer":"ik_smart",
                    "fields":{
                        "keyword":{
                            "type":"keyword",
                            "ignore_above":256
                        }
                    }
                },
                "content":{
                    "type":"text",
                    "analyzer":"ik_smart"
                },
                "created":{
                    "type":"date"
                },
                "comments":{
                    "type":"nested",
                    "properties":{
                        "name":{
                            "type":"text",
                            "analyzer":"ik_smart",
                            "fields":{
                                "keyword":{
                                    "type":"keyword",
                                    "ignore_above":256
                                }
                            }
                        },
                        "content":{
                            "type":"text",
                            "analyzer":"ik_smart",
                            "fields":{
                                "keyword":{
                                    "type":"keyword",
                                    "ignore_above":256
                                }
                            }
                        },
                        "created":{
                            "type":"date"
                        }
                    }
                }
            }
        }
    }
}

插入資料:

POST http://localhost:9200/blog/article
{
    "title":"ElasticSearch6.x實戰教程",
    "author":"OKevin",
    "content":"這是一篇水文",
    "created":1562141626000,
    "comments":[{
        "name":"張三",
        "content":"寫的真菜",
        "created":1562141689000
    },{
        "name":"李四",
        "content":"辣雞",
        "created":1562141745000
    }]
}
POST http://localhost:9200/blog/article
{
    "title":"ElasticSearch6.x從入門到放棄",
    "author":"OKevin",
    "content":"這是一篇ES從入門到放棄文章",
    "created":1562144089000,
    "comments":[{
        "name":"張三",
        "content":"我已入門",
        "created":1562144089000
    },{
        "name":"李四",
        "content":"我已放棄",
        "created":1562144089000
    }]
}
POST http://localhost:9200/blog/article
{
    "title":"ElasticSearch6.x原理解析",
    "author":"專家",
    "content":"這是一篇ES原理解析的文章",
    "created":1562144089000,
    "comments":[{
        "name":"張三",
        "content":"牛逼,專家就是不一樣",
        "created":1562144089000
    },{
        "name":"李四",
        "content":"大牛",
        "created":1562144089000
    }]
}
  1. 查詢作者為“OKevin”文章的所有評論(父查子)
GET http://localhost:9200/blog/article/_search
{
    "query":{
        "bool":{
            "must":[{
                "match":{
                    "author.keyword":"OKevin"
                }
            }]
        }
    }
}

ES結果返回2條作者為"OKevin"的全部資料。

  1. 查詢評論中含有“辣雞”的文章(子查父)
GET http://localhost:9200/blog/article/_search
{
    "query":{
        "bool":{
            "must":[{
                "match":{
                    "author.keyword":"OKevin"
                }
            },{
                "nested":{
                    "path":"comments",
                    "query":{
                        "bool":{
                            "must":[{
                                "match":{
                                    "comments.content":"辣雞"
                                }
                            }]
                        }
                    }
                }
            }]
        }
    }
}

ES確實只返回了包含"辣雞"的資料。

兩次查詢都直接返回了整個文件資料。

parent-child父子文件

既然父子文件能實現表的關聯查詢,那它的資料結構就應該是這樣:

文章資料結構

{
    "title":"ElasticSearch6.x實戰教程",
    "author":"OKevin",
    "content":"這是一篇實戰教程",
    "created":1562141626000,
    "comments":[]
}

評論資料結構

{
    "name":"張三",
    "content":"寫的真菜",
    "created":1562141689000
}

ES6.x以前是將這兩個結構分別儲存在兩個型別Type中關聯(這看起來更接近關係型資料庫表與表的關聯查詢),但在ES6.x開始一個索引Index只能建立一個型別Type,要再想實現表關聯查詢,就意味著需要把上述兩張表揉在一起,ES6.x由此定義了一個新的資料型別——join

通過RESTful API建立索引及定義對映結構:

{
    "mappings":{
        "article":{
            "properties":{
                "title":{
                    "type":"text",
                    "analyzer":"ik_smart",
                    "fields":{
                        "keyword":{
                            "type":"keyword",
                            "ignore_above":256
                        }
                    }
                },
                "author":{
                    "type":"text",
                    "analyzer":"ik_smart",
                    "fields":{
                        "keyword":{
                            "type":"keyword",
                            "ignore_above":256
                        }
                    }
                },
                "content":{
                    "type":"text",
                    "analyzer":"ik_smart"
                },
                "created":{
                    "type":"date"
                },
                "comments":{
                    "type":"join",
                    "relations":{
                        "article":"comment"
                    }
                }
            }
        }
    }
}

重點關注其中的"comments"欄位,可以看到型別定義為join,relations定義了誰是父誰是子,"article":"comment"表示article是父comment是子。

父子文件的插入是父與子分別插入(因為可以理解為把多個表塞到了一張表裡)。

插入父文件:

POST http://localhost:9200/blog/article/1
{
    "title":"ElasticSearch6.x實戰教程",
    "author":"OKevin",
    "content":"這是一篇水文",
    "created":1562141626000,
    "comments":"article"
}
POST http://localhost:9200/blog/article/2
{
    "title":"ElasticSearch6.x從入門到放棄",
    "author":"OKevin",
    "content":"這是一篇ES從入門到放棄文章",
    "created":1562144089000,
    "comments":"article"
}
POST http://localhost:9200/blog/article/3
{
    "title":"ElasticSearch6.x原理解析",
    "author":"專家",
    "content":"這是一篇ES原理解析的文章",
    "created":1562144089000,
    "comments":"article"
}

插入子文件:

POST http://localhost:9200/blog/article/4?routing=1
{
    "name":"張三",
    "content":"寫的真菜",
    "created":1562141689000,
    "comments":{
        "name":"comment",
        "parent":1
    }
}
POST http://localhost:9200/blog/article/5?routing=1
{
    "name":"李四",
    "content":"辣雞",
    "created":1562141745000,
    "comments":{
        "name":"comment",
        "parent":1
    }
}
POST http://localhost:9200/blog/article/6?routing=2
{
    "name":"張三",
    "content":"我已入門",
    "created":1562144089000,
    "comments":{
        "name":"comment",
        "parent":2
    }
}
POST http://localhost:9200/blog/article/7?routing=2
{
    "name":"李四",
    "content":"我已放棄",
    "created":1562144089000,
    "comments":{
        "name":"comment",
        "parent":2
    }
}
POST http://localhost:9200/blog/article/8?routing=3
{
    "name":"張三",
    "content":"牛逼,專家就是不一樣",
    "created":1562144089000,
    "comments":{
        "name":"comment",
        "parent":3
    }
}
POST http://localhost:9200/blog/article/9?routing=3
{
    "name":"李四",
    "content":"大牛",
    "created":1562144089000,
    "comments":{
        "name":"comment",
        "parent":3
    }
}

如果查詢索引資料會發現一共有9條資料,並不是nested那樣將"評論"巢狀"文章"中的。

  1. 查詢作者為“OKevin”文章的所有評論(父查子)
GET http://localhost:9200/blog/article/_search
{
    "query":{
        "has_parent":{
            "parent_type":"article",
            "query":{
                "match":{
                    "author.keyword":"OKevin"
                }
            }
        }
    }
}

ES只返回了comment評論結構中的資料,而不是全部包括文章資料也返回。這是巢狀物件查詢與父子文件查詢的區別之一——子文件可以單獨返回

  1. 查詢評論中含有“辣雞”的文章(子查父)
GET http://localhost:9200/blog/artice/_search
{
    "query":{
        "has_child":{
            "type":"comment",
            "query":{
                "match":{
                    "content":"辣雞"
                }
            }
        }
    }
}

ES同樣也只返回了父文件的資料,而沒有子文件(評論)的資料。

nested巢狀物件和parent-child父子文件之間最大的區別,巢狀物件中的"父子"是一個文件資料,而父子文件的中的"父子"是兩個文件資料。這意味著巢狀物件中如果涉及對巢狀文件的操作會對整個文件造成影響(重新索引,但查詢快),包括修改、刪除、查詢。而父子文件子文件或者父文件本身就是獨立的文件,對子文件或者父文件的操作並不會相互影響(不會重新索引,查詢相對慢)。
關注公眾號:CoderBuff,回覆“es”獲取《ElasticSearch6.x實戰教程》完整版PDF。

這是一個能給程式設計師加buff的公眾號 (CoderBuff)

《ElasticSearch6.x實戰教程》之父-子關係文件

相關文章