0%

elasticsearch连接查询

elasticsearch连接查询

在elasticsearch中提供了两种关联关系,一种是嵌套关系,一种是父子关系,可以进行这两种关联的查询

对象类型存在的问题

在说明连接查询之前,先展示一下对象类型进行查询时的问题

有一条文档

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
"name": "技术大会",
"events": [
{
"date": "2022-10-10",
"title": "java架构大会"
},
{
"date": "2022-08-08",
"title": "hadoop大会"
},
{
"date": "2022-02-02",
"title": "elasticsearch大会"
}
]
}

假设查询hadoop是否在2022-10-01和2022-12-31范围内

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{
"query": {
"bool": {
"must": [
{
"term": {
"events.title": "hadoop"
}
},
{
"range": {
"events.date": {
"from": "2022-10-01",
"to": "2022-12-31"
}
}
}
]
}
}
}

按理说不应该查到数据,但是

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
{
"took": 2,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"skipped": 0,
"failed": 0
},
"hits": {
"total": 1,
"max_score": 1.287682,
"hits": [
{
"_index": "test_object",
"_type": "default",
"_id": "r4UH2YYByIotaay27UN3",
"_score": 1.287682,
"_source": {
"name": "技术大会",
"events": [
{
"date": "2022-10-10",
"title": "java架构大会"
},
{
"date": "2022-08-08",
"title": "hadoop大会"
},
{
"date": "2022-02-02",
"title": "elasticsearch大会"
}
]
}
}
]
}
}

在存储的时候,对象类型的边界并未被考虑在内,导致了搜索结果不符合预期

elasticsearch会在内部将层级结构进行扁平化,使用每个内部字段的全路径,将其放入Lucene内的独立字段,变成了

1
2
3
4
5
{
"name":"技术大会",
"events.date":["2022-10-10","2022-08-08","2022-02-02"],
"events.title":["java架构大会","hadoop大会","elasticsearch大会"]
}

如果是一对一关系的话是没有问题的,但是一对多就会导致搜索结果不符合预期

嵌套查询

嵌套查询可以查询嵌套对象,执行的查询把嵌套对象作为父文档的单独文档

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"query": {
"nested" : {
"path" : "obj1", // 父对象,即嵌套对象的路径
"query" : { // 该查询是在path路径下的嵌套文档中执行,其内使用的字段必须要使用全路径
"bool" : {
"must" : [
{ "match" : {"obj1.name" : "blue"} },
{ "range" : {"obj1.count" : {"gt" : 5}} }
]
}
},
"score_mode" : "avg" // 设置内部子匹配如何影响父匹配的得分,默认是avg,还可以设置为sum/min/max/none
}
}
}

elasticsearch会将嵌套的内部对象索引到邻近的位置,但是保持独立的Lucene文档

还是上述的例子,我们把类型改为嵌套试一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
{
"mappings": {
"default": {
"properties": {
"events": {
"type": "nested",
"properties": {
"date": {
"type": "date"
},
"title": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
}
}
},
"name": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword",
"ignore_above": 256
}
}
}
}
}
}
}

再次使用原本的查询来进行查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
{
"query": {
"nested": { // 使用nested
"path": "events", // 通过path来告诉elasticsearch这些嵌套对象位于哪个Lucene分片中
"query": {
"bool": {
"must": [
{
"term": {
"events.title": "hadoop"
}
},
{
"range": {
"events.date": {
"from": "2022-10-01",
"to": "2022-12-31"
}
}
}
]
}
}
}
}
}

优缺点

优点
  • 嵌套类型知道对象的边界
  • 使用嵌套类型后,可以一次性索引整个文档
  • 嵌套查询和聚集将父辈和子辈的部分连接起来,可以在这个集合上运行任何查询
  • 在查询阶段的连接很快速,因为组成elasticsearch文档的所有Lucene文档都是放在同一分段中的同一分片中
缺点
  • 查询比对象要慢
  • 更新子辈需要重新索引整篇文档

父子查询

由于嵌套结构对于更新操作会索引整篇文档,所以经常更新的时候,使用嵌套就不合适了,可以使用父子文档,在映射中使用_parent字段来定义关系,父辈使用正常的方式索引,子辈需要在_parent字段中指定父辈id

父子文档在进行查询时是比较慢的,因为父辈和子辈文档是完全不同的elasticsearch文档,只能分别搜索它们

子文档查询

子文档查询将父文档拥有的查询返回匹配到的子文档

1
2
3
4
5
6
7
8
9
10
11
12
{
"query": {
"has_child" : {
"type" : "blog_tag",
"query" : {
"term" : {
"tag" : "something"
}
}
}
}
}

父文档查询

父文档查询在父文档中执行,通过parent_type指定,返回关联匹配的父文档的子文档

1
2
3
4
5
6
7
8
9
10
11
12
{
"query": {
"has_parent" : {
"parent_type" : "blog",
"query" : {
"term" : {
"tag" : "something"
}
}
}
}
}

es6.x

在es6.x之前一个索引下是可以有多个类型的,但是es6.x开始一个索引下只能有一个type,可以使用join类型来进行表示父子关系

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
PUT test_join
{
"mappings": {
"_doc": {
"properties": {
"my_join_field": {
"type": "join",
"relations": { // 定义父子关系
"question": "answer" // 表示question为父,answer是子
}
}
}
}
}
}

插入父数据如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
PUT test_join/_doc/1?refresh
{
"text": "This is a question",
"my_join_field": {
"name": "question"
}
}


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

插入子数据如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
PUT test_join/_doc/3?routing=1&refresh
{
"text": "This is an answer",
"my_join_field": {
"name": "answer",
"parent": "1"
}
}

PUT test_join/_doc/4?routing=1&refresh
{
"text": "This is another answer",
"my_join_field": {
"name": "answer",
"parent": "1"
}
}

使用routing是为了保证父子文档被索引到同一个分片

使用has_child查询一下

1
2
3
4
5
6
7
8
9
10
11
12
13
GET test_join/_doc/_search
{
"query": {
"has_child" : { // 是否有子文档,这里的type要填写配置关系时的子关系
"type" : "answer",
"query" : {
"term" : {
"text" : "another"
}
}
}
}
}

使用has_parent查询一下

1
2
3
4
5
6
7
8
9
10
11
12
13
GET test_join/_doc/_search
{
"query": {
"has_parent" : {// 是否有父文档,这里的parent_type要填写配置关系时的父关系
"parent_type" : "question",
"query" : {
"term" : {
"text" : "question"
}
}
}
}
}

使用parent_id查询一下

1
2
3
4
5
6
7
8
9
GET test_join/_doc/_search
{
"query": {
"parent_id": { // 使用parent_id可以找到属于该父文档id的子文档
"type": "answer", // type为子关系的名字
"id": "1" // id为父文档的id
}
}
}

欢迎关注我的其它发布渠道