由于在 Elasticsearch 中单个文档的增删改都是原子性操作,那么将相关实体数据都存储在同一文档中也就理所当然。 比如说,我们可以将订单及其明细数据存储在一个文档中。又比如,我们可以将一篇博客文章的评论以一个 comments
数组的形式和博客文章放在一起:
PUT /my_index/blogpost/1 { "title": "Nest eggs", "body": "Making your money work...", "tags": [ "cash", "shares" ], "comments": [ { "name": "John Smith", "comment": "Great article", "age": 28, "stars": 4, "date": "2014-09-01" }, { "name": "Alice White", "comment": "More like this please", "age": 31, "stars": 5, "date": "2014-10-22" } ] }
如果我们依赖字段自动映射,那么 |
由于所有的信息都在一个文档中,当我们查询时就没有必要去联合文章和评论文档,查询效率就很高。
但是当我们使用如下查询时,上面的文档也会被当做是符合条件的结果:
GET /_search { "query": { "bool": { "must": [ { "match": { "name": "Alice" }}, { "match": { "age": 28 }} ] } } }
Alice实际是31岁,不是28! |
正如我们在 对象数组 中讨论的一样,出现上面这种问题的原因是 JSON 格式的文档被处理成如下的扁平式键值对的结构。
{ "title": [ eggs, nest ], "body": [ making, money, work, your ], "tags": [ cash, shares ], "comments.name": [ alice, john, smith, white ], "comments.comment": [ article, great, like, more, please, this ], "comments.age": [ 28, 31 ], "comments.stars": [ 4, 5 ], "comments.date": [ 2014-09-01, 2014-10-22 ] }
Alice
和 31 、 John
和 2014-09-01
之间的相关性信息不再存在。虽然 object
类型 (参见 内部对象) 在存储 单一对象 时非常有用,但对于对象数组的搜索而言,毫无用处。
嵌套对象 就是来解决这个问题的。将 comments
字段类型设置为 nested
而不是 object
后,每一个嵌套对象都会被索引为一个 隐藏的独立文档 ,举例如下:
{ "comments.name": [ john, smith ], "comments.comment": [ article, great ], "comments.age": [ 28 ], "comments.stars": [ 4 ], "comments.date": [ 2014-09-01 ] } { "comments.name": [ alice, white ], "comments.comment": [ like, more, please, this ], "comments.age": [ 31 ], "comments.stars": [ 5 ], "comments.date": [ 2014-10-22 ] } { "title": [ eggs, nest ], "body": [ making, money, work, your ], "tags": [ cash, shares ] }
第一个 | |
第二个 | |
根文档 或者也可称为父文档 |
在独立索引每一个嵌套对象后,对象中每个字段的相关性得以保留。我们查询时,也仅仅返回那些真正符合条件的文档。
不仅如此,由于嵌套文档直接存储在文档内部,查询时嵌套文档和根文档联合成本很低,速度和单独存储几乎一样。
嵌套文档是隐藏存储的,我们不能直接获取。如果要增删改一个嵌套对象,我们必须把整个文档重新索引才可以。值得注意的是,查询的时候返回的是整个文档,而不是嵌套文档本身。
https://www.elastic.co/guide/cn/elasticsearch/guide/current/nested-objects.html#nested-objects
由于嵌套对象 被索引在独立隐藏的文档中,我们无法直接查询它们。 相应地,我们必须使用 nested
查询去获取它们:
GET /my_index/blogpost/_search { "query": { "bool": { "must": [ { "match": { "title": "eggs" } }, { "nested": { "path": "comments", "query": { "bool": { "must": [ { "match": { "comments.name": "john" } }, { "match": { "comments.age": 28 } } ] } } } } ] }}}
| |
| |
|
nested
字段可以包含其他的 nested
字段。同样地,nested
查询也可以包含其他的 nested
查询。而嵌套的层次会按照你所期待的被应用。
nested
查询肯定可以匹配到多个嵌套的文档。每一个匹配的嵌套文档都有自己的相关度得分,但是这众多的分数最终需要汇聚为可供根文档使用的一个分数。
默认情况下,根文档的分数是这些嵌套文档分数的平均值。可以通过设置 score_mode 参数来控制这个得分策略,相关策略有 avg
(平均值), max
(最大值), sum
(加和) 和 none
(直接返回 1.0
常数值分数)。
GET /my_index/blogpost/_search { "query": { "bool": { "must": [ { "match": { "title": "eggs" } }, { "nested": { "path": "comments", "score_mode": "max", "query": { "bool": { "must": [ { "match": { "comments.name": "john" } }, { "match": { "comments.age": 28 } } ] } } } } ] } } }
返回最优匹配嵌套文档的 |
如果 nested
查询放在一个布尔查询的 filter
子句中,其表现就像一个 nested
查询,只是 score_mode
参数不再生效。因为它被用于不打分的查询中 — 只是符合或不符合条件,不必打分 — 那么 score_mode
就没有任何意义,因为根本就没有要打分的地方。
https://www.elastic.co/guide/cn/elasticsearch/guide/current/nested-query.html