事实上在Elasticsearch中,建立丶删除丶修改一个文档是是原子性的,所以咱们能够在一个文档中储存密切关联的实体。举例来讲,咱们能够在一个文档中储存一笔订单及其全部内容,或是储存一个Blog文章及其全部回应,藉由传递一个comments阵列: 缓存
PUT /my_index/blogpost/1 { "title": "Nest eggs", "body": "Making your money work...", "tags": [ "cash", "shares" ], "comments": [ <1> { "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" } ] }
<1> 若是咱们依靠动态映射,comments栏位会被自动创建为一个object栏位。 app
由于全部内容都在同一个文档中,使搜寻时并不须要链接(join)blog文章与回应,所以搜寻表现更加优异。 post
问题在於以上的文档可能会以下所示的匹配一个搜寻: ui
GET /_search { "query": { "bool": { "must": [ { "match": { "name": "Alice" }}, { "match": { "age": 28 }} <1> ] } } }
<1> Alice是31岁,而不是28岁! this
形成跨对象配对的缘由如同咱们在对象阵列中所讨论到,在于咱们优美结构的JSON文档在索引中被扁平化为下方的 键-值 形式: spa
{ "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类型的栏位用于储存单一对象是很是有用的。 从搜寻的角度来看,对於排序一个对象阵列来讲关联是不须要的东西。 设计
这是嵌套对象被设计来解决的问题。 藉由映射commments栏位为nested类型而不是object类型, 每一个嵌套对象会被索引为一个隐藏分割文档,例如: code
{ <1> "comments.name": [ john, smith ], "comments.comment": [ article, great ], "comments.age": [ 28 ], "comments.stars": [ 4 ], "comments.date": [ 2014-09-01 ] } { <2> "comments.name": [ alice, white ], "comments.comment": [ like, more, please, this ], "comments.age": [ 31 ], "comments.stars": [ 5 ], "comments.date": [ 2014-10-22 ] } { <3> "title": [ eggs, nest ], "body": [ making, money, work, your ], "tags": [ cash, shares ] }
<1> 第一个嵌套对象 orm
<2> 第二个嵌套对象 对象
<3> 根或是父文档
藉由分别索引每一个嵌套对象,对象的栏位中保持了其关联。 咱们的查询能够只在同一个嵌套对象都匹配时才回应。
不只如此,因嵌套对象都被索引了,链接嵌套对象至根文档的查询速度很是快--几乎与查询单一文档同样快。
这些额外的嵌套对象被隐藏起来,咱们没法直接访问他们。 为了要新增丶修改或移除一个嵌套对象,咱们必须从新索引整个文档。 要牢记搜寻要求的结果并非只有嵌套对象,而是整个文档。
设定一个nested栏位很简单--在你会设定为object类型的地方,改成nested类型:
PUT /my_index { "mappings": { "blogpost": { "properties": { "comments": { "type": "nested", <1> "properties": { "name": { "type": "string" }, "comment": { "type": "string" }, "age": { "type": "short" }, "stars": { "type": "short" }, "date": { "type": "date" } } } } } } }
<1> 一个nested栏位接受与object类型相同的参数。
所需仅此而已。 任何comments对象会被索引为分离嵌套对象。 参考更多 nested type reference docs。
因嵌套对象(nested objects)会被索引为分离的隐藏文档,咱们不能直接查询它们。而是使用 nested查询或nested 过滤器来存取它们:
GET /my_index/blogpost/_search { "query": { "bool": { "must": [ { "match": { "title": "eggs" }}, <1> { "nested": { "path": "comments", <2> "query": { "bool": { "must": [ <3> { "match": { "comments.name": "john" }}, { "match": { "comments.age": 28 }} ] }}}} ] }}}
<1> title条件运做在根文档上
<2> nested条件深刻嵌套的comments栏位。它不会在存取根文档的栏位,或是其余嵌套文档的栏位。
<3> comments.name以及comments.age运做在相同的嵌套文档。
TIP
一个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", <1> "query": { "bool": { "must": [ { "match": { "comments.name": "john" }}, { "match": { "comments.age": 28 }} ] }}}} ] }}}
<1> 从最匹配的嵌套文档中给予根文档的_score值。
注意
nested过滤器相似於nested查询,除了没法使用score_mode参数。 只能使用在filter context—例如在filtered查询中--其做用相似其余的过滤器: 包含或不包含,但不评分。
nested过滤器的结果自己不会缓存,一般缓存规则会被应用於nested过滤器之中的过滤器。
咱们能够依照嵌套栏位中的值来排序,甚至藉由分离嵌套文档中的值。为了使其结果更加有趣,咱们加入另外一个记录:
PUT /my_index/blogpost/2 { "title": "Investment secrets", "body": "What they don't tell you ...", "tags": [ "shares", "equities" ], "comments": [ { "name": "Mary Brown", "comment": "Lies, lies, lies", "age": 42, "stars": 1, "date": "2014-10-18" }, { "name": "John Smith", "comment": "You're making it up!", "age": 28, "stars": 2, "date": "2014-10-16" } ] }
想像咱们要取回在十月中有收到回应的blog文章,并依照所取回的各个blog文章中最少stars数量的顺序做排序。 这个搜寻请求以下:
GET /_search { "query": { "nested": { <1> "path": "comments", "filter": { "range": { "comments.date": { "gte": "2014-10-01", "lt": "2014-11-01" } } } } }, "sort": { "comments.stars": { <2> "order": "asc", <2> "mode": "min", <2> "nested_filter": { <3> "range": { "comments.date": { "gte": "2014-10-01", "lt": "2014-11-01" } } } } } }
<1> nested查询限制告终果为十月份收到回应的blog文章。
<2> 结果在全部匹配的回应中依照comment.stars栏位的最小值(min)做递增(asc)的排序。
<3> 排序条件中的nested_filter与主查询query条件中的nested查询相同。 於下一个下方解释。
为何咱们要在nested_filter重复写上查询条件? 缘由是排序在於执行查询后才发生。 此查询匹配了在十月中有收到回应的blog文章,回传blog文章文档做为结果。 若是咱们不加上nested_filter条件,咱们最後会依照任何blog文章曾经收到过的回应做排序,而不是在十月份收到的。
如同咱们在查询时须要使用nested查询来存取嵌套对象,专门的nested集合使咱们能够取得嵌套对象中栏位的集合:
GET /my_index/blogpost/_search?search_type=count { "aggs": { "comments": { <1> "nested": { "path": "comments" }, "aggs": { "by_month": { "date_histogram": { <2> "field": "comments.date", "interval": "month", "format": "yyyy-MM" }, "aggs": { "avg_stars": { "avg": { <3> "field": "comments.stars" } } } } } } } }
<1> nested集合深刻嵌套对象的comments栏位
<2> 评论基於comments.date栏位被分至各个月份分段
<3> 每月份分段单独计算星号的平均数
结果显示集合发生於嵌套文档层级:
... "aggregations": { "comments": { "doc_count": 4, <1> "by_month": { "buckets": [ { "key_as_string": "2014-09", "key": 1409529600000, "doc_count": 1, <1> "avg_stars": { "value": 4 } }, { "key_as_string": "2014-10", "key": 1412121600000, "doc_count": 3, <1> "avg_stars": { "value": 2.6666666666666665 } } ] } } } ...
<1> 此处总共有四个comments: 一个在九月以及三个在十月
一个nested集合只能存取嵌套文档中的栏位,而没法看见根文档或其余嵌套文档中的栏位。 然而,咱们能够跳出嵌套区块,藉由reverse_nested集合回到父阶层。
举例来讲,咱们能够发现使用评论者的年龄为其加上tags颇有趣。 comment.age是在嵌套栏位中,可是tags位於根文档:
GET /my_index/blogpost/_search?search_type=count { "aggs": { "comments": { "nested": { <1> "path": "comments" }, "aggs": { "age_group": { "histogram": { <2> "field": "comments.age", "interval": 10 }, "aggs": { "blogposts": { "reverse_nested": {}, <3> "aggs": { "tags": { "terms": { <4> "field": "tags" } } } } } } } } } }
<1> nested集合深刻comments对象
<2> histogram集合以comments.age栏位汇集成每十年一个的分段
<3> reverse_nested集合跳回到根文档
<4> terms集合计算每一个年龄分段的火红词语
简略的结果显示以下:
.. "aggregations": { "comments": { "doc_count": 4, <1> "age_group": { "buckets": [ { "key": 20, <2> "doc_count": 2, <2> "blogposts": { "doc_count": 2, <3> "tags": { "doc_count_error_upper_bound": 0, "buckets": [ <4> { "key": "shares", "doc_count": 2 }, { "key": "cash", "doc_count": 1 }, { "key": "equities", "doc_count": 1 } ] } } }, ...
<1> 共有四个评论
<2> 有两个评论的发表者年龄介於20至30之间
<3> 两个blog文章与这些评论相关
<4> 这些blog文章的火红标签是shares丶cash丶equities
嵌套对象对於当有一个主要实体(如blogpost),加上有限数量的紧密相关实体(如comments)是很是有用的。 有办法能以评论内容找到blog文章颇有用,且nested查询及过滤器提供短查询时间链接(fast query-time joins)。
嵌套模型的缺点以下:
如欲新增丶修改或删除一个嵌套文档,则必须从新索引整个文档。所以越多嵌套文档形成越多的成本。
搜寻请求回传整个文档,而非只有匹配的嵌套文档。 虽然有个进行中的计画要支持只回传根文档及最匹配的嵌套文档,但目前并未支持。
有时你须要完整分离主要文档及其关连实体。 父-子关系提供这一个功能。