首页 > 编程笔记 > 通用技能 阅读:2

Elasticsearch object类型的用法(附带实例)

数据往往具有层级结构。例如,一个 email 对象可能包含 subject 等顶级字段,还有一个内部对象 attachments 用于存储附件信息,而 attachments 对象可能又包含文件名、类型等属性。

JSON 允许我们创建层级对象,即包装在其他对象中的对象。Elasticsearch 用一种特殊的数据类型来表示对象的层级结构,即 object 类型。

顶级的 subject 和 to 字段的数据类型分别是 text 和 keyword。因为 attachments 本身是一个对象,所以它的数据类型是 object。attachments 对象中的两个属性 filename 和 filetype 可以设置为 text 类型的字段。有了这些信息,我们就可以创建一个映射定义,代码如下:
PUT emails
{
  "mappings": {
    "properties": {  ←---   emails索引的顶级属性
      "to":{
        "type": "keyword"
      },
      "subject":{
        "type": "text"
      },
      "attachments":{  ←---   由二级属性组成的内部对象
        "properties": {
          "filename":{
            "type":"text"
          },
          "filetype":{
            "type":"text"
          }
        }
      }
    }
  }
}
值得注意的是 attachments 属性。该字段的类型是 object,因为它封装了另外两个字段。在 attachments 内部对象中定义的两个字段与在顶层声明的 subject 和 to 字段没有区别,只是它们位于下一层级。

一旦命令执行成功,我们就可以通过调用 GET emails/_mapping 命令来检查模式,响应结果如下:
{
  "emails" : {
    "mappings" : {
      "properties" : {  ←---  attachments 是一个包含其他字段的内部对象
        "attachments" : {  ←---  object类型默认是隐藏的
          "properties" : {
            "filename" : {
              "type" : "text",  ←---  和预期一样,显示了字段的类型
               . . .
}
响应包含 subject、to 和 attachments 作为顶级字段(为简洁起见,并未显示所有属性)。

attachments 对象又具有一些字段,这些字段被封装为带有字段及其定义的属性。当我们获取映射时(GET emails/_mapping),所有其他字段都会显示其关联的数据类型,而 attachments 不会:默认情况下,Elasticsearch 会把内部对象推断为 object 类型。

接下来,我们索引一个文档,代码如下:
PUT emails/_doc/1
{
  "to:":"johndoe@johndoe.com",
  "subject":"Testing Object Type",
  "attachments":{
    "filename":"file1.txt",
    "filetype":"confidential"
  }
}

现在我们已经为 emails 索引准备好了一个文档,可以对内部对象字段发出一个简单的搜索查询来获取相关文档(并证明我们的想法),示例代码如下:
GET emails/_search
{
  "query": {
    "match": {
      "attachments.filename": "file1.txt"
    }
  }
}
这个查询成功返回了我们存储的文档,因为文件名与我们的文档相匹配。注意,我们在 keyword 类型的字段上使用 term 查询,是因为我们希望匹配精确的字段的值(file1.txt)。

虽然 object 类型相当直接,但它有一个局限性:内部对象被扁平化,而不是作为单独的文档存储。这样做的缺点是,数组中索引的对象之间的关系会丢失。为了更好地理解这个局限性,我们来看一个具体的例子。

在之前的电子邮件示例中,attachments 字段被声明为 object 类型。虽然我们只为邮件创建了一个 attachment 对象,但实际上我们完全可以添加多个附件(电子邮件通常会有多个附件),示例代码如下:
PUT emails/_doc/2
{
  "to:":"mrs.doe@johndoe.com",
  "subject":"Multi attachments test",
  "attachments":[{
    "filename":"file2.txt",
    "filetype":"confidential"
  },{
    "filename":"file3.txt",
    "filetype":"private"
  }]
}
默认情况下,attachments 字段是 object 类型的:一个由附件文件数组组成的内部对象。注意,文件 file2.txt 的分类是 confidential,而 file3.txt 的分类是 private(见下表)。

表:附件名和分类
附件名 文件分类
file2.txt confidential
file3.txt private

我们的电子邮件文档已被索引,ID 为 2,并包含几个附件。让我们来处理一个简单的搜索需求:匹配文件名为 file2.txt 且分类为 private 的文档。查看上表中的数据,该查询不应该返回任何结果,因为 file2.txt 的分类是 confidential,不是 private。让我们查询并检查结果。

为此,我们需要使用一种称为复合查询(compound query)的高级查询,它将各种叶子查询组合起来,创建一个复杂查询。其中一种复合查询就是 bool 查询。

在不详细说明 bool 查询是如何构造的情况下,我们先来看看它的实际运用。我们将在 bool 查询中使用另外两个查询子句:
bool 查询示例代码如下:
GET emails/_search   ←---  bool查询搜索匹配的文件名和文件分类
{
  "query": {
    "bool": {  ←---  将查询定义为bool查询
      "must": [   ←---  must子句定义了必须满足的条件
        {"term": { "attachments.filename.keyword": "file2.txt"}},
        {"term": { "attachments.filetype.keyword": "private" }}
      ]
    }
  }
}

当执行这个查询时,它返回以下文档:
"hits" : [[
{
  ...
  "_source" : {
    "to:" : "mrs.doe@johndoe.com",
    "subject" : "Multi attachments test",
    "attachments" : [
    {
      "filename" : "file2.txt",
      "filetype" : "confidential"
    },
    ..
   ]
}
}]
但是,这个结果并不正确。实际上,并不存在文件名为 file2.txt 且分类为 private 的文档(可以重新查看上表进行确认)。这个问题揭示了 object 数据类型的局限性,就是它无法维护内部对象之间的关系。

理想情况下,值 file2.txt 和 private 存储在不同的对象中,因此搜索不应将它们视为单个实体。原因在于内部对象不作为单独的文档存储,它们被扁平化了:
{
  ...
  "attachments.filename" :["file1.txt","file2.txt","file3.txt"]
  "attachments.filetype":["private","confidential"]
}
正如所见,filename 被收集为数组并存储在 attachments.filename 字段中,filetype 被收集为数组并存储在 attachments.filetype 字段中。遗憾的是,由于它们以这种方式存储,因此它们之间的关系丢失了。我们无法确定 file1.txt 是 private 还是 confidential,因为数组不保存该状态。

这就是当我们将对象数组索引到一个字段中并尝试将这些对象作为单独文档来搜索时所面临的局限性。好消息是,一种称为 nested 的数据类型解决了这个问题。

相关文章