Elasticsearch:在Elasticsearch中的join数据类型父子关系

编程

在关系数据库中,子表使用外键引用父表,这种关系称为join。 设计通常涉及规范化数据。
ElasticSearch不是关系数据库,它全与搜索效率而不是存储效率有关。 存储的数据已被去规范化并且几乎是平坦的。 这意味着join不能跨索引,Elasticsearch的重点在于速度,而传统join的运行速度太慢。 因此,子文档和父文档都必须位于相同的索引和相同的分片中。

 
父-子关系例子
让我们考虑下图1所示的家谱。该树有3个父母和9个孩子。 每个角色都有“gender”和“ isAlive”状态。

 

在上面的示例中,我们探索以下场景:

亲子关系
每个父母有多个孩子
多个层次的亲子关系
创建family_tree索引
以下代码有为上述关系创建索引:

PUT family_tree
{
  "settings": {
    "index": {
      "number_of_shards": 1,
      "number_of_replicas": 1
    }
  },
  "mappings": {
    "properties": {
      "firstName": {
        "type": "text"
      },
      "lastName": {
        "type": "text"
      },
      "gender": {
        "type": "text"
      },
      "isAlive": {
        "type": "boolean"
      },
      "relation_type": {
        "type": "join",
        "eager_global_ordinals": true,
        "relations": {
          "parent": "child"
        }
      }
    }
  }
}
在上面:

relation_type: 是join数据类型的名字
eager_global_ordinals: 亲子使用Global Ordinals加快导入速度
relations: 定义了一组可能的关系,每个关系都是parent名称和child名称。在这个例子里,为了方便,我们使用了“parent”及“child”作为父子关系的名称。在实际的例子中,我们可以使用你们所喜欢的名字
插入parent数据
在运行脚本以插入上图1所示的其他父级数据之前,让我们逐步了解一个父级插入的代码。

PUT family_tree/_doc/1?routing=Darren
{
  "firstName": "Darren",
  "lastName": "Ford",
  "gender": "Male",
  "isAlive": false,
  "relation_type": {
    "name": "parent"
  }
}
上面的代码为Darren Ford创建了一个新文档,并使用related_type字段将其标记为父文档。 将值“parent”分配给关系的名称。 除了关系之外,它还添加了所需的字段,例如“firstName”,“lastName”,“gender”和“ isAlive”。
这里要注意的一件事是routing查询参数。 每个父对象都为其参数分配自己的名称。 路由字段可帮助我们控制文档将在哪个分片上建立索引。 分片使用以下公式标识:

shard = hash(routing_value) % number_of_primary_shards
我们通过如果的方法来插入余下的parent文档:

PUT family_tree/_doc/1?routing=Darren
{
  "firstName": "Darren",
  "lastName": "Ford",
  "gender": "Male",
  "isAlive": false,
  "relation_type": {
    "name": "parent"
  }
}
 
PUT family_tree/_doc/2?routing=Sienna
{
  "firstName": "Sienna",
  "lastName": "Evans",
  "gender": "Female",
  "isAlive": false,
  "relation_type": {
    "name": "parent"
  }
}
 
PUT family_tree/_doc/3?routing=Ryan
{
  "firstName": "Ryan",
  "lastName": "Turner",
  "gender": "Male",
  "isAlive": false,
  "relation_type": {
    "name": "parent"
  }
}
插入child数据
同样,让我们先遍历一个子插件,然后运行上图所示的9个子插件的批量插入。

PUT family_tree/_doc/5?routing=Darren
{
  "firstName": "Pearl",
  "lastName": "Ford",
  "gender": "Female",
  "isAlive": true,
  "relation_type": {
    "name": "child",
    "parent": "1"
  }
}
在我们的示例中,“Pearl Ford”是“ Darren Ford”的子代,请注意,我们使用与创建Darren记录相同的routing查询参数。 这是因为子文档和父文档必须位于同一分片上的限制。
该记录与Darren的记录之间的关联是由related_type字段进行的,在该字段中,我们将关系的名称添加为“child”,从而使Pearl Ford成为ID为“1”的parent的子代(我们创建父代Darren的同一个ID 与)。

我们可以运用如下的代码来创建如下的子文档:

PUT family_tree/_bulk?routing=Darren
{"index": {"_id": "4"}}
{"firstName":"Otis", "lastName":"Ford", "gender":"Male", "isAlive":false, "relation_type":{ "name":"child", "parent":"1" }}
{"index": {"_id": "5"}}
{"firstName":"Pearl", "lastName":"Ford", "gender":"Female", "isAlive":true, "relation_type": { "name":"child", "parent":"1" }}
{"index": {"_id": "6"}}
{"firstName":"Ava", "lastName":"Ford", "gender":"Female", "isAlive":true, "relation_type": { "name":"child", "parent":"1" }}
{"index": {"_id": "7"}}
{"firstName":"Tyler", "lastName":"Ford", "gender":"Male", "isAlive":true, "relation_type": { "name":"child", "parent":"1" }}
{"index": {"_id": "8"}}
{"firstName":"Xavier", "lastName":"Ford", "gender":"Male", "isAlive":true, "relation_type": { "name":"child", "parent":"1"}}
 
 
PUT family_tree/_bulk?routing=Sienna
{"index": {"_id": "9"}}
{"firstName":"Ralph", "lastName":"Evans", "gender":"Male", "isAlive":true, "relation_type":{ "name":"child", "parent":"2" }}
 
PUT family_tree/_bulk?routing=Ryan
{"index": {"_id": "10"}}
{"firstName":"Fred", "lastName":"Turner", "gender":"Male", "isAlive":true, "relation_type":{ "name":"child", "parent":"3" }}
{"index": {"_id": "11"}}
{"firstName":"Scarlet", "lastName":"Turner", "gender":"Female", "isAlive":false, "relation_type":{ "name":"child", "parent":"3" }}
{"index": {"_id": "12"}}
{"firstName":"Wayne", "lastName":"Turner", "gender":"Male", "isAlive":true, "relation_type":{ "name":"child", "parent":"3" }}
我们使用上面bulk API来把我们的child数据导入到Elasticsearch中。

查询数据
现在是执行和理解的有趣部分,我们可以在刚刚创建的关系上运行查询。

搜索和过滤特定的parent
得到Sienna Evans的child:parent_id查询可用于查找属于特定parent的child文档
GET family_tree/_search
{
  "query": {
    "parent_id": {
      "type": "child",
      "id": "2"
    }
  }
}
返回的结果是:

    "hits" : [
      {
        "_index" : "family_tree",
        "_type" : "_doc",
        "_id" : "9",
        "_score" : 1.7917595,
        "_routing" : "Sienna",
        "_source" : {
          "firstName" : "Ralph",
          "lastName" : "Evans",
          "gender" : "Male",
          "isAlive" : true,
          "relation_type" : {
            "name" : "child",
            "parent" : "2"
          }
        }
      }
    ]
上查询出parent_id为2的所有child文档。

获取所有isAlive为true的所有Darren Ford的孩子:bool和must关键字可用于获取记录。
GET family_tree/_search
{
  "query": {
    "bool": {
      "filter": {
        "term": {
          "isAlive": true
        }
      },
      "must": {
        "parent_id": {
          "type": "child",
          "id": "1"
        }
      }
    }
  }
}
返回的结果为:

    "hits" : [
      {
        "_index" : "family_tree",
        "_type" : "_doc",
        "_id" : "5",
        "_score" : 0.6931472,
        "_routing" : "Darren",
        "_source" : {
          "firstName" : "Pearl",
          "lastName" : "Ford",
          "gender" : "Female",
          "isAlive" : true,
          "relation_type" : {
            "name" : "child",
            "parent" : "1"
          }
        }
      },
      {
        "_index" : "family_tree",
        "_type" : "_doc",
        "_id" : "6",
        "_score" : 0.6931472,
        "_routing" : "Darren",
        "_source" : {
          "firstName" : "Ava",
          "lastName" : "Ford",
          "gender" : "Female",
          "isAlive" : true,
          "relation_type" : {
            "name" : "child",
            "parent" : "1"
          }
        }
      },
      {
        "_index" : "family_tree",
        "_type" : "_doc",
        "_id" : "7",
        "_score" : 0.6931472,
        "_routing" : "Darren",
        "_source" : {
          "firstName" : "Tyler",
          "lastName" : "Ford",
          "gender" : "Male",
          "isAlive" : true,
          "relation_type" : {
            "name" : "child",
            "parent" : "1"
          }
        }
      },
      {
        "_index" : "family_tree",
        "_type" : "_doc",
        "_id" : "8",
        "_score" : 0.6931472,
        "_routing" : "Darren",
        "_source" : {
          "firstName" : "Xavier",
          "lastName" : "Ford",
          "gender" : "Male",
          "isAlive" : true,
          "relation_type" : {
            "name" : "child",
            "parent" : "1"
          }
        }
      }
    ]
执行上面的查询将获取“ Pearl”,“ Ava”,“ Tyler”和“ Xavier” Ford的记录。

查询拥有child及parent查询
查询关键字has_child和has_parent,有助于查询具有父子关系的数据。

获取所有育有女儿并且女儿已死的父母:has_child,关键字可帮助我们获取所有父母记录,其中孩子有过滤器。

GET family_tree/_search
{
  "query": {
    "has_child": {
      "type": "child",
      "query": {
        "bool": {
          "must": [
            {
              "match": {
                "gender": "Female"
              }
            },
            {
              "match": {
                "isAlive": false
              }
            }
          ]
        }
      }
    }
  }
}
查询的返回结果是:

    "hits" : [
      {
        "_index" : "family_tree",
        "_type" : "_doc",
        "_id" : "3",
        "_score" : 1.0,
        "_routing" : "Ryan",
        "_source" : {
          "firstName" : "Ryan",
          "lastName" : "Turner",
          "gender" : "Male",
          "isAlive" : false,
          "relation_type" : {
            "name" : "parent"
          }
        }
      }
    ]
执行上面的查询,将获得“Ryan Turner”的记录,Ryan Turner是唯一一个死去的女儿“Scarlet Turner”的父母。

获取所有父母性别为“female”的child:has_parent关键字可帮助我们获取所有有父母过滤条件的孩子记录。
GET family_tree/_search
{
  "query": {
    "has_parent": {
      "parent_type": "parent",
      "query": {
        "match": {
          "gender": "Female"
        }
      }
    }
  }
}
返回的结果:

    "hits" : [
      {
        "_index" : "family_tree",
        "_type" : "_doc",
        "_id" : "9",
        "_score" : 1.0,
        "_routing" : "Sienna",
        "_source" : {
          "firstName" : "Ralph",
          "lastName" : "Evans",
          "gender" : "Male",
          "isAlive" : true,
          "relation_type" : {
            "name" : "child",
            "parent" : "2"
          }
        }
      }
    ]
执行上述查询,获取“Ralph Evans”的记录,其父母是“Sienna Evans”,所有其他父母均为Male。

Parent 拥有多个children
让我们将“Melissa Ford”作为wife添加到“Darren Ford”中,如下图所示。“Darren”现在已附加了“children和wife”文件。

可以使用以下代码更改索引:

PUT family_tree/_mapping
{
  "properties": {
    "relation_type": {
      "type": "join",
      "eager_global_ordinals": true,
      "relations": {
        "parent": [
          "child",
          "wife"
        ]
      }
    }
  }
}
我们修改了上面的mapping,从而使得我们的relation_type具有一个“parent”及两个children: “child”及“wife”。

插入“Melissa Ford”文档与我们之前创建的子记录类似,这将使用与父路径“Darren”相同的路径参数,并使用“wife”作为relationship_type名称。

PUT family_tree/_doc/13?routing=Darren
{
  "firstName": "Melissa",
  "lastName": "Ford",
  "gender": "Female",
  "isAlive": false,
  "relation_type": {
    "name": "wife",
    "parent": "1"
  }
}
查询wife数据
获取有wife的“parent” (请注意这里的parent不是指的是妻子的父母,而是我们的数据关系的父母,也就是Daren。我们可以从上面的图中可以看出来):查询使用has_child关键字并按“wife”类型进行过滤。

GET family_tree/_search
{
  "query": {
    "has_child": {
      "type": "wife",
      "query": {
        "match_all": {}
      }
    }
  }
}
返回数据:

    "hits" : [
      {
        "_index" : "family_tree",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 1.0,
        "_routing" : "Darren",
        "_source" : {
          "firstName" : "Darren",
          "lastName" : "Ford",
          "gender" : "Male",
          "isAlive" : false,
          "relation_type" : {
            "name" : "parent"
          }
        }
      }
    ]
执行以上查询,获取“Darren Ford”的记录。

 

多级关系(孙子关系)
让我们将Grand Children添加到家谱中,如下图所示:

索引需要在这里重新创建! 这是由于另一个限制,只有在元素已经是父元素的情况下,才可以在现有元素中添加子元素。 由于在较早创建索引时“child”类型不是父类型,因此我们需要删除较早的索引,使用下面的代码创建一个新的索引,然后重新插入所有数据。

我们首先重新修改family_tree的mapping:

DELETE family_tree
 
PUT family_tree
{
  "settings": {
    "index": {
      "number_of_shards": 1,
      "number_of_replicas": 1
    }
  },
  "mappings": {
    "properties": {
      "firstName": {
        "type": "text"
      },
      "lastName": {
        "type": "text"
      },
      "gender": {
        "type": "text"
      },
      "isAlive": {
        "type": "boolean"
      },
      "relation_type": {
        "type": "join",
        "eager_global_ordinals": true,
        "relations": {
          "parent": [
            "child",
            "wife"
          ],
          "child": "grandchild"
        }
      }
    }
  }
}
在这里,“child”也成为“grandchild”类型的父母。 这使我们具有关系parent→child→grandchild。

我们先按照上面的顺序,把我们之前的数据导入到family_tree中。然后再导入我们的grandchild数据。插入“grandchild”文档与插入“child”记录非常相似。

PUT family_tree/_doc/14?routing=Darren
{
  "firstName": "Douglas",
  "lastName": "Ford",
  "gender": "Male",
  "isAlive": true,
  "relation_type": {
    "name": "grandchild",
    "parent": "5"
  }
}
在我们的示例中,“Douglas Ford”是“Pearl Ford”的子代,也是“Darren Ford”的孙代,请注意,我们使用与创建Darren记录相同的路由查询参数。 这样可以确保与超级父代“Darren”关联的所有子代都在同一分片上建立索引。

此记录与“Pearl Ford”之间的join由Relation_type字段进行,在该字段中,我们将关系的名称添加为“grandchild”,使“Douglas Ford”成为其ID为“5”的父代的孙代(同一个ID 我们创建了Pearl Ford)。

我们可以使用如下的代码把其它的grandchild的文档写入到Elasticsearch中:

PUT family_tree/_bulk?routing=Darren
{"index": {"_id": "14"}}
{"firstName":"Douglas", "lastName":"Ford", "gender":"Male", "isAlive":true, "relation_type":{ "name":"grandchild", "parent":"5" }}
 
PUT family_tree/_bulk?routing=Ryan
{"index": {"_id": "15"}}
{"firstName":"Frederick", "lastName":"Turner", "gender":"Male", "isAlive":false, "relation_type":{ "name":"grandchild", "parent":"11" }}
{"index": {"_id": "16"}}
{"firstName":"Eleanor", "lastName":"Turner", "gender":"Female", "isAlive":false, "relation_type":{ "name":"grandchild", "parent":"11" }}
{"index": {"_id": "17"}}
{"firstName":"Troy", "lastName":"Turner", "gender":"Male", "isAlive":false, "relation_type":{ "name":"grandchild", "parent":"11" }}
请求grandparent数据
获取所有拥有孙女的祖父母:

GET family_tree/_search
{
  "query": {
    "has_child": {
      "type": "child",
      "query": {
        "has_child": {
          "type": "grandchild",
          "query": {
            "match": {
              "gender": "Female"
            }
          }
        }
      }
    }
  }
}
返回的结果:

    "hits" : [
      {
        "_index" : "family_tree",
        "_type" : "_doc",
        "_id" : "3",
        "_score" : 1.0,
        "_routing" : "Ryan",
        "_source" : {
          "firstName" : "Ryan",
          "lastName" : "Turner",
          "gender" : "Male",
          "isAlive" : false,
          "relation_type" : {
            "name" : "parent"
          }
        }
      }
    ]
执行此查询将获得“Ryan Turner”记录,因为他是唯一拥有孙女“Eleanor Turner”的祖父母,如上面的图所示。

在这里我们必须指出的是:

不建议使用多个级别的关系来复制关系模型。 每个关系级别都会在查询时增加内存和计算方面的开销。 如果您关心性能,则应该对数据进行非规范化。 — elastic.co

 
Elasticsearch中的join限制
现在,我们已经看到了加入功能的作用,让我们回顾一下上面注意到的限制。

父文档和子文档必须在同一分片上建立索引。
每个索引仅允许一个join字段映射。
一个元素可以有多个子级,但只能有一个父级。
可以向现有join字段添加新关系。
也可以将子元素添加到现有元素中,但前提是该元素已经是父元素。
 
总结
当索引时间性能比搜索时间性能更重要时,父子联接可能是管理关系的有用技术,但代价是很高的。 必须意识到折衷方案,例如父子文档的物理存储约束和增加的复杂性。 另一个预防措施是避免多层父子关系,因为这将消耗更多的内存和计算量。
————————————————
 

以上是 Elasticsearch:在Elasticsearch中的join数据类型父子关系 的全部内容, 来源链接: utcz.com/z/517880.html

回到顶部