14.ES 之 nested 详解(2019-05-22)

1.问题引入:
由于在 ES 里新建、删除、更新单个文档都是原子性的,那么将相关实体保存在同一文档里面是有意义的。
PUT /blog/_doc/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"
        }
    ]
}

因为所有的内容都在同一文档里,在查询的时候就没有必要拼接多份文档,因此检索性能会更好。但是,上面的文档会匹配这样的一个查询:
POST /blog/_search
{
    "query":{
        "bool":{
            "must":[
                {
                    "match":{
                        "comments.name":"Alice"
                    }
                },
                {
                    "match":{
                        "comments.age":28
                    }
                }
            ]
        }
    }
}

居然有结果!但是 Alice 是 31,不是 28 啊!
造成这种交叉对象匹配的原因是因为 JSON 文档在 Lucene 底层会被打平成一个简单的键值格式,就像这样:
{
  "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 类型的字段对于保存单一的 object 很有用,但是从检索的角度来说,这对于保存一个 object 数组却是无用的。

 

2.引入 nested 解决问题:
nested object 就是为了解决上述问题而设计出来的。通过将 comments 字段映射为 nested 类型,而不是 object 类型,每个 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"]
}

通过分开给每个 nested object 进行保存,object 内部字段间的关系就能保持。当执行查询时,只会匹配同时出现在相同的 nested object 里的结果。不仅如此,由于 nested objects 保存数据的方式,在查询的时候将根文档和 nested objects 文档拼接是很快的,就跟把他们当成一个单独的文档一样快。这些额外的 nested objects 文档是隐藏的,我们不能直接接触。为了更新、增加或者移除一个 nested 对象,必须重新插入整个文档。要记住一点:查询请求返回的结果不仅仅包括 nested 对象,而是整个文档。

 

3.nested 设置 mapping:
1).删除之前的索引:
DELETE  /blog

2).创建一个nested 字段很简单,只要在你通常指定 object 类型的地方,改成 nested 类型就行:
PUT /blog
{
  "mappings": {
    "properties": {
      "title": {
        "type": "text"
      },
      "body": {
        "type": "text"
      },
      "tags": {
        "type": "keyword"
      },
      "comments": {
        "type": "nested",
        "properties": {
          "name": {
            "type": "text"
          },
          "comment": {
            "type": "text"
          },
          "age": {
            "type": "short"
          },
          "stars": {
            "type": "short"
          },
          "date": {
            "type": "date"
          }
        }
      }
    }
  }
}

3).插入之前的文档:
PUT /blog/_doc/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"
        }
    ]
}

4).再用以前的方法就搜索不到了:
POST /blog/_search
{
    "query":{
        "bool":{
            "must":[
                {
                    "match":{
                        "comments.name":"Alice"
                    }
                },
                {
                    "match":{
                        "comments.age":28
                    }
                }
            ]
        }
    }
}

 

4.nested 之搜索:
nested object 作为一个独立隐藏文档单独建索引,因此,我们不能直接查询它们。取而代之,我们必须使用 nested 查询或者 nested filter 来获取它们:
GET /blog/_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 查询可以匹配多个 nested 文档。每个匹配的 nested 文档都有它自己相关评分,但是这些评分必须归为一个总分应用于根文档上。默认会平均所有匹配的 nested 文档的分数。当然,也可以通过设定 score_mode 参数为 avg,max,sum 或者甚至为none(根文档获得一致评分1.0)。
GET /blog/_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 过滤跟 nested 查询非常像,只是过滤不接受评分。

 

5.nested 之排序:
1).我们可以基于 nested 字段的值进行排序。再插入一份文档:
PUT /blog/_doc/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"
        }
    ]
}

2).设想我们想要检索在10月份被评论的博客文章,同时按每篇文章收到的最低星级排序。检索请求应该类似如下:
POST /blog/_search
{
  "query": {
    "nested": {
      "path": "comments",
      "query": {
        "range": {
          "comments.date": {
            "gte": "2014-10-01",
            "lt": "2014-11-01"
          }
        }
      }
    }
  },
  "sort": {
    "comments.stars": {
      "order": "asc",
      "mode": "min"
    }
  }

}

 

6.nested 之聚合:
1).与在搜索时需要使用特定的 nested 查询来获取 nested object 一样,特定的 nested 聚合同样能让我们对 nested object 内的字段进行聚合:
POST /blog/_search
{
    "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"
                            }

                        }
                    }
                }
            }
        }
    }
}

①:nested 聚合进入 nested 评论对象。
②:评论基于 comments.date 字段聚合成月。
③:对于每一个簇,计算 stars 的平均值。
结果表明,聚合的确发生在 nested 文本层:
...
"aggregations": {
  "comments": {
     "doc_count": 4, 
     "by_month": {
        "buckets": [
           {
              "key_as_string": "2014-09",
              "key": 1409529600000,
              "doc_count": 1, 
              "avg_stars": {
                 "value": 4
              }
           },
           {
              "key_as_string": "2014-10",
              "key": 1412121600000,
              "doc_count": 3, 
              "avg_stars": {
                 "value": 2.6666666666666665
              }
           }
        ]
     }
  }
}


2).反嵌套聚合:
一个 nested 聚合只能接入 nested 文本内的字段,它不能看到根文本或者不同 nested 文本内的字段。但是,我们可以通过一个反嵌套聚合跳出 nested 局域进入父层。比如我们基于评论者的年龄找出哪些标签是评论者感兴趣的。comment.age 是一个 nested 字段,而 tags 位于根文本中:
POST /blog/_search
{
    "aggs":{
        "comments":{
            "nested":{①
                "path":"comments"
            },

            "aggs":{
                "age_group":{
                    "histogram":{②
                        "field":"comments.age",
                        "interval":10
                    },

                    "aggs":{
                        "blogposts":{
                            "reverse_nested":{},③
                            "aggs":{
                                "tags":{
                                    "terms":{④
                                        "field":"tags"
                                    }

                                }
                            }
                        }
                    }
                }
            }
        }
    }
}

①:nested 聚合进入 nested 评论对象。
②:histogram(直方图)聚合在 comments.age 字段上分组,每10年一组。
③:reverse_nested 聚合跳转回根文本。
④:terms 聚合计算每个年龄组的流行 terms。
下面是简化结果:
..
"aggregations": {
  "comments": {
     "doc_count": 4, 
     "age_group": {
        "buckets": [
           {
              "key": 20, 
              "doc_count": 2, 
              "blogposts": {
                 "doc_count": 2, 
                 "tags": {
                    "doc_count_error_upper_bound": 0,
                    "buckets": [ 
                       { "key": "shares",   "doc_count": 2 },
                       { "key": "cash",     "doc_count": 1 },
                       { "key": "equities", "doc_count": 1 }
                    ]
                 }
              }
           },
...

 

7.使用 nested 对象的场景:
当有一个主要的实体,就像 blog ,还有有限的一些相关但是没这么重要的其他实体数组,比如 comments(评论),nested 对象就显得非常有用。基于评论的内容找到博客文章是有价值的。

 

8. nested 模式的缺点:
1).为了增加、改变或者删除一个 nested 文本,整个文本必须重建。nested 文本越多,代价就越大。
2).检索请求返回整个文本,而不仅是匹配的 nested 文本。


 

首先,这个错误提示是在编译过程中出现的,具体的错误是 C compiler cannot create executables,意思是 C 编译器无法生成可执行文件。那么解决这个问题,一般可以按照以下步骤来进行: 1. 检查编译器是否正确安装 这个问题的根源是编译器无法生成可执行文件,所以我们需要检查编译器是否正确安装。使用以下命令可以查看已经安装的编译器: ``` dpkg --get-selections | grep gcc ``` 如果没有安装或者安装的版本不正确,可以使用以下命令来安装: ``` sudo apt-get install build-essential ``` 2. 检查编译环境是否正确设置 使用以下命令可以检查编译环境是否正确设置: ``` echo $PATH ``` 如果没有包含编译器的路径,可以使用以下命令添加路径: ``` export PATH=$PATH:/usr/local/bin ``` 3. 检查是否有必要的依赖库 有些编译器需要依赖库才能正常工作,如果没有安装这些依赖库,也会出现 C 编译器无法生成可执行文件的问题。使用以下命令可以检查是否安装了 libtool: ``` sudo apt-get install libtool ``` 4. 检查系统架构 最后,还需要检查系统架构是否正确。根据提示信息,可以看到目标系统是 arm-ostl-linux-gnueabi,而本机的系统是 x86_64-pc-linux-gnu,因此需要在编译过程中指定目标系统的架构,比如: ``` ./configure --host=arm-ostl-linux-gnueabi ``` 通过以上步骤,一般就可以解决 C 编译器无法生成可执行文件的问题。如果仍然无法解决,可以查看 config.log 文件,里面会有详细的错误信息,根据错误信息再进行调试。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值