在第4章,我们提到了 keyword
(一笔带过)。在本章,我们将介绍 ES 的字段类型。全面的带大家了解 ES 各个字段类型的使用场景。
ES 支持以下字段类型(仅介绍开发中常用,更多内容请自行阅读 官方文档)。
手动设置字段类型为 keyword
PUT /test3
{
"mappings": {
"properties": {
"tags": {
"type": "keyword"
}
}
}
}
写入数据
PUT /test3/_doc/1
{
"tags": "hello world"
}
keyword
其实就是字符串,输入什么,存储就是什么。
keyword
适用于 排序、聚合、term(精确查询)
查询场景中。
例如
GET /test3/_search
{
"query": {
"term": {
"tags": {
"value": "hello"
}
}
}
}
有 2 个对查询优化重要的点:
与 keyword 相对的则是 text。在第三章,我们介绍了全文搜索 match 的用法。你可能会好奇,为啥默认写入的数据就可以使用全文搜索。因为当输入是无规则字符串时,字段类型就是 text。(别着急,默认的字段类型,一会我们就会详细介绍)
手动设置字段类型为 text
# 先删除索引
DELETE test3
PUT /test3
{
"mappings": {
"properties": {
"tags": {
"type": "text"
}
}
}
}
text 适用场景:全文搜索
text 字段会对输入进行分词。
例如
PUT /test3/_doc/1
{
"tags": "hello world"
}
tags 会被分词存储为 hello、world 2个词。
当然,具体被分词为什么,其实跟我们设置的分词器有关(后续讲解,这里先有个概念)。
text 不适用场景:排序、聚合、脚本。
如果你在 text 字段上,进行排序、聚合,或者脚本操作,都会收到以下异常。
Text fields are not optimised for operations that require per-document field data like aggregations and sorting, so these operations are disabled by default. Please use a keyword field instead. Alternatively, set fielddata=true on [name] in order to load field data by uninverting the inverted index. Note that this can use significant memory.
例如:
GET /test3/_search
{
"sort": [
{
"tags": {
"order": "desc"
}
}
]
}
# 聚合
GET /test3/_search
{
"size": 0,
"aggs": {
"popular_tags": {
"terms": {
"field": "tags"
}
}
}
}
# 脚本操作
GET /test3/_search
{
"query": {
"script": {
"script": "doc['tags'].value == 'hello'"
}
}
}
要解决该异常,有2种方法
DELETE test3
PUT test3
{
"mappings": {
"properties": {
"tags": {
"type": "text",
"fields": {
"keyword": {
"type": "keyword"
}
}
}
}
}
}
排序、聚合时,则使用 tags.keyword
。需要全文索引时,依然可以使用 tags
字段。
GET /test3/_search
{
"sort": [
{
"tags.keyword": {
"order": "desc"
}
}
]
}
fielddata
(不建议!不建议!不建议!)DELETE test3
PUT /test3/_mapping
{
"properties": {
"tags": {
"type": "text",
"fielddata": true
}
}
}
PS:在 text 字段上启用 fielddata,会消耗非常大的内存!!!
手动指定字段类型为 date
PUT /test3
{
"mappings": {
"properties": {
"ctime": {
"type": "date"
}
}
}
}
未指定 format
参数时,默认的值为 strict_date_optional_time||epoch_millis
该默认值接收以下数据
# 秒时间戳
PUT /test3/_doc/1
{
"ctime": 1721135125
}
# 毫秒时间戳
PUT /test3/_doc/2
{
"ctime": 1721135125000
}
# datetime
PUT /test3/_doc/3
{
"ctime":"2024-07-16T12:10:30Z"
}
# date
PUT /test3/_doc/4
{
"ctime": "2024-07-15"
}
# 对数据排序
GET test3/_search
{
"sort": { "ctime": "asc"}
}
我们可以手动指定允许的数据格式。例如
PUT /test3
{
"mappings": {
"properties": {
"ctime": {
"type": "date",
"format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis||epoch_second"
}
}
}
}
如果时间为秒时间戳,可以考虑使用 epoch_second
PUT my-index-000001
{
"mappings": {
"properties": {
"ctime": {
"type": "date",
"format": "strict_date_optional_time||epoch_second"
}
}
}
}
PUT /test3/_doc/1
{
"ctime": 1721135125
}
# 以下查询,时间会被格式化
GET test3/_search
{
"fields": [ {"field": "ctime"}]
}
在复习一下,如果不需要范围查询,建议使用 keyword
存储(后续在进阶篇会讲原理)。
ES 除了支持常见的数字类型。如:long、integer、short、byte、double、float
还针对浮点数,有一个优化的类型 scaled_float
。
如果我们能够得知我们的浮点数最多有多少个小数点。使用该类型,在空间存储上会比浮点数更好。
PUT /test3
{
"mappings": {
"properties": {
"sf": {
"type": "scaled_float",
"scaling_factor": 100
}
}
}
}
上面的意思为:存储时,* 100。即,将浮点数变为整数。
PUT /test3
{
"mappings": {
"properties": {
"enable": {
"type": "boolean"
}
}
}
}
POST /test3/_doc
{
"enable": false
}
POST /test3/_doc
{
"enable": "false"
}
POST /test3/_doc
{
"enable": ""
}
POST /test3/_doc
{
"enable": true
}
POST /test3/_doc
{
"enable": "true"
}
GET /test3/_search
{
"query": {
"term": {
"enable": {
"value": false
}
}
}
}
写入一个 manager 对象
PUT test3/_doc/1
{
"region": "US",
"manager": {
"age": 30,
"name": {
"first": "John",
"last": "Smith"
}
}
}
在 ES 内部,该文档被索引为一个简单的键值对列表,大致如下
{
"region": "US",
"manager.age": 30,
"manager.name.first": "John",
"manager.name.last": "Smith"
}
例如,我们可以查询 manager.age=30
的文档
GET /test3/_search
{
"query": {
"term": {
"manager.age": {
"value": 30
}
}
}
}
上述文档的显式映射如下
PUT /test3
{
"mappings": {
"properties": {
"region": {
"type": "keyword"
},
"manager": {
"properties": {
"age": { "type": "integer" },
"name": {
"properties": {
"first": { "type": "text" },
"last": { "type": "text" }
}
}
}
}
}
}
}
POST /test3/_doc
{
"arr": ["12", 12, false]
}
PUT test3/_doc/1
{
"group" : "fans",
"user" : [
{
"first" : "John",
"last" : "Smith"
},
{
"first" : "Alice",
"last" : "White"
}
]
}
# 查询 user.first=Alice & user.last=White。你可能会使用以下写法,但实际上并不能正确工作
GET test3/_search
{
"query": {
"bool": {
"must": [
{ "match": { "user.first": "Alice" }},
{ "match": { "user.last": "Smith" }}
]
}
}
}
如果你的索引结构是这么设计的,并且有这样的需求,可能需要考虑下如何做优化了。例如,将表铺平。
PS:虽然 nested
嵌套类型可以解决该问题,但开发中会尽可能的把数据结构铺平,从而避免使用 nested
嵌套类型。这里不对 nested
过多介绍,因为开发中真的很不推荐使用。
PUT test3
{
"mappings": {
"properties": {
"arr": {
"type": "keyword"
}
}
}
}
PUT test3/_doc/1
{
"arr": ["1", "2", "3"]
}
GET test3/_search
{
"query": {
"term": {
"arr": {
"value": "1"
}
}
}
}