elasticsearch之modeling your data(not flat)--nested objects

来源:互联网 发布:网络布线的工作量 编辑:程序博客网 时间:2024/05/29 04:01

我们常会有这样的应用场景:把跟某一个实体相关的实体存储在一个document中。比如我们会存储博客与之相关的评论。

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"    }  ]}
如果我们依赖动态mapping,comments字段将会作为一个object创建。

因为所有关于一个博客的所有评论都存储在一个document中,所以查询过程中就可以很快速的检索。但是问题来了,以下查询仍然会成立:

GET /_search{  "query": {    "bool": {      "must": [        { "match": { "name": "Alice" }},        { "match": { "age":  28      }}       ]    }  }}
这显然不是我们想要的结果。为什么会出现这种情形?

原因在于:我们结构化的json数据在es中被扁平化存储了,如下:

{  "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.

object类型对于存储单一实体是有效的,但是在数组情况下,就无效了,会丧失实体内的关系。

这就是es提供nested objects的目的,来解决以上问题。通过mapping将comments字段设置为nested,每一个nested object就会在索引中作为单一的document存储,如下:

{   "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 ]}
通过把没一个nested object单独存储(nested 跟 root 处在同一个level上),其fields保持了之间的关系。因此以上查询过程中出现的问题就解决了。不仅如此,由于采用了这样的存储方式,nested object跟root之间的连接查询也变得非常高效,就像是在同一个document中一样。

这种nested document是隐藏存储的,我们不能直接访问。更新、添加或者删除一个nested object,必须reindex整个document。查询结果也不仅仅是返回nested object,而是返回整个document。

1:nested object mapping

设置非常简单:

PUT /my_index{  "mappings": {    "blogpost": {      "properties": {        "comments": {          "type": "nested",           "properties": {            "name":    { "type": "string"  },            "comment": { "type": "string"  },            "age":     { "type": "short"   },            "stars":   { "type": "short"   },            "date":    { "type": "date"    }          }        }      }    }  }}
2:query a nested object

因为nested objet 在索引中是作为隐藏文档的形式存在的,所以我们不能直接query,而应该利用nested query 或者 nested filter去访问:

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 query会去匹配多个nested object,每一个match都会产生一个score值,但是这些score值需要统一成一个score跟root级别的query一起使用。默认是采用average的方式,es也提供了score_mode:avg,max,min,node。

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     }}                ]        }}}}      ]}}}
3:sorting by nested fields

即使value值存在于一个单独的nested document中,针对某一个nested field进行排序也是可以的。

假设我们想要得到在十月份收到评论的博客,按照这些博客评论中最小的star值进行排序(我们先看下这个查询:十月份收到评论的博客中,每一个博客都有多条评论,因此排序根据这些评论中的最小值进行也就不足为奇了,但是我们的检索结果只是想得到这些博客而已,而不是评论。)

GET /_search{  "query": {    "nested": {       "path": "comments",      "filter": {        "range": {          "comments.date": {            "gte": "2014-10-01",            "lt":  "2014-11-01"          }        }      }    }  },  "sort": {    "comments.stars": {       "order": "asc",         "mode":  "min",         "nested_filter": {         "range": {          "comments.date": {            "gte": "2014-10-01",            "lt":  "2014-11-01"          }        }      }    }  }}
query部分不难理解,sort中为啥还要嵌套一个跟query一样的查询呢???

首先sort是针对query的结果进行排序的,但是query的返回结果是博客信息(query筛选了一部分博客信息,这些博客信息存在10月份的评论,但并不意味着这一篇博客不存在其他月份的评论,而这些信息都是跟随query信息一起返回的,而如果一篇博客有10篇评论而都不在10月份,则这个结果不会返回),如果不执行一次筛选的话,排序的依据就可能不仅仅是10月份的的博客信息了。

4:nested aggregations

如同我们可以用nested query去访问nested object一样,我们也可以用nested aggregation对nested object中的fields进行操作。

GET /my_index/blogpost/_search?search_type=count{  "aggs": {    "comments": {       "nested": {        "path": "comments"      },      "aggs": {        "by_month": {          "date_histogram": {             "field":    "comments.date",            "interval": "month",            "format":   "yyyy-MM"          },          "aggs": {            "avg_stars": {              "avg": {                 "field": "comments.stars"              }            }          }        }      }    }  }}
上面的aggregation先按照comments.date字段进行bucket,然后每一个bucket根据comment.stars字段进行avg。
nested aggregation只能访问nested document中的field,不能访问root cocument 或者 不同的nested document。然而使用reverse_nested aggregation可以跳出nested scope,去访问parent level的数据.
比如:我们想要得到发表评论的人感兴趣的tags,以评论者的年龄区分。这里comment.age是一个nested field,而tags是一个root document。
GET /my_index/blogpost/_search?search_type=count{  "aggs": {    "comments": {      "nested": {         "path": "comments"      },      "aggs": {        "age_group": {          "histogram": {             "field":    "comments.age",            "interval": 10          },          "aggs": {            "blogposts": {              "reverse_nested": {},               "aggs": {                "tags": {                  "terms": {                     "field": "tags"                  }                }              }            }          }        }      }    }  }}
以上:histogtam agg用comment.age进行bucket,然后reverse_nested aggs到了root document,然后term aggs对上边的没一个bucket中的数据根据tags进行buckent统计。
总结:什么时候使用nested objects呢?
Nested objects适用与一个主实体(blog),关联几个有限数目的不太重要的实体(comment)的情况。利于根据次要实体的内容查找符合条件的主实体,并且nested query和filter提供了快速的query-time join性能。
nested model 的不足之处在于:
添加,更新,删除一个nested document,整个document必须reindexed。nested object越多,代价越高。
查询请求返回完整的document,并不仅仅是match到的nested objects(目前es这块有打算去做,但是现在还是不支持)。
有些应用场景下你可能需要在main document和associated entity做完全的分离,这种分离由parent-child relationship提供。

0 0
原创粉丝点击