数据类型

  • 常见类型
  • binary:接受二进制值作为 Base64 编码的字符串。默认情况下,该字段不存储,也不可搜索,不能包含换行符 \n
  • boolean:布尔类型,可以接受 truefalse ,可以使用字符串和直接到布尔类型,空字符串为 false,包含:truefalse"true""false"""
  • keyword:关键字类型,不进行分词,直接索引,支持模糊、支持精确匹配,支持聚合、排序操作,用于筛选数据。最大支持的长度为——32766 个 UTF-8 类型的字符。
  • number:数字类型
• long
• integer
• short
• byte
• double
• float
• half_float
• scaled_float
• unsigned_long
  • Dates:日期类型
  • date:可以是格式化后的日期字符串,也可以是时间戳,例如 2015-01-012015-01-01T12:10:30Z1420070400001
  • date_nanos:支持纳秒的日期格式,在 es 内部是存的长整型
  • alias :别名类型
  • 对象和关系类型
  • object:对象类型,是一个 json 对象
  • flattened:将对象作为单个字段值存储
  • nested:嵌套数据类型,可以看成是一个特殊的对象类型,可以让对象数组独立检索
  • join:同一个文档,但具有父子关系的,类似于树
  • 结构化数据类型
  • range:范围类型,可以用来表示数据的区间
  • integer_range
  • float_range
  • long_range
  • double_range
  • date_range
  • ip_range

日期类型

JSON 没有日期数据类型,因此 Elasticsearch 中的日期可以是:

date 类型在 Elasticsearch 展示的格式有下面几种:

  • 将日期时间格式化后的字符串,如 "2015-01-01" 或者 "2015/01/01 12:10:30"
  • long 型的整数,意义是 milliseconds-since-the-epoch,翻译一下就是自 1970-01-01 00:00:00 UTC 以来经过的毫秒数。
  • int 型的整数,意义是 seconds-since-the-epoch, 是指自 1970-01-01 00:00:00 UTC 以来经过的秒数。

后两种的描述里都包含 UTC,什么是 UTC 呢?

UTC(Universal Time Coordinated) 叫做世界统一时间,中国大陆和 UTC 的时差是 + 8 ,也就是 UTC+8。

不论 date 是什么展示格式,在 Elasticsearch 内部存储时都是转换成 UTC,并且把时区也会计算进去,从而得到 milliseconds-since-the-epoch 并作为存储的格式。

在查询日期时,会执行下面的过程:

  1. 转换成 long 整形格式的范围 (range) 查询
  2. 得到聚合的结果
  3. 将结果中的 date 类型(long 整型数据)根据 date format 字段转换回对应的展示格式

Date 的默认格式

Date 的格式化类型是可以通过 format 来指定的,如果没有指定,就会使用默认的格式:

"strict_date_optional_time||epoch_millis"

这表示什么意思呢?

先来弄懂 strict_date_optional_time

A generic ISO datetime parser where the date is mandatory and the time is
optional. Full details here.

这是 elasticsearch 官网的解释,表示只要是 ISO datetime parser 可以正常解析的都是 strict_date_optional_time。都有哪些语法呢?

date-opt-time     = date-element ['T' [time-element] [offset]]
 date-element      = std-date-element | ord-date-element | week-date-element
 std-date-element  = yyyy ['-' MM ['-' dd]]
 ord-date-element  = yyyy ['-' DDD]
 week-date-element = xxxx '-W' ww ['-' e]
 time-element      = HH [minute-element] | [fraction]
 minute-element    = ':' mm [second-element] | [fraction]
 second-element    = ':' ss [fraction]
 fraction          = ('.' | ',') digit+

其中中括号内的都是可选的,可填可不填。以 std-date_element 举个例子

2018-11-19
2018
2018-11

上面 3 种格式都满足要求。

除了 strict_date_optional_time ,还可以是 epoch_millis 格式,即 epoch 以来的毫秒数。

举个例子

PUT my_index
{
  "mappings": {
    "_doc": {
      "properties": {
        "date": {
          "type": "date" 
        }
      }
    }
  }
}

PUT my_index/_doc/1
{ "date": "2015-01-01" } 

PUT my_index/_doc/2
{ "date": "2015-01-01T12:10:30Z" } 

PUT my_index/_doc/3
{ "date": 1420070400001 } 

GET my_index/_search
{
  "sort": { "date": "asc"}

上面的 PUT 请求中的 date 数据均满足默认的要求。

如何指定多个 date 格式

同一个 date 字段可以指定多个 date 格式,只要使用 || 分隔就可以了。在索引,都会对 date 格式挨个进行匹配,直到找到匹配的格式为止。

如果存储时 date 格式为 milliseconds-since-the-epoch ,在查询时会将其转换为指定的第一个 date 格式。

举个例子,有兴趣的同学可在 sense 中动手实践下。

PUT my_index
{
  "mappings": {
    "doc": {
      "properties": {
        "date": {
          "type":   "date",
          "format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"
        }
      }
    }
  }
}


PUT /my_index/doc/1
{ "date": "2018-09-24 19:23:45" }


PUT /my_index/doc/2
{ "date": "2018-09-25" }

GET my_index/_search
{
  "query": {
    "match_all": {}
  }
}

date_nanos 类型,支持纳秒

date 类型支持到毫秒,如果特殊情况下用到纳秒得用 date_nanos 这个类型。

和普通的 date 类型一样,可以存 strict_date_optional_time||epoch_millis 这些格式的,不过在 es 内部是存的长整型是纳秒单位的

可以存一个纳秒的试试

PUT my_index/_doc/2
{ "date": "2015-01-01T12:10:30.123456789Z" }

keyword

keyword 类型字段可以设置 ignore_above 属性 (默认是 10) ,表示最大的字段值长度,超出这个长度的字段将不会被索引,但是会存储。也就是说,如果你超过了这个长度,模糊查询这个字段是查不到的,但查询全部是可以查询到的

别名类型

可以给字段起个别名,在搜索的时候可以使用别名搜索

官网示例

PUT trips
{
  "mappings": {
    "properties": {
      "distance": {
        "type": "long"
      },
      "route_length_miles": {
        "type": "alias",
        "path": "distance" 
      },
      "transit_mode": {
        "type": "keyword"
      }
    }
  }
}

GET _search
{
  "query": {
    "range" : {
      "route_length_miles" : {
        "gte" : 39
      }
    }
  }
}

path 就是目标字段的名称,不过有一些限制

  • 目标必须是具体字段,而不是对象或其他字段别名。
  • 创建别名时目标字段必须存在。
  • 如果定义了嵌套对象,则字段别名必须与其目标具有相同的嵌套范围。

字段别名只能指向一个字段,不能同时指向多个字段

不能在写入数据的时候使用字段别名,因为本身字段别名是虚拟的,不存在的,所以不支持写入,同样也不能用于 copy_to

因为字段的别名是不存在 _source中的,所以搜索请求时的过滤字段也是不会生效的

对象类型

object

JSON 字符串允许嵌套对象,一个文档可以嵌套多个、多层对象。可以通过对象类型来存储二级文档,不过由于 Lucene 并没有内部对象的概念,ES 会将原 JSON 文档扁平化,例如文档:

PUT my-index-000001/_doc/1
{ 
  "region": "US",
  "manager": { 
    "age":     30,
    "name": { 
      "first": "John",
      "last":  "Smith"
    }
  }
}

实际上 ES 会将其转换为以下格式,并通过 Lucene 存储,即使 name 是 object 类型

{
  "region":             "US",
  "manager.age":        30,
  "manager.name.first": "John",
  "manager.name.last":  "Smith"
}

上面的文档映射关系是

PUT my-index-000001
{
  "mappings": {
    "properties": { 
      "region": {
        "type": "keyword"
      },
      "manager": { 
        "properties": {
          "age":  { "type": "integer" },
          "name": { 
            "properties": {
              "first": { "type": "text" },
              "last":  { "type": "text" }
            }
          }
        }
      }
    }
  }
}

不用设置 type:object,因为这是默认值

flattened

官方文档:

默认情况下,对象中的每个子字段都是单独映射和索引的。如果子字段的名称或类型事先不知道,然后动态映射。但 flattened 可以提供其中整个对象映射为单个字段

他将整个对象映射为单个字段。给定一个对象,映射将解析出它的叶子节点的值,并将它们作为关键字索引到一个字段中

为了更好地处理包含大量或未知数量字段的文档而引入的 Elasticsearch 扁平化数据类型 flattened datatype。 默认情况下,Elasticsearch 会在提取文档时自动映射文档中包含的字段。虽然这是开始使用 Elasticsearch 的最简单方法,但随着时间的推移,它往往会导致字段爆炸,并且 Elasticsearch 的性能将受到“内存不足(out of memory)”错误和索引和查询数据时性能不佳的影响。

这种被称为“mapping explosions”的情况实际上很常见。这就是 Flattened 数据类型旨在解决的问题。

官网例子

PUT bug_reports
{
  "mappings": {
    "properties": {
      "title": {
        "type": "text"
      },
      "labels": {
        "type": "flattened"
      }
    }
  }
}

POST bug_reports/_doc/1
{
  "title": "Results are not sorted correctly.",
  "labels": {
    "priority": "urgent",
    "release": ["v1.2.5", "v1.3.0"],
    "timestamp": {
      "created": 1541458026,
      "closed": 1541457010
    }
  }
}

如果直接查询顶级节点的话,将会搜索所有叶子节点的值

POST bug_reports/_search
{
  "query": {
    "term": {"labels": "urgent"}
  }
}

如果需要查询对象中特定的属性的话,需要通过对象表达式来查询

POST bug_reports/_search
{
  "query": {
    "term": {"labels.release": "v1.3.0"}
  }
}

查询时,不能使用通配符引用字段键

nested

嵌套数据类型,可以看成是一个特殊的对象类型,可以让对象数组独立检索

PUT my-index-000001/_doc/1
{
  "group" : "fans",
  "user" : [ 
    {
      "first" : "wu",
      "last" :  "px"
    },
    {
      "first" : "hu",
      "last" :  "xy"
    },
    {
      "first" : "wu",
      "last" :  "mx"
    }
  ]
}

如果 user 是对象数据类型,在内部会转换为

{
  "group" :        "fans",
  "user.first" : [ "wu", "hu", "wu"],
  "user.last" :  [ "px", "xy" ,"mx"]
}

可以看出转换后的 JSON 文档中 first 和 last 的关联丢失了,如果尝试搜索 first 为 wu,last 为 xy 的文档,那么成功会检索出上述文档,但是 wu 和 xy 在原 JSON 文档中并不属于同一个 JSON 对象,应当是不匹配的,即检索不出任何结果。

嵌套类型就是为了解决这种问题的,嵌套类型将数组中的每个 JSON 对象作为独立的隐藏文档来存储,每个嵌套的对象都能够独立地被搜索,所以上述案例中虽然表面上只有 1 个文档,但实际上是存储了 4 个文档。

join

在 Elasticsearch 中,Join 可以让我们创建 parent/child 关系。Elasticsearch 不是一个 RDMS。通常 join 数据类型尽量不要使用,除非不得已

例子

PUT my_index
{
  "mappings": {
 "properties": {
   "my_join_field": { 
  "type": "join",
  "relations": {
    "question": "answer" 
  }
   }
 }
  }
}

在这里我们定义了一个叫做 my_index 的索引。在这个索引中,我们定义了一个 field,它的名字是 my_join_field。它的类型是 join 数据类型。同时我们定义了单个关系:question 是 answer 的 parent。

要使用 join 来 index 文档,必须在 source 中提供关系的 name 和文档的可选 parent。例如,以下示例在 question 上下文中创建两个 parent 文档

PUT my_index/_doc/1?refresh
{
  "text": "This is a question",
  "my_join_field": {
 "name": "question" 
  }
}
 
PUT my_index/_doc/2?refresh
{
  "text": "This is another question",
  "my_join_field": {
 "name": "question"
  }
}

这里采用 refresh 来强制进行索引,以便接下来的搜索。在这里 name 标识 question,说明这个文档时一个 question 文档。

索引 parent 文档时,您可以选择仅将关系的名称指定为快捷方式,而不是将其封装在普通对象表示法中:

PUT my_index/_doc/1?refresh
{
  "text": "This is a question",
  "my_join_field": "question" 
}
 
PUT my_index/_doc/2?refresh
{
  "text": "This is another question",
  "my_join_field": "question"
}

例如,以下示例显示如何索引两个 child 文档:

PUT my_index/_doc/3?routing=1?refresh  (1)
{
  "text": "This is an answer",
  "my_join_field": {
 "name": "answer",   (2)
 "parent": "1"       (3)
  }
}
 
PUT my_index/_doc/4?routing=1?refresh
{
  "text": "This is another answer",
  "my_join_field": {
 "name": "answer",
 "parent": "1"
  }
}

在上面的(1)处,我们必须使用 routing,这样能确保 parent 和 child 是在同一个 shard 里。我们这里 routing 为 1,这是因为 parent 的 id 为 1,在(3)处定义。(2) 处定义了该文档 join 的名称。

join 的限制
  • 对于每个 index 来说,只能有一个 join 字段
  • parent 及 child 文档,必须是在一个 shard 里建立索引。这也意味着,同样的 routing 值必须应用于 getting, deleting 或 updating 一个 child 文档。
  • 一个元素可以有多个 children,但是只能有一个 parent.
  • 可以对已有的 join 项添加新的关系
  • 也可以将 child 添加到现有元素,但仅当元素已经是 parent 时才可以。

结构化类型

range

在 Elasticsearch 中有一种数据类型叫做 range 的数据类型。它目前支持的类型如下:

类型

说明

integer_range

一个带符号的 32 位整数范围,最小值为 -2 的 31 次方,最大值为 2 的 31 次方减 1

float_range

一系列单精度 32 位 IEEE 754 浮点值

long_range

一系列带符号的 64 位整数,最小值为 -2 的 63 次方,最大值为 2 的 63 次方 -1

double_range

一系列双精度 64 位 IEEE 754 浮点值。

date_range

自系 EPOCH 以来经过的一系列日期值,表示为无符号的 64 位整数毫秒。

ip_range

支持 IPv4 或 IPv6(或混合)地址的一系列 ip 值。

示例

PUT range_index
{
  "settings": {
    "number_of_shards": 2
  },
  "mappings": {
    "properties": {
      "expected_attendees": {
        "type": "integer_range"
      },
      "time_frame": {
        "type": "date_range", 
        "format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"
      }
    }
  }
}

插入文档

PUT range_index/_doc/1?refresh
{
  "expected_attendees" : { 
    "gte" : 10,
    "lte" : 20
  },
  "time_frame" : { 
    "gte" : "2015-10-31 12:00:00", 
    "lte" : "2015-11-01"
  }
}

gte 是开始,lte是结束,其实就是大于号小于号

查询

GET range_index/_search
{
  "query": {
    "term": {
      "expected_attendees": {
        "value": "10"
      }
    }
  }
}

因为 10 刚好是在我们之前的文档定义的 10-20 区间,所以可以查询到我们刚刚插入的文档