ES入门十:关系模型的实现:嵌套类型和父子文档

现实中,关系模型是到处存在的,例如书本与作者的关系。但是在ES中想要处理这个事情并不简单

在ES中保存关系型模型数据的方式主要有以下几种:

  • nested:在这种方式中,会通过一对多的放系保存在同一个文档中
  • join(parent/child):通过维护文档的父子节点,将两个对象分离

上面这2种方式都可以描述一对多的关系,今天我们就来了解一下

nested(嵌套类型)

nested类型是一种特别的object类型,其允许数组中的对象可以被单独索引,使他们可以被独立地索引。下面的示例是使用普通的object数组来保存书本与作者的一对多关系,我们看看会产生什么问题

bash 复制代码
# 创建 Mapping
PUT books_index
{
  "mappings": {
    "properties": { 
      "book_id": { "type": "keyword" },
      "author": { 
        "properties": {
          "first_name": { "type": "keyword" },
          "last_name": { "type": "keyword" }
        }
      }
    }
  }
}

# 写入书本数据
PUT books_index/_doc/1
{
  "book_id": "1234",
  "author": [
    { "first_name": "zhang", "last_name": "san" },
    { "first_name": "wang", "last_name": "wu" }
  ]
}

如上面的示例,我们创建了books_index索引,其中author字段是一个对象,包括了first_name与last_name两个属性,并且在我们写入数据的时候,书本的作者有两个(描述了一对多的关系):zhangsan和wangwu。

我们对上面的数据进行一个查询:

按道理来说我们的数据中是没有zhangwu这个作者的,但是在实际查询中能命中文档1,为什么那?

因为object被扁平化指挥,其丢失了first_name和last_name之间的关系,变成了下面这样的关系

对于这个扁平化的数组,原先first_name和last_name见的关系当然以及不复存在了。所以我们的查询语句在author.first_name中匹配了"zhang",在author.last_name匹配了"wu",自然就命中了文档1,那么有什么办法解决这个问题吗?
那就是使用nested数据类型,他可以是对象数组的对象被独立索引,这样fist_name和last_name之间的关系就不会丢失了。下面我们修改一下mapping,把author的类型定义为nested:

如上所示,使用nested关键字指定一个nested对象的查询,使用path指定nested对象的名字。

从上面的示例来看,nested通过冗余的方式将对象和文档存储在一起,所以查询时的性能是很高的,但是需要更新对象的时候,需要更新所有包含此对象的文档,例如某个作者的信息更改了,那么所有这个作者的书本文档都需要更新。所以nested适合查询频繁但更新频率低的场景

parent/child(父子类型)

join数据类型允许在一个索引中的文档创建父子关系,通过维护父子文档的关系独立出来两个对象。父文档和自文档是相互独立的,通过类似引用的关系进行绑定,所以当父文档更新时,不需要更新自文档,而自文档可以被任意的添加、修改、删除而不会影响到父文档和其他自文档

需要注意的是,为了维护父子文档的关系需要占用额外的内存资源,并且读取性能相对较差。但是由于父子文档是互相独立的,所以适合自文档更新频率高的场景

在Mapping中定义join数据类型
bash 复制代码
PUT join_books_index
{
  "mappings": {
    "properties": { 
      "book_id": { "type": "keyword" },
      "name": { "type": "text" },
      "book_comments_relation": { # 定义字段名字
        "type": "join", # 此字段为 join 类型
        "relations": { # 声明 Parent / Child 的关系
          "book": "comment" # book 是 Parent 的名称,comment 是 Child 的名称
        }
      }
    }
  },
  "settings": {
    "number_of_shards": 3, # 定义 3 个主分片
    "number_of_replicas": 1
  }
}

如上示例,book_comments_relation是字段的名字,使用join关键字定义此字段的类型为join类型,relations处声明了Parent/Child的关系,其中book是Parent的名称, comment是Child的名称

索引父文档(创建)

在定义了Mapping之后,我们写入父文档的数据

索引子文档(创建)

如上所示,book_comments_relation中声明了文档的类型为comment(即mapping中的自文档),并且使用parent字段指向父文档的id

为了确保查询时候的性能,父文档和子文档必须在同一个分片,所以需要强制使用routing参数,并且其值为父文档的Id(如果写入父文档的时候也用routing参数,那么需要保证他们的值是一样的)

数据检索

返回结果:

如图所示,我们在获取父文档的数据时候是不返回子文档的信息的,因为父子文档是相互独立的

获取子文档:

如上图所示,在获取子文档时,如果不加routing参数时,是无法找到对应的子文档的。routing参数的值为父文档的Id

parent id查询

如上所示,parent_id字段里面,我们查询了父文档Id为11并且comment类型的文档

返回结果:

has child查询

如果我们想查询用户"fork"评论了那些书本,可以使用 Has Child 查询 。Has Child 查询将在子文档中进行条件匹配,然后返回匹配文档对应的父文档的信息

返回结果:

has parent查询

那如果我们想查询java相关书籍的评论时,可以使用Has Parent 查询 。 Has Parent 查询会在父文档中进行匹配,然后返回匹配文档对应的子文档的信息。

返回结果:

相关推荐
Summer不秃4 分钟前
Flutter之使用mqtt进行连接和信息传输的使用案例
前端·flutter
旭日猎鹰8 分钟前
Flutter踩坑记录(二)-- GestureDetector+Expanded点击无效果
前端·javascript·flutter
Viktor_Ye15 分钟前
高效集成易快报与金蝶应付单的方案
java·前端·数据库
hummhumm17 分钟前
第 25 章 - Golang 项目结构
java·开发语言·前端·后端·python·elasticsearch·golang
一二小选手21 分钟前
【Maven】IDEA创建Maven项目 Maven配置
java·maven
J老熊27 分钟前
JavaFX:简介、使用场景、常见问题及对比其他框架分析
java·开发语言·后端·面试·系统架构·软件工程
猿java32 分钟前
什么是 Hystrix?它的工作原理是什么?
java·微服务·面试
AuroraI'ncoding33 分钟前
时间请求参数、响应
java·后端·spring
乐闻x43 分钟前
Vue.js 性能优化指南:掌握 keep-alive 的使用技巧
前端·vue.js·性能优化
一条晒干的咸魚1 小时前
【Web前端】创建我的第一个 Web 表单
服务器·前端·javascript·json·对象·表单