在Elasticsearch中,Join可以让我们创建parent/child关系。Elasticsearch不是一个RDMS。通常join数据类型尽量不要使用,除非不得已。那么Elasticsearch为什么需要Join数据类型呢?

在Elasticsearch中,更新一个object需要root object一个完整的reindex:

  • 即使是一个field的一个字符的改变
  • 即便是nested object也需要完整的reindex才可以实现搜索

通常情况下,这是完全OK的,但是在有些场合下,如果我们有频繁的更新操作,这样可能对性能带来很大的影响。

如果你的数据需要频繁的更新,并带来性能上的影响,这个时候,join数据类型可能是你的一个解决方案。

join数据类型可以完全地把两个object分开,但是还是保持这两者之前的关系。

  1. parent及child是完全分开的两个文档
  2. parent可以单独更新而不需要重新reindex child
  3. children可以任意被添加/串改/删除而不影响parent及其它的children

与 nested类型类似,父子关系也允许您将不同的实体关联在一起,但它们在实现和行为上有所不同。 与nested文档不同,它们不在同一文档中,而parent/child文档是完全独立的文档。 它们遵循一对多关系原则,允许您将一种类型定义为parent类型,将一种或多种类型定义为child类型

即便join数据类型给我们带来了方便,但是,它也在搜索时给我带来额外的内存及计算方便的开销。

注意:目前Kibana对nested及join数据类型有比较少的支持。如果你想使用Kibana来在dashboard里展示数据,这个方面的你需要考虑。在未来,这种情况可能会发生改变。

**join数据类型是一个特殊字段,用于在同一索引的文档中创建父/子关系。 关系部分定义文档中的一组可能关系,每个关系是父(parent)名称和子(child)名称。 **

一个例子:

    PUT my_index
{
"mappings": {
"properties": {
"my_join_field": {
"type": "join",
"relations": {
"question": "answer"
}
}
}
}
}

在这里我们定义了一个叫做my_index的索引。在这个索引中,我们定义了一个field,它的名字是my_join_field。它的类型是join数据类型。同时我们定义了单个关系:question是answer的parent。

要使用join来index文档,必须在source中提供关系的name和文档的可选parent。 例如,以下示例在question上下文中创建两个parent文档:

    PUT my_index/_doc/1?refresh
{
"text": "This is a question",
"my_join_field": {
"name": "question"
}
} PUT my_index/_doc/2?refresh
{
"text": "This is another question",
"my_join_field": {
"name": "question"
}
}

这里采用refresh来强制进行索引,以便接下来的搜索。在这里name标识question,说明这个文档时一个question文档。

索引parent文档时,您可以选择仅将关系的名称指定为快捷方式,而不是将其封装在普通对象表示法中:

    PUT my_index/_doc/1?refresh
{
"text": "This is a question",
"my_join_field": "question"
} PUT my_index/_doc/2?refresh
{
"text": "This is another question",
"my_join_field": "question"
}

这种方法和前面的是一样的,只是这里我们只使用了question, 而不是一个像第一种方法那样,使用如下的一个对象来表达:

    "my_join_field": {
"name": "question"
}

在实际的使用中,你可以根据自己的喜好来使用。

索引child项时,必须在_source中添加关系的名称以及文档的parent id。

注意:需要在同一分片中索引父级的谱系,必须使用其parent的id来确保这个child和parent是在一个shard中。每个文档分配在那个shard之中在默认的情况下是按照文档的id进行一些hash来分配的,当然也可以通过routing来进行。针对child,我们使用其parent的id,这样就可以保证。否则在我们join数据的时候,跨shard是非常大的一个消费。

例如,以下示例显示如何索引两个child文档:

    PUT my_index/_doc/3?routing=1?refresh  (1)
{
"text": "This is an answer",
"my_join_field": {
"name": "answer", (2)
"parent": "1" (3)
}
} PUT my_index/_doc/4?routing=1?refresh
{
"text": "This is another answer",
"my_join_field": {
"name": "answer",
"parent": "1"
}
}

在上面的(1)处,我们必须使用routing,这样能确保parent和child是在同一个shard里。我们这里routing为1,这是因为parent的id 为1,在(3)处定义。(2) 处定义了该文档join的名称。

parent-join及其性能

join字段不应像关系数据库中的连接一样使用。 在Elasticsearch中,良好性能的关键是将数据去规范化为文档。 每个连接字段has_child或has_parent查询都会对查询性能产生重大影响。

join字段有意义的唯一情况是,如果您的数据包含一对多关系,其中一个实体明显超过另一个实体。 这种情况的一个例子是产品的用例和这些产品的报价。 如果提供的产品数量明显多于产品数量,则将产品建模为父文档并将产品建模为子文档是有意义的。

parent-join的限制

  • 对于每个index来说,只能有一个join字段
  • parent及child文档,必须是在一个shard里建立索引。这也意味着,同样的routing值必须应用于getting, deleting或updating一个child文档。
  • 一个元素可以有多个children,但是只能有一个parent.
  • 可以对已有的join项添加新的关系
  • 也可以将child添加到现有元素,但仅当元素已经是parent时才可以。

针对parent-join的搜索

parent-join创建一个字段来索引文档中关系的名称(my_parent,my_child,...)。

它还为每个parent/child关系创建一个字段。 此字段的名称是join字段的名称,后跟和关系中parent的名称。 因此,例如对于my_parent⇒[my_child,another_child]关系,join字段会创建一个名为my_join_field#my_parent的附加字段。

如果文档是子文件(my_child或another_child),则此字段包含文档链接到的parent_id,如果文档是parent文件(my_parent),则包含文档的_id。

搜索包含join字段的索引时,始终在搜索响应中返回这两个字段:

上面的描述比较绕口,我们还是以一个例子来说说明吧:

    GET my_index/_search
{
"query": {
"match_all": {}
},
"sort": ["_id"]
}

这里我们搜索所有的文档,并以_id进行排序:

    {
"took" : 2,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 4,
"relation" : "eq"
},
"max_score" : null,
"hits" : [
{
"_index" : "my_index",
"_type" : "_doc",
"_id" : "1",
"_score" : null,
"_source" : {
"text" : "This is a question",
"my_join_field" : "question" (1)
},
"sort" : [
"1"
]
},
{
"_index" : "my_index",
"_type" : "_doc",
"_id" : "2",
"_score" : null,
"_source" : {
"text" : "This is another question",
"my_join_field" : "question" (2)
},
"sort" : [
"2"
]
},
{
"_index" : "my_index",
"_type" : "_doc",
"_id" : "3",
"_score" : null,
"_routing" : "1",
"_source" : {
"text" : "This is an answer",
"my_join_field" : {
"name" : "answer", (3)
"parent" : "1" (4)
}
},
"sort" : [
"3"
]
},
{
"_index" : "my_index",
"_type" : "_doc",
"_id" : "4",
"_score" : null,
"_routing" : "1",
"_source" : {
"text" : "This is another answer",
"my_join_field" : {
"name" : "answer",
"parent" : "1"
}
},
"sort" : [
"4"
]
}
]
}
}

在这里,我们可以看到4个文档:

(1)表明这个文档是一个question join

(2)表明这个文档是一个question join

(3)表明这个文档是一个answer join

(4)表明这个文档的parent是id为1的文档

Parent-join 查询及aggregation

可以在aggregation和script中访问join字段的值,并可以使用parent_id查询进行查询:

    GET my_index/_search
{
"query": {
"parent_id": {
"type": "answer",
"id": "1"
}
}
}

我们通过查询parent_id,返回所有parent_id为1的所有answer类型的文档:

{
"took" : 0,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 2,
"relation" : "eq"
},
"max_score" : 0.35667494,
"hits" : [
{
"_index" : "my_index",
"_type" : "_doc",
"_id" : "4",
"_score" : 0.35667494,
"_routing" : "1",
"_source" : {
"text" : "This is another answer",
"my_join_field" : {
"name" : "answer",
"parent" : "1"
}
}
},
{
"_index" : "my_index",
"_type" : "_doc",
"_id" : "3",
"_score" : 0.35667494,
"_routing" : "1",
"_source" : {
"text" : "This is an answer",
"my_join_field" : {
"name" : "answer",
"parent" : "1"
}
}
}
]
}
}

在这里,我们可以看到返回id为3和4的文档。我们也可以对这些文档进行aggregation:

    GET my_index/_search
{
"query": {
"parent_id": {
"type": "answer",
"id": "1"
}
},
"aggs": {
"parents": {
"terms": {
"field": "my_join_field#question",
"size": 10
}
}
},
"script_fields": {
"parent": {
"script": {
"source": "doc['my_join_field#question']"
}
}
}
}

就像我们在上一节中介绍的那样, 在我们的应用实例中,在index时,它也创建一个额外的一个字段,虽然在source里我们看不到。这个字段就是my_join_filed#question,这个字段含有parent _id。在上面的查询中,我们首先查询所有的parent_id为1的所有的answer类型的文档。接下来对所有的文档以parent_id进行聚合:

    {
"took" : 0,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 2,
"relation" : "eq"
},
"max_score" : 0.35667494,
"hits" : [
{
"_index" : "my_index",
"_type" : "_doc",
"_id" : "4",
"_score" : 0.35667494,
"_routing" : "1",
"fields" : {
"parent" : [
"1"
]
}
},
{
"_index" : "my_index",
"_type" : "_doc",
"_id" : "3",
"_score" : 0.35667494,
"_routing" : "1",
"fields" : {
"parent" : [
"1"
]
}
}
]
},
"aggregations" : {
"parents" : {
"doc_count_error_upper_bound" : 0,
"sum_other_doc_count" : 0,
"buckets" : [
{
"key" : "1",
"doc_count" : 2
}
]
}
}
}

一个parent对应多个child

对于一个parent来说,我们可以定义多个child,比如:

    PUT my_index
{
"mappings": {
"properties": {
"my_join_field": {
"type": "join",
"relations": {
"question": ["answer", "comment"]
}
}
}
}
}

在这里,question是answer及comment的parent。

多层的parent join

虽然这个不建议,这样做可能会可能在query时带来更多的内存及计算方面的开销:

    PUT my_index
{
"mappings": {
"properties": {
"my_join_field": {
"type": "join",
"relations": {
"question": ["answer", "comment"],
"answer": "vote"
}
}
}
}
}

这里question是answer及comment的parent,同时answer也是vote的parent。它表明了如下的关系:

索引grandchild文档需routing值等于grand-parent(谱系里的更大parent):

    PUT my_index/_doc/3?routing=1&refresh
{
"text": "This is a vote",
"my_join_field": {
"name": "vote",
"parent": "2"
}
}

这个child文档必须是和他的grand-parent在一个shard里。在这里它使用了1,也即question的id。同时,对于vote来说,它的parent必须是它的parent,也即answer的id。

更多参考:https://www.elastic.co/guide/en/elasticsearch/reference/7.3/parent-join.html

Elasticsearch: Join数据类型的更多相关文章

  1. Elasticsearch 字段数据类型

    Elasticsearch 可以支持单个document中含有多个不同的数据类型. 核心数据类型(Core datatypes) 字符型(String datatype):string 数字型(Num ...

  2. Elasticsearch 中数据类型 text 与 keyword 的区别

    随着ElasticSearch 5.X 系列的到来, 同时也迎来了该版本的重大特性之一: 移除了string类型. 这个变动的根本原因是string类型会给我们带来很多困惑: 因为ElasticSea ...

  3. Elasticsearch : alias数据类型

    就像其他的很多语言一样,我们可以给已有的变量取一个别名(alias).即便是对高级语言一样,比如我们定义不同的指针变量,指向同一个内存空间.这个有些类似别名的概念. 在Elasticsearch中,我 ...

  4. elasticsearch父子文档处理(join)

    elasticsearch父子文档处理 join 一.背景 二.需求 三.前置知识 四.实现步骤 1.创建 mapping 2.添加父文档数据 3.添加子文档 4.查询文档 1.根据父文档id查询它下 ...

  5. 使用Hive或Impala执行SQL语句,对存储在Elasticsearch中的数据操作(二)

    CSSDesk body { background-color: #2574b0; } /*! zybuluo */ article,aside,details,figcaption,figure,f ...

  6. 使用Hive或Impala执行SQL语句,对存储在Elasticsearch中的数据操作

    http://www.cnblogs.com/wgp13x/p/4934521.html 内容一样,样式好的版本. 使用Hive或Impala执行SQL语句,对存储在Elasticsearch中的数据 ...

  7. Elasticsearch 通关教程(二): 索引映射Mapping问题

    数据库建表的时候,我们的DDL语句一般都会指定每个字段的存储类型,例如:varchar,int,datetime等等,目的很明确,就是更精确的存储数据,防止数据类型格式混乱. CREATE TABLE ...

  8. Elasticsearch实践(三):Mapping

    版本:Elasticsearch 6.2.4. Mapping类似于数据库中的表结构定义,主要作用如下: 定义Index下字段名(Field Name) 定义字段的类型,比如数值型,字符串型.布尔型等 ...

  9. ElasticSearch实战系列二: ElasticSearch的DSL语句使用教程---图文详解

    前言 在上一篇中介绍了ElasticSearch集群和kinaba的安装教程,本篇文章就来讲解下 ElasticSearch的DSL语句使用. ElasticSearch DSL 介绍 Elastic ...

随机推荐

  1. Tapdata x 轻流,为用户打造实时接入轻流的数据高速通道

      在全行业加速布局数字化的当口,如何善用工具,也是为转型升级添薪助力的关键一步.   那么当轻量的异构数据实时同步工具,遇上轻量的数字化管理工具,将会收获什么样的新体验?此番 Tapdata 与轻流 ...

  2. 项目中使用@Transactional需要注意的点

    项目如果是Spring Boot.或者Spring Cloud,切记需要在启动类上加入@EnableTransactionManagement该注解.否则事务不生效. @Transactional是一 ...

  3. 2022年windows的Visual Studio 安装后初始配置

    目录 前言 1.开发环境设置,修改存储空间 打开编译器,选择工具-->导入和导出设置-->重置所有设置-->下一步 修改盘符-->下一步->选择环境->完成 2.主 ...

  4. go交叉编译,部署到linux上出现cannot execute binray file的解决方案

    写在前面: 了解过常见的项目部署方式后,打算先从最简单的方式开始.没想到踩了大坑.先说下整个部署的过程.    博主是在window上生成linux上的可执行文件.. 具体过程: 1.首先按照网上说的 ...

  5. 从零开始Blazor Server(5)--权限验证

    序 之前我们一直使用的是微软自带的身份验证方式,即使用[Authorize]标签来做. 但是这种方式十分不灵活,微软推荐的方式是加Policy,但是这种方式对我们来说还是不够灵活. 所以本节我们用完全 ...

  6. 精心整理16条MySQL使用规范,减少80%问题,推荐分享给团队

    上篇文章介绍了如何创建合适的MySQL索引,今天再一块学一下如何更规范.更合理的使用MySQL? 合理规范的使用MySQL,可以大大减少开发工作量和线上问题,并提升SQL查询性能. 我精心总结了这16 ...

  7. luogu1419 寻找段落 (二分,单调队列)

    单调队列存坐标 #include <iostream> #include <cstdio> #include <cstring> #include <algo ...

  8. 创新能力加速产业发展,SphereEx 荣获“中关村银行杯”『大数据与云计算』领域 TOP1

    8 月 9 日下午,2022 中关村国际前沿科技创新大赛"中关村银行杯"大数据与云计算领域决赛在北京市门头沟区中关村(京西)人工智能科技园·智能文创园落下了帷幕.SphereEx ...

  9. 【建议收藏】Mac VMWare NAT模式安装 CentOS 7-操作教程

    学习大数据离不开 Linux 系统,网络上大部分文章都是在 Windows 系统下使用 VMWare Workstation 安装 CentOS ,并使用 NAT 模式配置网络.本文基于 Mac OS ...

  10. es5 es6 新增

    es5的新特性 对于数组和字符串都进行了加强 map 遍历 es6的新特性 数组的增强 find 查找findIndex 查找下标 字符的增强 includes 是否包含 (包含返回true 不包含返 ...