当前位置: 首页 > news >正文

ElasticSearch文档数据关联关系处理

文章目录

    • ES如何处理关联关系
    • 对象类型
      • 案例一 适用场景
      • 案例二 不适用场景
    • 嵌套对象nested object
    • 父子关联关系
    • 嵌套文档 VS 父子关系

ES如何处理关联关系

关系型数据库中的范式化:

  • 减少了数据冗余,节省了磁盘空间
  • 减少了不必要的更新操作,因为没有了数据冗余,我更新一个地方的数据就可以了,不用去更新冗余数据
  • 单查询需要join更多的表,范式简化了更新,读取操作可能更多。

反范式化:

  • 不使用join关联关系,而是在文档中保存冗余数据
  • 读取的性能更好,并且ES通过压缩_source字段减少所占磁盘空间
  • 不适合频繁更新的场景

ElasticSearch就是使用的反范式。ES并不擅长处理关联关系,如果出现了一般会采取以下几种方式:

  • 对象类型
  • 嵌套对象
  • 父子关联关系
  • 应用层面自己处理



对象类型

文档中使用一个字段,该字段保存的值value是一个object。这种方式不适用于 value为对象数组的场景,查询会不准确。

如下所示,案例一是适用场景,案例二为不适用场景

案例一 适用场景

博客作者信息变更

对象类型:

  • 在每一博客的文档中都保留作者的信息
  • 如果作者信息发生变化,需要修改相关的博客文档
DELETE /blog
# 创建一个blog索引,其中的user字段保存的是一个object
PUT /blog
{"mappings": {"properties": {"content": {"type": "text"},"create_time": {"type": "date","format": ["yyyy-MM-dd HH:mm:ss"]},"user": {				# user字段中保存的是一个对象,通过properties关键字定义对象中的字段"properties": {"userid": {"type": "long"},"username": {"type": "text"},"age": {"type": "long"}}}}}
}# 插入一条 blog信息
PUT /blog/_doc/1
{"content": "I like Elasticsearch","create_time": "2024-01-01 00:00:00","user": {			# user字段中保存的是一个对象"userid": 1,"username": "hushang","age": 24}
}# 查询,可以使用使用user.field 对象内的字段查询
GET /blog/_search
{"query": {"bool": {"must": [{ "match": { "content": "Elasticsearch" } },{ "match": { "user.username": "hushang" } }]}}
}



案例二 不适用场景

user字段如果保存的是一个对象数组,在搜索时添加两个查询条件,数组中两个对象分别满足一个条件。

我使用bool must 关键字,表示两个查询条件都需要满足,才会显示文档。但是上方中数组中两个对象分别满足一个条件 这也查询出来了。

DELETE /blog
# 创建一个blog索引,其中的user字段保存的是一个object
PUT /blog
{"mappings": {"properties": {"content": {"type": "text"},"user": {			#  user字段保存 姓 和 名 两个字段"properties": {"first_name": {"type": "text"},"last_name": {"type": "text"}}}}}
}# 写入一条数据  张三和李四
PUT /blog/_doc/1
{"content": "speed","user": [{"first_name":"zhang","last_name":"san"},{"first_name":"li","last_name":"si"}]
}# 查询,此时我的性查询的是zhang  名查询的是si    而且还是采用的bool must方式。我期望的结果是应该查询不到数据
# 但实际上此时能查询到数据
GET /blog/_search
{"query": {"bool": {"must": [{ "match": { "user.first_name": "zhang" } },{ "match": { "user.last_name": "si" } }]}}
}

在这里插入图片描述



造成这种情况的原因是

ES在存储文档数据时,内部对象的边界并没有考虑在内,JSON格式被处理成扁平式键值对结构。当对多个字段进行查询时,导致了意外结果。

"content":"speed"
"user".first_name: ["zhang","li"]
"user".last_name: ["san","si"]

可以使用nested data type查询解决这个问题



嵌套对象nested object

  • nested数据类型,它允许对象数组中的对象被独立索引

  • 使用nested和properties关键字,将上方案例中所有user索引到多个分隔的文档。

  • 在内部,Nested文档会被保存在两个Lucene文档中,在查询时做join处理

DELETE /blog
# 创建一个blog索引,其中的user字段保存的是一个object
# 并且使用了type:nested
PUT /blog
{"mappings": {"properties": {"content": {"type": "text"},"user": {"type": "nested", 		# 使用了type:nested"properties": {"first_name": {"type": "keyword"},"last_name": {"type": "keyword"}}}}}
}# 写入一条数据  张三和李四
PUT /blog/_doc/1
{"content": "speed","user": [{"first_name":"zhang","last_name":"san"},{"first_name":"li","last_name":"si"}]
}# 查询
GET /blog/_search
{"query": {"nested": {			# 使用nested关键字"path": "user",	# user字段是nested类型  这里指定nestred类型的字段,并且下面就是对这个字段进行查询"query": {		# 之后就是正常的查询query语句"bool": {"must": [{ "match": { "user.first_name": "zhang" } },{ "match": { "user.last_name": "si" } }]}}}}
}

在这里插入图片描述



nested类型的字段,直接进行aggs聚合操作是没有数据的

# user字段是nested类型 直接聚合操作是没有数据的
GET /blog/_search
{"size": 0, "aggs": {"hs_first_name": {"terms": {"field": "user.first_name"}}}
}# 需要添加nestred关键字,并指定user这个字段
GET /blog/_search
{"size": 0, "aggs": {"hs_agg": {"nested": {		# 添加nestred关键字"path": "user"}, "aggs": {		# 再进行聚合操作"hs_first_name": {"terms": {"field": "user.first_name"}}}}}
}

在这里插入图片描述

在这里插入图片描述



父子关联关系

使用ES时,大部分的场景都不会频繁更新操作,父子关联关系了解即可。

再更新操作时,对象类型和嵌套对象nested方式有一个问题,因为根对象和嵌套对象本质上它们还是存在一个文档中的,每次更新时就可以需要重新索引整个文档。

ES提供了父子关联关系,通过维护parent/child的关系,分离它们,使父文档和子文档是两个独立的文档,更新其中一个文档不会影响另一个文档。



设定 Parent/Child Mapping

# 设定 Parent/Child Mapping映射关系
# 指定我们定义的hs_blog_comments_relation字段类型为join
# 并在relations下指定两个关联的自定义字符串值 其中hs_blog为parent名称,hs_comment为child名称
PUT /my_blogs
{"settings": {"number_of_shards": 2},"mappings": {"properties": {"hs_blog_comments_relation": {"type": "join","relations": {"hs_blog": "hs_comment"}},"content": {"type": "text"},"title": {"type": "keyword"}}}
}

在这里插入图片描述



索引父文档

# 索引两个父文档 ,指定文档id为blog1 和 blog2
# 同时指定文档的类型为hs_blog父文档
PUT /my_blogs/_doc/blog1
{"content": "learning ELK ","title": "Learning Elasticsearch","hs_blog_comments_relation": {"name": "hs_blog"}
}
PUT /my_blogs/_doc/blog2
{"content": "learning Hadoop ","title": "Learning Hadoop","hs_blog_comments_relation": {"name": "hs_blog"}
}

在这里插入图片描述



索引子文档

创建子文档时,必须通过routing指定父文档id,保证父子文档在一个shard中,提高join查询性能。

当指定子文档时,必须指定父文档id

# 索引三个子文档,指定文档id、同时指定routing 让父子文档在相同的shard中
# 指定文档的类型为子文档,同时必须指定它的父文档id
PUT /my_blogs/_doc/comment1?routing=blog1
{"comment":"I am learning ELK","username":"Jack","hs_blog_comments_relation": {"name": "hs_comment","parent": "blog1"}
}
PUT /my_blogs/_doc/comment2?routing=blog2
{"comment":"I like Hadoop!!!!!","username":"Jali","hs_blog_comments_relation": {"name": "hs_comment","parent": "blog2"}
}
PUT /my_blogs/_doc/comment3?routing=blog2
{"comment":"Hello Hadoop","username":"Bob","hs_blog_comments_relation": {"name": "hs_comment","parent": "blog2"}
}

在这里插入图片描述



测试查询

# 查询所有文档,就是正常的查询,能查询到5个文档,因为父子文档都是独立的文档
POST /my_blogs/_search#根据父文档ID查看,也就是正常的查询
GET /my_blogs/_doc/blog2# has_child 查询,返回这个子文档对应的父文档
GET /my_blogs/_search
{"query": {"has_child": {"type": "hs_comment","query": {"match": {"username": "Jack"}}}}
}# has_parent 查询,返回相关的子文档
GET /my_blogs/_search
{"query": {"has_parent": {"parent_type": "hs_blog","query": {"match": {"content": "ELK"}}}}
}

在这里插入图片描述

在这里插入图片描述



GET /my_blogs/_search
#通过ID ,访问子文档,会发现查询不到数据,返回404
# 但是通过上方直接查询全部是能查询到comment3这个文档的
GET /my_blogs/_doc/comment3
# 通过query语句能查询到到这个子文档
GET /my_blogs/_search
{"query": {"match": {"comment": "Hello"}}
}
#通过ID和routing ,这种方式也能查询到这个子文档
GET /my_blogs/_doc/comment3?routing=blog2#更新子文档,因为使用的是PUT 全量更新,所以在文档中还是需要指定子文档类型和父文档id
PUT /my_blogs/_doc/comment3?routing=blog2
{"comment": "Hello Hadoop??","blog_comments_relation": {"name": "hs_comment","parent": "blog2"}
}



嵌套文档 VS 父子关系

Nested ObjectParent / Child
优点文档存储在一起,读取性能高父子文档可以独立更新
缺点更新嵌套的子文档时,需要更新整个文档需要额外的内存维护关系。读取性能相对差
适用场景子文档偶尔更新,以查询为主子文档更新频繁

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • HttpUtils工具类(二)Apache HttpClient 5 使用详细教程
  • 英文域名注册选什么样的好?
  • 快讯 | 苹果拟于2026年推出1000美元桌面机器人,集成Siri智能技术
  • JavaScript学习笔记(十三):网络请求JS AJAX
  • 学习嵌入式第二十六天
  • 财务会计与管理会计(七)
  • redis面试(十三)公平锁排队代码剖析
  • 私域场景中的数字化营销秘诀
  • 欧拉角(Euler angles)详解
  • NVIDIA Isaac Lab 入门教程(一)
  • 几种防止Spring Boot 程序崩溃的方法
  • mfc140u.dll丢失错误解决方法的基本思路——四种修复mfc140u.dll的方法
  • go-zero中间件的使用
  • C++ //练习 16.55 如果我们的可变参数版本print的定义之后声明非可变参数版本,解释可变参数的版本会如何执行。
  • Java 集成测试详解及示例
  • JS 中的深拷贝与浅拷贝
  • [LeetCode] Wiggle Sort
  • 10个确保微服务与容器安全的最佳实践
  • Android 初级面试者拾遗(前台界面篇)之 Activity 和 Fragment
  • ECS应用管理最佳实践
  • Idea+maven+scala构建包并在spark on yarn 运行
  • Js实现点击查看全文(类似今日头条、知乎日报效果)
  • Linux后台研发超实用命令总结
  • Logstash 参考指南(目录)
  • MySQL主从复制读写分离及奇怪的问题
  • Quartz实现数据同步 | 从0开始构建SpringCloud微服务(3)
  • uni-app项目数字滚动
  • 如何选择开源的机器学习框架?
  • 手机app有了短信验证码还有没必要有图片验证码?
  • ​ArcGIS Pro 如何批量删除字段
  • ​LeetCode解法汇总2583. 二叉树中的第 K 大层和
  • # 深度解析 Socket 与 WebSocket:原理、区别与应用
  • #14vue3生成表单并跳转到外部地址的方式
  • $.proxy和$.extend
  • (1) caustics\
  • (9)STL算法之逆转旋转
  • (leetcode学习)236. 二叉树的最近公共祖先
  • (Python) SOAP Web Service (HTTP POST)
  • (Ruby)Ubuntu12.04安装Rails环境
  • (补充):java各种进制、原码、反码、补码和文本、图像、音频在计算机中的存储方式
  • (独孤九剑)--文件系统
  • (附源码)springboot学生选课系统 毕业设计 612555
  • (附源码)ssm失物招领系统 毕业设计 182317
  • (精确度,召回率,真阳性,假阳性)ACC、敏感性、特异性等 ROC指标
  • (亲测有效)推荐2024最新的免费漫画软件app,无广告,聚合全网资源!
  • (三)Kafka 监控之 Streams 监控(Streams Monitoring)和其他
  • (十七)Flask之大型项目目录结构示例【二扣蓝图】
  • (转)socket Aio demo
  • (转)Unity3DUnity3D在android下调试
  • (轉貼) VS2005 快捷键 (初級) (.NET) (Visual Studio)
  • .NET Core6.0 MVC+layui+SqlSugar 简单增删改查
  • .NET NPOI导出Excel详解
  • .Net 路由处理厉害了
  • .net 中viewstate的原理和使用
  • .Net 转战 Android 4.4 日常笔记(4)--按钮事件和国际化