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" : { "bool" : { "must" : [ { "match" : {"obj1.name" : "blue"} }, { "range" : {"obj1.count" : {"gt" : 5}} } ] } }, "score_mode" : "avg" } } }
|
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": { "path": "events", "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" } } } } } }
|
插入父数据如下
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" : "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" : "question", "query" : { "term" : { "text" : "question" } } } } }
|
使用parent_id查询一下
1 2 3 4 5 6 7 8 9
| GET test_join/_doc/_search { "query": { "parent_id": { "type": "answer", "id": "1" } } }
|