ES数据打平或者flat扁平化 导致嵌套对象查询可能出错
探索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
对象的关系额外进行了存储。