复合核心字段类型

除了之前提到的简单的标量类型,JSON还有null值,数组和对象,所有这些Elasticsearch都支持:

多值字段

我们想让tag字段包含多个字段,这非常有可能发生。我们可以索引一个标签数组来代替单一字符串:

{ "tag": [ "search", "nosql" ]}

对于数组不需要特殊的映射。任何一个字段可以包含零个、一个或多个值,同样对于全文字段将被分析并产生多个词。

言外之意,这意味着数组中所有值必须为同一类型。你不能把日期和字符窜混合。如果你创建一个新字段,这个字段索引了一个数组,Elasticsearch将使用第一个值的类型来确定这个新字段的类型。

当你从Elasticsearch中取回一个文档,任何一个数组的顺序和你索引它们的顺序一致。你取回的_source字段的顺序同样与索引它们的顺序相同。

然而,数组是做为多值字段被索引的,它们没有顺序。在搜索阶段你不能指定“第一个值”或者“最后一个值”。倒不如把数组当作一个值集合(bag of values)

空字段

当然数组可以是空的。这等价于有零个值。事实上,Lucene没法存放null值,所以一个null值的字段被认为是空字段。

这四个字段将被识别为空字段而不被索引:

"empty_string":             "",
"null_value":               null,
"empty_array":              [],
"array_with_null_value":    [ null ]

多层对象

我们需要讨论的最后一个自然JSON数据类型是对象(object)——在其它语言中叫做hash、hashmap、dictionary 或者 associative array.

内部对象(inner objects)经常用于在另一个对象中嵌入一个实体或对象。例如,做为在tweet文档中user_nameuser_id的替代,我们可以这样写:

{
    "tweet":            "Elasticsearch is very flexible",
    "user": {
        "id":           "@johnsmith",
        "gender":       "male",
        "age":          26,
        "name": {
            "full":     "John Smith",
            "first":    "John",
            "last":     "Smith"
        }
    }
}

内部对象的映射

Elasticsearch 会动态的检测新对象的字段,并且映射它们为 object 类型,将每个字段加到 properties 字段下

{
  "gb": {
    "tweet": { <1>
      "properties": {
        "tweet":            { "type": "string" },
        "user": { <2>
          "type":             "object",
          "properties": {
            "id":           { "type": "string" },
            "gender":       { "type": "string" },
            "age":          { "type": "long"   },
            "name":   { <3>
              "type":         "object",
              "properties": {
                "full":     { "type": "string" },
                "first":    { "type": "string" },
                "last":     { "type": "string" }
              }
            }
          }
        }
      }
    }
  }
}

<1> 根对象.

<2><3> 内部对象.

username字段的映射与tweet类型自己很相似。事实上,type映射只是object映射的一种特殊类型,我们将 object 称为根对象。它与其他对象一模一样,除非它有一些特殊的顶层字段,比如 _source, _all 等等。

内部对象是怎样被索引的

Lucene 并不了解内部对象。 一个 Lucene 文件包含一个键-值对应的扁平表单。 为了让 Elasticsearch 可以有效的索引内部对象,将文件转换为以下格式:

{
    "tweet":            [elasticsearch, flexible, very],
    "user.id":          [@johnsmith],
    "user.gender":      [male],
    "user.age":         [26],
    "user.name.full":   [john, smith],
    "user.name.first":  [john],
    "user.name.last":   [smith]
}

内部栏位可被归类至name,例如"first"。 为了区别两个拥有相同名字的栏位,我们可以使用完整路径,例如"user.name.first" 或甚至类型名称加上路径:"tweet.user.name.first"

注意: 在以上扁平化文件中,并没有栏位叫作user也没有栏位叫作user.name。 Lucene 只索引阶层或简单的值,而不会索引复杂的资料结构。

对象-数组

内部对象数组

最后,一个包含内部对象的数组如何索引。 我们有个数组如下所示:

{
    "followers": [
        { "age": 35, "name": "Mary White"},
        { "age": 26, "name": "Alex Jones"},
        { "age": 19, "name": "Lisa Smith"}
    ]
}

此文件会如我们以上所说的被扁平化,但其结果会像如此:

{
    "followers.age":    [19, 26, 35],
    "followers.name":   [alex, jones, lisa, smith, mary, white]
}

{age: 35}{name: Mary White}之间的关联会消失,因每个多值的栏位会变成一个值集合,而非有序的阵列。 这让我们可以知道:

  • 是否有26岁的追随者?

但我们无法取得准确的资料如:

  • 是否有26岁的追随者且名字叫Alex Jones?

关联内部对象可解决此类问题,我们称之为嵌套对象,我们之後会在嵌套对象中提到它。