探索ES-對象和嵌套對象(三)
前文回顧
上篇文章寫了探索ES-入門Kibana(二),算是基本上講完了ElasticSearch
和Kibana
的安裝和基本的概念。今天來正式講一講一些ElasticSearch
在使用中會遇到的問題和解決問題的方式方法。
引言
ElasticSearch
作為一個Nosql
的數據庫,其中一個特點是不支持多表關聯的
。所以,在ElasticSearch
中的數據都是以反范式
的方式進行存儲的。簡單來說,就是會存儲為一個大的Json對象
。
對象數據在ElasticSearch
中是作為object
的類型存儲的。但是對象數據如果是數組的情況下,會存在查詢時候不能關聯查詢的問題。
不知道有沒有小伙伴看到這里一臉懵比?
如果你不知道這是什么問題,那么請看下面的例子說明。
如果你知道是什么意思,那么請看例子下面的解決方式。
對象
舉一個博客系統的例子。
假設我們要在ElasticSearch
中建立一個blog
索引。blog
索引包括下面幾個內容。
- title:博客的標題
- content:博客的內容
- comment:博客的評論
- comment.username:博客評論的用戶
- comment.content:博客評論的內容
不知道有沒有人發現這里博客和評論是一對多的關系,沒有發現也沒有關系,繼續往下面看。
根據上面分解的字段,我們在ElasticSearch
中建立下面這個索引。
PUT blog
{
"mappings": {
"_doc": {
"properties": {
"blog": {
"properties": {
"title": {
"type": "keyword"
},
"content": {
"type": "text"
},
"comment": {
"properties": {
"content": {
"type": "text"
},
"username": {
"type": "keyword"
}
}
}
}
}
}
}
}
}
復制代碼
可以發現blog
和comment
都是作為object
類型來存儲的。在ElasticSearch
中會默認將字段作為object
來存儲。
我們插入一條文檔。
PUT blog/_doc/1
{
"blog": {
"title": "this is my first blog",
"content": "this is my first blog content",
"comment": {
"content": "so bad!",
"username": "li si"
}
}
}
復制代碼
這條文檔的內容大致是有人寫了一篇博客,然后李四同學在下面批評說so bad
。
我們以評論為維度進行搜索。
GET blog/_search
{
"query":{
"match": {
"blog.comment.content": "bad"
}
}
}
復制代碼
是可以搜索到數據的。

扁平化
這里要注意的是對象數據最后在ES中都是會被flat(扁平化)的
。
什么意思?
就是說上面的這條內容數據最后在ES
中是以下面這種形式存儲的。
blog:this is my first blog content:this is my first blog content comment.content:so bad comment.username:li si
復制代碼
但是,一般來說博客肯定不止是一條數據,一般來說會有多條數據,就是上面提到的一對多的關系了。
我們更新之前的數據。
PUT blog/_doc/1
{
"blog": {
"title": "this is my first blog",
"content": "this is my first blog content",
"comment": [
{
"content": "oh so good!",
"username": "zhang san"
},
{
"content": "so bad!",
"username": "li si"
}
]
}
}
復制代碼
上面這條數據的意思是除了李四說bad之后,還有個張三大哥說了good。
我們按照之前的查詢方式也是可以正常查詢到的。但是,我們現在如果要關聯查詢,我們既要評論者是李四的,又要李四說good的查詢。我們寫一個bool
查詢。bool
可以寫多個查詢,不論是term
還是match
都只能寫一個查詢。
GET blog/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"blog.comment.content": "good"
}
},
{
"match": {
"blog.comment.username": "li si"
}
}
]
}
}
}
復制代碼
但是,我們居然還是能夠查詢到這條數據。明明這條數據,李四說的是bad而不是good,為什么還是能夠查詢到呢?

那是因為對象被扁平化了之后,在ES
中是這么存儲的。
blog.comment.content:["oh so good!","so bad!"]
blog.comment.username:["zhang san","li si"]
復制代碼
兩個字段都是一個數組的形式來存儲數據的。數組與數組之間沒有對應關系。
ES
先在blog.comment.coetent
中看看有沒有good
,發現有。再在blog.comment.username
中看看有沒有li si
發現還是有。
最后這條數據就被查詢出來了。
那么怎么才能實現關聯查詢呢?用nested
,嵌套對象即可。
嵌套對象
默認字段的類型是object
類型,要使用nested
需要顯式指定。我們刪除之前的索引之后,重新建立索引。因為在ES
中,我們沒有辦法修改之前的mapping
,我們只能刪除重新建立。如果需要之前的數據,可以使用reindex
來將數據導入到新索引中。
PUT blog
{
"mappings": {
"_doc": {
"properties": {
"blog": {
"properties": {
"title": {
"type": "keyword"
},
"content": {
"type": "text"
},
"comment": {
"type":"nested",
"properties": {
"content": {
"type": "text"
},
"username": {
"type": "keyword"
}
}
}
}
}
}
}
}
}
復制代碼
我們再重新插入一條數據。
PUT blog/_doc/1
{
"blog": {
"title": "this is my first blog",
"content": "this is my first blog content",
"comment": [
{
"content": "oh so good!",
"username": "zhang san"
},
{
"content": "so bad!",
"username": "li si"
}
]
}
}
復制代碼
我們進行關聯搜索。
GET blog/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"blog.comment.content": "good"
}
},
{
"match": {
"blog.comment.username": "li si"
}
}
]
}
}
}
復制代碼
這個時候,我們發現就搜索不到對應的數據了。因為這個時候,ES
對nested
對象的關系額外進行了存儲。
