Parent-Child RelationShip
ElasticSearch 中的Parent-Child關(guān)系和nested模型是相似的, 兩個(gè)都可以用于復(fù)雜的數(shù)據(jù)結(jié)構(gòu)中,區(qū)別是 nested 類型的文檔是把所有的實(shí)體聚合到一個(gè)文檔中而Parent-Child現(xiàn)對(duì)于比較獨(dú)立,每個(gè)實(shí)體即為一個(gè)文檔
Parent-Child 優(yōu)點(diǎn)
1、父文檔更新時(shí)不用重新為子文檔建立索引
2、子文檔的增加、修改、刪除是對(duì)父文檔和其他子文檔沒有任何影響的,這非常適用于子文檔非常大并且跟新頻繁的場(chǎng)景
3、子文檔也可以查詢結(jié)果返回
ElasticSearch 內(nèi)部維護(hù)一個(gè)map來保存Parent-Child之間的關(guān)系,正是由于這個(gè)map,所以關(guān)聯(lián)查詢能夠做到響應(yīng)速度很快,但是確實(shí)有個(gè)限制是Parent 文檔和所有的Child 文檔都必須保存到同一個(gè)shard中
ElasticSearch parent-child ID的映射是存到Doc value 中的,有足夠的內(nèi)存時(shí)響 應(yīng)是很快的。當(dāng)這個(gè)map很大的時(shí)候,還是有要有一部分存儲(chǔ)在硬盤中的。
Parent-Child Mapping
為了建立Parent-Child 模型我們需要在創(chuàng)建mapping的時(shí)候指定父文檔和子文檔或者在子文檔創(chuàng)建之前利用update-index API 來指定
例如:我們有個(gè)公司,其子公司分布在全國各地,我要分析員工和子公司的關(guān)系
我們使用Parent-Child 機(jī)構(gòu)
我們需要建立employee type 和 branch type 并且指定 branch 為_parent
PUT /company
{
"mappings": {
"branch": {},
"employee": {
"_parent": {
"type": "branch"
}
}
}
}
Indexing Parents and Children
創(chuàng)建父索引和創(chuàng)建其他索引并沒有區(qū)別,父文檔并不需要知道他們的子文檔
POST /company/branch/_bulk
{ "index": { "_id": "london" }}
{ "name": "London Westminster", "city": "London", "country": "UK" }
{ "index": { "_id": "liverpool" }}
{ "name": "Liverpool Central", "city": "Liverpool", "country": "UK" }
{ "index": { "_id": "paris" }}
{ "name": "Champs élysées", "city": "Paris", "country": "France" }
創(chuàng)建子文檔的時(shí)候你必須指出他們的父文檔的id
PUT /company/employee/1?parent=london
{
"name": "Alice Smith",
"dob": "1970-10-24",
"hobby": "hiking"
}
指定parent id 有兩個(gè)目的:他是父文檔和子文檔的關(guān)聯(lián),而且他也保證了父文檔和子文檔會(huì)存儲(chǔ)在同一個(gè)shard中,
在routing那個(gè)章節(jié)我們解釋了ElasticSearch 如何利用routing的值來決定分配到shard中的,如果文檔沒有指定routing的值的化,那么默認(rèn)為_id,公式為
shard = hash(routing) % number_of_primary_shards
但是,如果指定了 parent id 那么routing的值就不是_id 了 而是 parent id,換句話說就是父文檔和子文檔是具有相同的routing的值來確保他們會(huì)分配到同一個(gè)shard中的
當(dāng)我們用GET請(qǐng)求來檢索子文檔時(shí),我們需要指定parent id,并且創(chuàng)建索引、更新索引、還有刪除索引都需要指定parent id,不像搜索的請(qǐng)求,他會(huì)分發(fā)到所有的shard中,這些single-document請(qǐng)求只會(huì)發(fā)送到存儲(chǔ)它的shard中。如果沒有指定parent id 也許請(qǐng)求會(huì)發(fā)送到一個(gè)錯(cuò)誤的shard中
當(dāng)我們使用buk API 時(shí)也需要指定parent id
POST /company/employee/_bulk
{ "index": { "_id": 2, "parent": "london" }}
{ "name": "Mark Thomas", "dob": "1982-05-16", "hobby": "diving" }
{ "index": { "_id": 3, "parent": "liverpool" }}
{ "name": "Barry Smith", "dob": "1979-04-01", "hobby": "hiking" }
{ "index": { "_id": 4, "parent": "paris" }}
{ "name": "Adrien Grand", "dob": "1987-05-11", "hobby": "horses" }
Finding Parents by Their Children
has_child 和 filter 可以根據(jù)子文檔的內(nèi)容來查詢父文檔,例如我們可以用這樣的語句搜索所有分公司,出生在1980年以后的員工:
GET /company/branch/_search
{
"query": {
"has_child": {
"type": "employee",
"query": {
"range": {
"dob": {
"gte": "1980-01-01"
}
}
}
}
}
}
has_child 查詢會(huì)匹配到多個(gè)子文檔,每個(gè)文檔都會(huì)有不同的關(guān)聯(lián)得分。這些得分如何減少父文檔的單個(gè)得分取決于分?jǐn)?shù)模型的參數(shù)。默認(rèn)參數(shù)為none,即會(huì)忽略子文檔的得分,并且父文檔會(huì)加1.0.
下面的查詢會(huì)同時(shí)返回london 還有 liverpool 但是london 會(huì)得到一個(gè)更好的得分,因?yàn)锳lice Smith 更加匹配london
GET /company/branch/_search
{
"query": {
"has_child": {
"type": "employee",
"score_mode": "max",
"query": {
"match": {
"name": "Alice Smith"
}
}
}
}
}
min_children and max_children
has_child 和 filter 都有min_children 和 max_children 兩個(gè)參數(shù),作用是返回那些具有子文檔個(gè)數(shù)與之相匹配的父文檔數(shù)據(jù)
下面的查詢會(huì)返回具有兩個(gè)員工以上的分公司
GET /company/branch/_search
{
"query": {
"has_child": {
"type": "employee",
"min_children": 2,
"query": {
"match_all": {}
}
}
}
}
Finding Children by Their Parents
和nested 查詢只能返回根節(jié)點(diǎn)數(shù)據(jù)不同的是,父文檔和子文檔都是相對(duì)獨(dú)立的,并且可以被單獨(dú)查詢,has_child 查詢可以根據(jù)子文檔返回父文檔 而 has_parent查詢會(huì)根據(jù)父文檔返回子文檔
和has_child 查詢很相似,下面的查詢會(huì)返回那些工作在uk的員工
GET /company/employee/_search
{
"query": {
"has_parent": {
"type": "branch",
"query": {
"match": {
"country": "UK"
}
}
}
}
}
has_parent 查詢也支持score_mode模式,但是它只有兩種設(shè)置none(默認(rèn))和score,每個(gè)子文檔可以只擁有一個(gè)父文檔,所以就沒有必要將分?jǐn)?shù)分給多個(gè)子文檔了,這僅僅取決于你使用none還是score模式了
Children Aggregation
Parent-child 支持children aggregation parent aggregation 是不支持的
下面的例子示范了我們分析了員工的興趣
GET /company/branch/_search
{
"size" : 0,
"aggs": {
"country": {
"terms": {
"field": "country"
},
"aggs": {
"employees": {
"children": {
"type": "employee"
},
"aggs": {
"hobby": {
"terms": {
"field": "hobby"
}
}
}
}
}
}
}
}
Grandparents and Grandchildren
parent-child 關(guān)系不僅僅可以有兩代,他可以具有多代關(guān)系,但是所有關(guān)聯(lián)的數(shù)據(jù)都必須分到同一個(gè)shard中去。
我們稍微修改下之前的列子,叫county 成為branch 的父文檔
PUT /company
{
"mappings": {
"country": {},
"branch": {
"_parent": {
"type": "country"
}
},
"employee": {
"_parent": {
"type": "branch"
}
}
}
}
Countries and branches 只是簡(jiǎn)單的父子關(guān)系,所以我們用相同的方式來創(chuàng)建索引數(shù)據(jù)
POST /company/country/_bulk
{ "index": { "_id": "uk" }}
{ "name": "UK" }
{ "index": { "_id": "france" }}
{ "name": "France" }
POST /company/branch/_bulk
{ "index": { "_id": "london", "parent": "uk" }}
{ "name": "London Westmintster" }
{ "index": { "_id": "liverpool", "parent": "uk" }}
{ "name": "Liverpool Central" }
{ "index": { "_id": "paris", "parent": "france" }}
{ "name": "Champs élysées" }
parent id 保證了每個(gè)branch和他們的父文檔都被分配到了同一個(gè)shard中了,
如果和之前一樣,我們來創(chuàng)建employee 數(shù)據(jù),會(huì)發(fā)生什么?
PUT /company/employee/1?parent=london
{
"name": "Alice Smith",
"dob": "1970-10-24",
"hobby": "hiking"
}
shard 會(huì)根據(jù)文檔的parent ID—london 來分配employee 文檔,但是這個(gè)london 文檔會(huì)根據(jù)他的parent id uk來分配,所以employee文檔和country、branch 很有可能被分配到不同的shard中。
所以我們需要一個(gè)額外的參數(shù)routing保證所有關(guān)聯(lián)的文檔被分配到同一個(gè)shard中。
PUT /company/employee/1?parent=london&routing=uk
{
"name": "Alice Smith",
"dob": "1970-10-24",
"hobby": "hiking"
}
parent 參數(shù)仍然用于子文檔和父文檔的關(guān)聯(lián),routing 參數(shù)是用于保證文檔被分配到哪個(gè)shard中去
查詢和聚合對(duì)于多級(jí)的文檔也仍然有效,例如:?jiǎn)柫苏业侥男┏鞘械膯T工喜歡hiking
GET /company/country/_search
{
"query": {
"has_child": {
"type": "branch",
"query": {
"has_child": {
"type": "employee",
"query": {
"match": {
"hobby": "hiking"
}
}
}
}
}
}
}