到现在已经基本熟悉 ES 的映射基本了,现在本篇内容就是开始正式测试,文本内容同之前一样大部分转载于官网文档,这里对映射类型进行了分类,文档中的是按照字母排序的有点乱。
映射字段类型时我们都需要去设置映射参数,更多用法查看官方文档:Elastic Docs ›Elasticsearch Guide [8.6] › Mapping 。下面对映射参数简单整理出一个表格,方便查看:
参数 | 作用 |
---|---|
analyzer | 定义文本字段的分词器,默认对索引和查询都是有效的。默认分词器使用的关键字分词,英文和汉字都会分成一个个,这是当我们使用 term 只能匹配单个关键字,我们需要根据需求去设置分词器。 |
coerce | 强制尝试清除脏值以适合字段的数据类型,默认为 true,可以将字符串强制转换为数字和浮点将被截断为整数值。例:设置数字为integer类型,存入字符串 “1”,文档依然可以创建,如果设置成 false,则必须传入 integer 类型。 |
copy_to | 可以将多个字段的值,复制到同一个字段中。 |
doc_values | 支持排序、聚合 会占用额外存储空间,与 source 独立,同时开启 doc_values 和 _source 则会将该字段原始内容保存两份。doc_values 数据在磁盘上采用列式存储,关闭后无法使用排序和聚合。 |
dynamic | 动态映射,默认 true。自动设置字段类型。 |
eager_global_ordinals | 提升高基数聚合性能,默认 false。开启会影响写入性能,适用场景:高基数聚合 。高基数聚合场景中的高基数含义:一个字段包含很大比例的唯一值。 |
enabled | 是否对该字段进行索引,默认 true。ES 默认会索引所有的字段,但是有的字段可能只需要存储,不需要索引。关闭后,只在 _source中存储。 |
format | 日期格式。format 可以规范日期格式,而且一次可以定义多个 format。 |
ignore_above | 用于指定分词和索引的字符串最大长度,超过最大长度的话,该字段将不会被索引,这个字段只适用于 keyword 类型。 |
ignore_malformed | 忽略格式不对的数据,默认 false。 |
index | 字段是否被索引,默认 true。关闭后无法对其进行搜索,但字段仍会存储到 _source 和 doc_values,字段可以被排序和聚合。 |
index_options | 控制索引时哪些信息被存储到倒排索引中(用在 text 字段中),可设置:docs(默认,只存储文档编号),freqs(在 docs 基础上,存储词项频率),positions(在 freqs 基础上,存储词项偏移位置),offsets(在 positions 基础上,存储词项开始和结束的字符位置)。 |
index_phrases | 将两个词的组合词索引到一个单独的字段中。默认false。 |
index_prefixes | 为字段值的前缀编制索引,以加快前缀搜索速度。 |
meta | 附加到字段的元数据。 |
fields | 为不同的目的以不同的方式对同一字段建立索引。 |
normalizer | 用于解析前(索引或者查询)的标准化配置,可以在索引和查询时,分别对文档进行预处理,比如索引和查询单词全部小写。 |
norms | 用于计算查询的文档分数,默认true。对于仅用于过滤或聚合的字段,不需要对字段进行打分排序时设置为false。 |
null_value | 使用指定的值替换为null值,以便可以进行索引和搜索。 |
position_increment_gap | 当为具有多个值的文本字段建立索引时,将在值之间添加“假”间隙,以防止大多数短语查询在值之间进行匹配,默认值为100。 |
properties | 类型映射,object字段和nested字段包含子字段叫properties。 |
search_analyzer | 查询时候的分词器。默认情况下,如果没有配置 search_analyzer,则查询时,首先查看有没有 search_analyzer,有的话,就用 search_analyzer 来进行分词,如果没有,则看有没有 analyzer,如果有,则用 analyzer 来进行分词,否则使用 es 默认的分词器。 |
similarity | 字段打分的相似性算法,默认 BM25。 |
store | 单独存储属性值。默认对字段值进行索引以使其可搜索,但不单独存储它们,但是已存储在_source字段中。 |
subobjects | 8.3版本以后新增映射参数,默认为 true,用于保留字段名称中的 . ,这些字段将扩展到相应的对象结构。子对象设置为 false 的对象只能保存叶子子字段,而不能保存其他对象。 |
term_vector | 存储分析过程的词矢量(Term vectors)信息。包括:词、位置、偏移量、有效载荷。 |
找到一个 ES7.7 版本的中文文档,速度很快。
text:
- 会分词,然后进行索引,用于全文搜索。
- 支持模糊、精确查询
- 不支持聚合
keyword:
- 不进行分词,直接索引,keyword用于关键词搜索
- 支持模糊、精确查询
- 支持聚合
有时候对同一字段同时使用 text
和 keyword
两种类型会很有用 :一个用于全文搜索和其他用于聚合和排序。
官方文档。
文本字段最适合非结构化但可读的内容,比如Email内容、产品描述,应该使用 text
类型。当一个字段设置 text
类型以后,字段内容会被分析,字符串会被分析器分成一个一个词项,生成倒排索引。text类型的字段不用于排序,很少用于聚合。
//请求
PUT test
{"mappings": {"properties": {"address": {"type": "text"}}}
}
//返回:
{"acknowledged": true,"shards_acknowledged": true,"index": "test"
}
ES 自动为 字符串类型添加text和keyword类型:
//添加数据:
POST test/_doc/1
{"address": "江苏省苏州市苏州工业园区"
}//查看映射:
GET test/_mapping
{"test": {"mappings": {"properties": { # 定义属性关键字"address": { # 声明字段"type": "text", # 声明字段类型"fields": { # 多字段属性"keyword": { # 多字段属性名,可使用 address.keyword 进行操作"type": "keyword", # 声明字段类型"ignore_above": 256 # 索引最大程度,超过不被索引}}}}}}
}
倒排索引可以提供全文检索能力,但是无法提供对排序和数据聚合的支持。doc_values
本质上是一个序列化的列式存储结构,适用于聚合(aggregations)
、排序(Sorting)
、脚本(scripts access to field)
等操作。默认情况下,ES几乎会为所有类型的字段存储 doc_value
,但是 text 或 text_annotated 等可分词字段不支持 doc values
。如果不需要对某个字段进行排序或者聚合,则可以关闭该字段的doc_value
存储。
ES 为了存储原始数据,设计了 _source
来存储, 为了解决设计排序与聚合统计,又设计了doc_values
存储对应的列,这造成了数据重复存储,现在通过混合方式,重建构建 source,部分数据可以来自 列式doc_values
,这会显著节约索引存储占用。
// 设置 synthetic 模式
PUT test
{"mappings": {"_source": { "mode": "synthetic" },"properties": {"address": {"type": "text","fields": {"keyword": {"type": "keyword"}}}}}
}
//添加数据:
PUT test/_doc/1
{"address": ["address 11","address 11","address 222"]
}
//查询文档,数据会被去重:
GET test/_source/1
{"address": ["address 11","address 222"]
}
如果文本字段将store设置为true,则保留顺序和重复项:
PUT test
{"mappings": {"_source": { "mode": "synthetic" },"properties": {"address": {"type": "text","store": true # 字段是否要被单独存储}}}
}
//添加数据:
PUT test/_doc/1
{"address": ["address 11","address 11","address 222"]
}
//查询文档:
GET test/_source/1
{"address": ["address 11","address 11","address 222"]
}
默认情况下,文本字段是可搜索的,但默认情况下不可用于聚合、排序或脚本编写。
fielddata
默认是关闭的,映射时在你的字段上设置 "fielddata": true
,以便通过取消倒排索引将 fielddata
加载到内存中。注意,这可能会占用大量内存。
PUT /_mapping
{"properties": {"my_field": { "type": "text","fielddata": true}}
}
在 text
字段上启用 fielddata
通常没有意义。因为 fielddata
与 fielddata 缓存
一起存储在堆中,计算起来很昂贵。计算 fielddata
会导致延迟峰值,而堆使用率的增加会导致集群性能问题。
大多数希望对 text
字段进行更多操作的用户都使用 fields
多字段映射,既有用于全文搜索的文本字段,也有用于聚合的未分析关键字字段。
官方文档。
keyword 用于结构化内容,如ID、电子邮件地址、主机名、状态码、邮政编码或标记。通常用来排序(sorting)、聚合(aggregations)和 term-level 查询,例如 term
。
keyword 的 synthetic 模式和 text 是一样的:
//设置映射:
PUT test
{"mappings": {"_source": { "mode": "synthetic" },"properties": {"address": {"type": "keyword"}}}
}
//添加数据:
PUT test/_doc/1
{"address": ["address 11","address 11","address 222"]
}
//查询文档,数据会被去重:
GET test/_source/1
{"address": ["address 11","address 222"]
}
如果文本字段将store设置为true,则保留顺序和重复项。
长度超过ignore_above设置的字段文档会被存储,但不会被索引。
//设置映射:
PUT test
{"mappings": {"properties": {"name": {"type": "keyword","ignore_above": 10}}}
}//添加数据:
POST test/_doc/1
{"name": "abcdefghijklmn"
}//搜索数据:
GET /test/_search
{"query": {"term": {"name": "a"}}
}
//索引不到结果:
{"took": 0,"timed_out": false,"_shards": {"total": 1,"successful": 1,"skipped": 0,"failed": 0},"hits": {"total": {"value": 0,"relation": "eq"},"max_score": null,"hits": []}
}//查询文档:
GET test/_source/1
{"name": "abcdefghijklmn"
}
constant_keyword
是索引中所有文档都具有相同值的情况下关键字字段的特例。
constant_keyword
支持与 keyword
字段相同的查询和聚合,但利用所有文档每个索引具有相同值的事实来更有效地执行查询。允许提交没有字段值或值等于映射中配置的值的文档。
//设置映射:
PUT test
{"mappings": {"properties": {"message": {"type": "text"},"level": {"type": "constant_keyword","value": "debug"}}}
}//添加数据:
POST test/_doc
{"message": "Starting up Elasticsearch 1","level": "debug"
}
POST test/_doc
{"message": "Starting up Elasticsearch 2"
}//搜索:
GET /test/_search
{"query": {"match": {"level": "debug"}}
}
//两个文档都会被索引到
{"took": 36,"timed_out": false,"_shards": {"total": 1,"successful": 1,"skipped": 0,"failed": 0},"hits": {"total": {"value": 2,"relation": "eq"},"max_score": 1,"hits": [{"_index": "test","_id": "T62Lz4YBD3T716op1iuU","_score": 1,"_source": {"message": "Starting up Elasticsearch 1","level": "debug"}},{"_index": "test","_id": "UK2Lz4YBD3T716op3CtV","_score": 1,"_source": {"message": "Starting up Elasticsearch 2"}}]}
}
当第一个文档里定义的 level
的值为 debug
,那么之后所有的文档将视 debug
为索引 test
字段 level
的默认值。设置映射时如果没有设置 value
值,那么第一个文档中的 value
就是默认值。
wildcard
字段类型是一个专门的 keyword
字段,用于非结构化机器生成的内容,你计划使用 grep-like
的 wildcard
和 regexp
查询进行搜索。wildcard
类型针对具有大值或高基数的字段进行了优化。
在内部,wildcard字段使用 ngrams
索引整个字段值,并存储完整字符串。索引用作粗过滤器,通过检索和检查完整值来减少随后检查的值的数量。此字段特别适合在日志行上运行类似 grep
的查询。存储成本通常低于 keyword 字段的存储成本
,但在完整术语上精确匹配的搜索速度较慢。如果字段值共享许多前缀,例如同一网站的URL,则 wildcard
字段的存储成本可能高于等效 keyword
字段。
//映射:
PUT test
{"mappings": {"properties": {"address": {"type": "wildcard"}}}
}//添加数据
POST test/_doc/1
{"address" : "江苏省苏州市苏州工业园区"
}//搜索文档
GET /test/_search
{"query": {"wildcard": {"address": "*苏州*"}}
}
//匹配结果:
{"took": 5,"timed_out": false,"_shards": {"total": 1,"successful": 1,"skipped": 0,"failed": 0},"hits": {"total": {"value": 1,"relation": "eq"},"max_score": 1,"hits": [{"_index": "test","_id": "1","_score": 1,"_source": {"address": "江苏省苏州市苏州工业园区"}}]}
}
wildcard
字段与 keyword
字段一样是未排序的,因此不支持依赖于单词位置的查询,例如短语(phrase)查询。
运行 wildcard
查询时,将忽略任何重写参数。得分总是一个恒定的分数。
ES 支持以下数字类型:
类型 | 范围 |
---|---|
long | 最小值为-263,最大值为263-1的有符号64位整数。 |
integer | 最小值为-231,最大值为231-1的有符号32位整数。 |
short | 最小值为-32768,最大值为32767的有符号16位整数。 |
byte | 最小值为-128,最大值为127的有符号8位整数。 |
double | 一种双精度64位IEEE 754浮点数,限制为有限值。 |
float | 一种单精度32位IEEE 754浮点数,限制为有限值。 |
half_float | 一种半精度16位IEEE 754浮点数,限于有限值。 |
scaled_float | 一种浮点数字,由一个固定的双倍缩放因子进行缩放的长数字支持。 |
unsigned_long | 一个无符号64位整数,最小值为0,最大值为264-1。 |
数字类型尽可能选择范围小的数据类型,字段的长度越短,索引和搜索的效率越高,尽可能避免浮点类型,如果必须使用,优先考虑带缩放因子的浮点类型。
支持的映射参数:
参数 | 作用 |
---|---|
coerce | 尝试将字符串转换为数字,并截断整数的小数部分。接受true (默认) 和false。 |
boost | 映射字段级查询时提升。接受浮点数,默认值为1.0。 |
doc_values | 是否应该以列跨度(column-stride)的方式将字段存储在磁盘上,以便以后用于排序、聚合或编写脚本? 接受true(默认) 或false。 |
ignore_malformed | 如果为true,则忽略格式错误的数字。 如果为false(默认值),格式错误的数字会抛出异常并拒绝整个文档。 |
index | 该字段应该是可搜索的吗?接受true (默认) 和 false。 |
null_value | 接受与字段type相同的数值,用于替换任何显式null值。 默认值为null,这意味着该字段被视为缺失。 |
store | 字段值是否应该与_source字段分开存储和检索。 接受true或false (默认)。 |
meta | 字段的元数据。 |
就整数类型(byte、short、integer和long)而言,应该选择满足用例的最小类型。 这将有助于提高编制索引和搜索的效率。 但是请注意,存储是基于存储的实际值进行优化的,因此选择一种类型而不是另一种类型不会影响存储需求。
这里示例根据需求自行选择整数类型:
PUT
{"mappings": {"properties": {"NUMBER_FIELD": {"type": "long|integer|short|byte|unsigned_long" # ES 默认设置 long 类型}}}
}
double
、float
及 half_float
三种浮点类型用法并无区别,根据取值范围选择:
PUT
{"mappings": {"properties": {"NUMBER_FIELD": {"type": "double|float|half_float"}}}
}
double
、float
及 half_float
类型认为 -0.0
和 +0.0
的值是不同的。 因此,在 -0.0
上进行 term
查询将与 +0.0
不匹配,反之亦然。 对于 range
查询也是一样:如果上限是 -0.0
,则 +0.0
将不匹配,如果下限是 +0.0
,则 -0.0
将不匹配。
scaled_float
是带有缩放因子的缩放类型浮点数。需配合缩放因子scaling_factor
一起使用。
在存储距离、价格等浮点类型的数据,推荐使用scaled_float类型。比如价格等,单位为元,我们使用scaled_float类型,然后将比例因子(scaling_factor)设置为100,这样价格在ES中就会以“分”进行存储。
例如:假设缩放因子scale_factor为100,这样scaled_float字段将在ES内部存储10.25(元)为1025(分)。存储10.2588(元)为1026(分)。
scaled_float类型注意事项: 必须指定缩放因子scaling_factor。ES索引时,原始值会乘以该缩放因子并四舍五入得到新值,ES内部储存的是这个新值,但返回结果仍是原始值。使用比例因子的好处是整型比浮点型更易压缩,节省磁盘空间。
//映射:
PUT test
{"mappings": {"properties": {"total": {"type": "scaled_float", # 设置为 scaled_float 浮点类型"scaling_factor": 100 # 缩放因子}}}
}//索引文档:
POST test/_doc/1
{"total" : "3.1415926" # 3.1415926 * 100(缩放因子) = 314.15926,然后四舍五入得到314,所以内部真正存储的索引值是314。
}
POST test/_doc/2
{"total" : "3.1455926" # 3.1455926 * 100(缩放因子) = 314.55926,然后四舍五入得到315,所以内部真正存储的索引值是315。
}//搜索文档:
GET /test/_search
{"query": {"match": {"total": "3.1416" # 3.1416 * 100(缩放因子) = 314.16,然后四舍五入得到314。}}
}
//搜索结果:
{"took": 0,"timed_out": false,"_shards": {"total": 1,"successful": 1,"skipped": 0,"failed": 0},"hits": {"total": {"value": 1,"relation": "eq"},"max_score": 1,"hits": [{"_index": "test","_id": "1", "_score": 1,"_source": {"total": "3.1415926" # 所以只匹配到文档 3.1415926}}]}
}
需要注意的是,虽然ES在内部做了缩放处理,但是查询返回值还是原始值。
布尔字段接受JSON true
和 false
值,但也可以接受被解释为 true
或 false
的字符串:
false 值:
true 值
映射语法:
PUT
{"mappings": {"properties": {"BOOLEAN_FIELD": {"type": "boolean" }}}
}
插入测试:
// 请求:
POST /test/_bulk
{"create":{}}
{"is_published":""}
{"create":{}}
{"is_published":false}
{"create":{}}
{"is_published":"false"}
{"create":{}}
{"is_published":true}
{"create":{}}
{"is_published":"true"}
{"create":{}}
{"is_published":"aaaaa"} # 这个会失败,无法转换成布尔值
//返回结果:
{"took": 9,"errors": true,"items": [{"create": { # "" 空字符串"_index": "test","_id": "ZK212YYBD3T716opVSu4","_version": 1,"result": "created","_shards": {"total": 2,"successful": 1,"failed": 0},"_seq_no": 14,"_primary_term": 1,"status": 201}},{ # false"create": {"_index": "test","_id": "Za212YYBD3T716opVSu4","_version": 1,"result": "created","_shards": {"total": 2,"successful": 1,"failed": 0},"_seq_no": 15,"_primary_term": 1,"status": 201}},{"create": { # "false""_index": "test","_id": "Zq212YYBD3T716opVSu4","_version": 1,"result": "created","_shards": {"total": 2,"successful": 1,"failed": 0},"_seq_no": 16,"_primary_term": 1,"status": 201}},{"create": { # true"_index": "test","_id": "Z6212YYBD3T716opVSu4","_version": 1,"result": "created","_shards": {"total": 2,"successful": 1,"failed": 0},"_seq_no": 17,"_primary_term": 1,"status": 201}},{"create": { # "true""_index": "test","_id": "aK212YYBD3T716opVSu4","_version": 1,"result": "created","_shards": {"total": 2,"successful": 1,"failed": 0},"_seq_no": 18,"_primary_term": 1,"status": 201}},{"create": { # "aaaaa" 添加失败"_index": "test","_id": "aa212YYBD3T716opVSu4","status": 400,"error": {"type": "mapper_parsing_exception","reason": "failed to parse field [is_published] of type [boolean] in document with id 'aa212YYBD3T716opVSu4'. Preview of field's value: 'aaaaa'","caused_by": {"type": "illegal_argument_exception","reason": "Failed to parse value [aaaaa] as only [true] or [false] are allowed."}}}}]
}
JSON没有日期数据类型,因此Elasticsearch中的日期可以是下列之一:
在ES的内部,时间会被转换为UTC时间(如果声明了时区)并使用从新纪元开始的毫秒数的长整形数字类型的进行存储。
在日期字段上的查询,内部将会转换为使用长整形的毫秒进行范围查询,根据与字段关联的日期格式,聚合和存储字段的结果将转换回字符串。
注意点:日期最终都会作为字符串呈现,即使最开始初始化的时候是利用JSON文档的 long 声明的。
日期的格式可以被定制化的,如果没有声明日期的格式,它将会使用默认的格式:
"strict_date_optional_time||epoch_millis"
这意味着它将接受带有可选时间戳的日期,这些时间戳符合strict_date_optional_time
或 自纪元以来的 毫秒数(milliseconds-sin-the-epoch)
支持的格式。
epoch_millis: 是从开始纪元(1970-01-01 00:00:00 UTC)开始的毫秒数(long型),1970以前的时间也可以, 值是负数。
strict_date_optional_time: 是date_optional_time的严格级别,这个严格指的是年份、月份、天必须分别以4位、2位、2位表示,不足两位的话第一位需用0补齐。常见格式如下:
- yyyy
- yyyyMM
- yyyyMMdd
- yyyyMMddHHmmss
- yyyy-MM
- yyyy-MM-dd
- yyyy-MM-ddTHH:mm:ss # dd后面有个T,T即Time, 就是表示其后的数据为time, 其之前的数据为 Date。
- yyyy-MM-ddTHH:mm:ss.SSS
- yyyy-MM-ddTHH:mm:ss.SSSZ # "Z"表示时区。
工作常见到是yyyy-MM-dd HH:mm:ss,但ES默认不支持这格式,我们可以在format里自定义支持它。
映射语法:
PUT
{"mappings": {"properties": {"DATE_FIELD": {"type": "date"}}}
}
日期格式测试:
POST /test/_bulk
{"create":{}}
{"date_test":1672502400}
{"create":{}}
{"date_test":"20230102"}
{"create":{}}
{"date_test":"20230102030405"}
{"create":{}}
{"date_test":"2023-01"}
{"create":{}}
{"date_test":"2023-01-02"}
{"create":{}}
{"date_test":"2023-01-02T03:04:05"}
{"create":{}}
{"date_test":"2023-01-02T03:04:05.000"}//返回结果:
{"took": 20,"errors": false,"items": [{"create": { # 1672502400"_index": "test","_id": "cq3s2YYBD3T716opsyu8","_version": 1,"result": "created","_shards": {"total": 2,"successful": 1,"failed": 0},"_seq_no": 6,"_primary_term": 1,"status": 201}},{"create": { # "20230102""_index": "test","_id": "c63s2YYBD3T716opsyu8","_version": 1,"result": "created","_shards": {"total": 2,"successful": 1,"failed": 0},"_seq_no": 7,"_primary_term": 1,"status": 201}},{"create": { # "20230102030405""_index": "test","_id": "dK3s2YYBD3T716opsyu8","_version": 1,"result": "created","_shards": {"total": 2,"successful": 1,"failed": 0},"_seq_no": 8,"_primary_term": 1,"status": 201}},{ # "2023-01""create": {"_index": "test","_id": "da3s2YYBD3T716opsyu8","_version": 1,"result": "created","_shards": {"total": 2,"successful": 1,"failed": 0},"_seq_no": 9,"_primary_term": 1,"status": 201}},{"create": { # "2023-01-02""_index": "test","_id": "dq3s2YYBD3T716opsyu8","_version": 1,"result": "created","_shards": {"total": 2,"successful": 1,"failed": 0},"_seq_no": 10,"_primary_term": 1,"status": 201}},{ # "2023-01-02T03:04:05""create": {"_index": "test","_id": "d63s2YYBD3T716opsyu8","_version": 1,"result": "created","_shards": {"total": 2,"successful": 1,"failed": 0},"_seq_no": 11,"_primary_term": 1,"status": 201}},{"create": { # "2023-01-02T03:04:05.000""_index": "test","_id": "eK3s2YYBD3T716opsyu8","_version": 1,"result": "created","_shards": {"total": 2,"successful": 1,"failed": 0},"_seq_no": 12,"_primary_term": 1,"status": 201}}]
}
经常使用的 yyyy-MM-dd HH:mm:ss
格式 ES 默认不支持:
//插入 yyyy-MM-dd HH:mm:ss 格式
POST /test/_doc
{"date_test":"2023-01-02 03:04:05"
}
//返回结果:
{"error": {"root_cause": [{"type": "mapper_parsing_exception","reason": "failed to parse field [date_test] of type [date] in document with id 'ea3z2YYBD3T716opGCvh'. Preview of field's value: '2023-01-02 03:04:05'"}],"type": "mapper_parsing_exception","reason": "failed to parse field [date_test] of type [date] in document with id 'ea3z2YYBD3T716opGCvh'. Preview of field's value: '2023-01-02 03:04:05'","caused_by": {"type": "illegal_argument_exception","reason": "failed to parse date field [2023-01-02 03:04:05] with format [strict_date_optional_time||epoch_millis]","caused_by": {"type": "date_time_parse_exception","reason": "Failed to parse with all enclosed parsers"}}},"status": 400
}
我们可以在format里自定义支持 yyyy-MM-dd HH:mm:ss
:
// 支持 yyyy-MM-dd HH:mm:ss 格式
PUT test
{"mappings": {"properties": {"date_test": {"type": "date","format": "yyyy-MM-dd HH:mm:ss||strict_date_optional_time||epoch_millis"}}}
}// 添加 yyyy-MM-dd HH:mm:ss
POST /test/_doc
{"date_test":"2023-01-02 03:04:05"
}
//返回:
{"_index": "test","_id": "eq342YYBD3T716opcivY","_version": 1,"result": "created","_shards": {"total": 2,"successful": 1,"failed": 0},"_seq_no": 0,"_primary_term": 1
}
该数据类型是对 date
数据类型的补充。 然而,这两者之间有一个重要的区别。 现有的 date
数据类型以毫秒为分辨率存储日期。 date_nanos
数据类型以纳秒为分辨率存储日期,这将其日期范围限制在大约1970年到2262年之间,因为日期仍然存储为长整型类型,表示自纪元以来的纳秒。
对纳秒的查询在内部转换为对这种长整型表示形式的范围查询,聚合的结果和存储的字段根据与字段相关联的日期格式转换回字符串。
可以自定义日期格式,但是如果没有指定 format
,则使用默认格式:
"strict_date_optional_time||epoch_millis"
这意味着它将接受带有可选时间戳的日期,这些时间戳符合strict_date_optional_time
支持的格式,包括最多九个小数的秒或自纪元以来的毫秒数(因此失去了纳秒部分的精度)。
这意味着它将接受带有可选时间戳的日期,这些时间戳符合strict_date_optional_time
或 自纪元以来的毫秒数(milliseconds-sin-the-epoch)
支持的格式。
// 设置 date_nanos 类型
PUT test
{"mappings": {"properties": {"date_nanos_test": {"type": "date_nanos"}}}
}// 创建文档:
POST /test/_doc
{"date_test": "2023-01-03T03:14:15.123456789Z"
}
range(范围)数据类型支持以下几种范围类型:
类型 | 范围 |
---|---|
integer_range | 32位整数范围,最小值为-231,最大值为231-1。 |
float_range | 单精度32位 IEEE 754 浮点值范围。 |
long_range | 64位有符号整数,最小值为-263,最大值为263-1。 |
double_range | 64位双精度 IEEE 754 浮点值范围。 |
date_range | 自系统纪元以来,以无符号64位整数毫秒表示的日期值范围。 |
ip_range | 支持IPv4或IPv6(或混合)地址的IP值范围。 |
// 添加映射:
PUT test
{"mappings": {"properties": {"integer_range_test": {"type": "integer_range"},"date_range_test": {"type": "date_range", #
date_range类型接受由date类型定义的相同的字段参数。"format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"}}}
}// 添加文档:
POST test/_doc
{"integer_range_test" : { # 为10~20名与会者的会议编制索引的示例。"gte" : 10,"lte" : 20},"date_range_test" : { # 日期范围接受与日期范围查询中所述相同的格式。"gte" : "2015-10-31 12:00:00", # 使用日期时间戳的日期范围示例。 还可以接受date math格式。 请注意,在索引时不能使用“now”。"lte" : "2015-11-01"}
}
integer_range
搜索测试:
// 搜索:
GET test/_search
{"query" : {"term" : {"integer_range_test" : {"value": 12}}}
}
//搜索结果:
{"took": 1407,"timed_out": false,"_shards": {"total": 1,"successful": 1,"skipped": 0,"failed": 0},"hits": {"total": {"value": 1,"relation": "eq"},"max_score": 1,"hits": [{"_index": "test","_id": "fa0c2oYBD3T716opZSsZ","_score": 1,"_source": {"integer_range_test": {"gte": 10,"lte": 20},"date_range_test": {"gte": "2015-10-31 12:00:00","lte": "2015-11-01"}}}]}
}
date_range
搜索测试:
GET test/_search
{"query" : {"range" : {"date_range_test" : { # 范围查询的工作方式与范围查询中描述的相同。"gte" : "2015-10-31","lte" : "2015-11-01","relation" : "within" # range 字段上的范围查询支持参数relation,该参数可以是WITHIN、CONTAINS、INTERSECTS(默认)之一。}}}
}//搜索结果:
{"took": 168,"timed_out": false,"_shards": {"total": 1,"successful": 1,"skipped": 0,"failed": 0},"hits": {"total": {"value": 1,"relation": "eq"},"max_score": 1,"hits": [{"_index": "test","_id": "fa0c2oYBD3T716opZSsZ","_score": 1,"_source": {"integer_range_test": {"gte": 10,"lte": 20},"date_range_test": {"gte": "2015-10-31 12:00:00","lte": "2015-11-01"}}}]}
}
binary(二进制)
类型接受二进制值以Base64编码的字符串。 默认情况下,该字段不存储,并且不可搜索:
PUT my_index
{"mappings": {"properties": {"name": {"type": "text"},"blob": {"type": "binary"}}}
}PUT my_index/_doc/1
{"name": "Some binary blob","blob": "U29tZSBiaW5hcnkgYmxvYg=="
}
ip字段可以索引/存储IPv4及IPv6地址。
PUT test
{"mappings": {"properties": {"ip_addr": {"type": "ip"}}}
}//创建文档:
POST test/_doc
{"ip_addr": "192.168.1.1"
}
查询ip地址最常用的方法是使用 CIDR
符号:[ip_address]/[prefix_length]
。
// 搜索文档:
GET test/_search
{"query": {"term": {"ip_addr": "192.168.0.0/16"}}
}
//搜索结果:
{"took": 990,"timed_out": false,"_shards": {"total": 1,"successful": 1,"skipped": 0,"failed": 0},"hits": {"total": {"value": 1,"relation": "eq"},"max_score": 1,"hits": [{"_index": "test","_id": "g61j2oYBD3T716opuyuG","_score": 1,"_source": {"ip_addr": "192.168.1.1"}}]}
}
还可以使用 ip_range 数据类型 在单个字段中存储 IP 范围。
geo_point
类型的字段接受经度-纬度对,可用于:
有五种方法可以指定 geo-point,如下所示:
表示为对象的geo-point,带有lat(纬度)和lon(经度)键。
以字符串形式表示的geo-point,格式为:“lat,lon”。
表示为 geohash 的geo-point。
以数组形式表示的geo-point,格式为:[ lon, lat]。
表示为众所周知的文本点,格式为:“POINT(lon lat)”
映射:
//添加映射:
PUT test
{"mappings": {"properties": {"location": {"type": "geo_point"}}}
}// 添加文档:
POST /test/_bulk
{"create":{}}
{"text":"Geo-point as an object","location":{"lat":41.12,"lon":-71.34}}
{"create":{}}
{"text":"Geo-point as a string","location":"41.12,-71.34"}
{"create":{}}
{"text":"Geo-point as a geohash","location":"drm3btev3e86"}
{"create":{}}
{"text":"Geo-point as an array","location":[-71.34,41.12]}
{"create":{}}
{"text":"Geo-point as a WKT POINT primitive","location":"POINT (-71.34 41.12)"}
//返回结果:
{"took": 2078,"errors": false,"items": [{"create": {"_index": "test","_id": "fq1X2oYBD3T716opeiv4","_version": 1,"result": "created","_shards": {"total": 2,"successful": 1,"failed": 0},"_seq_no": 0,"_primary_term": 1,"status": 201}},{"create": {"_index": "test","_id": "f61X2oYBD3T716opeiv4","_version": 1,"result": "created","_shards": {"total": 2,"successful": 1,"failed": 0},"_seq_no": 1,"_primary_term": 1,"status": 201}},{"create": {"_index": "test","_id": "gK1X2oYBD3T716opeiv4","_version": 1,"result": "created","_shards": {"total": 2,"successful": 1,"failed": 0},"_seq_no": 2,"_primary_term": 1,"status": 201}},{"create": {"_index": "test","_id": "ga1X2oYBD3T716opeiv4","_version": 1,"result": "created","_shards": {"total": 2,"successful": 1,"failed": 0},"_seq_no": 3,"_primary_term": 1,"status": 201}},{"create": {"_index": "test","_id": "gq1X2oYBD3T716opeiv4","_version": 1,"result": "created","_shards": {"total": 2,"successful": 1,"failed": 0},"_seq_no": 4,"_primary_term": 1,"status": 201}}]
}
搜索:
GET test/_search
{"query": {"geo_bounding_box": { # geo_bounding_box:一种地理边界框查询,用于查找落在该框内的所有 geo-point。"location": {"top_left": {"lat": 42,"lon": -72},"bottom_right": {"lat": 40,"lon": -74}}}}
}
//搜索结果:
{"took": 917,"timed_out": false,"_shards": {"total": 1,"successful": 1,"skipped": 0,"failed": 0},"hits": {"total": {"value": 5,"relation": "eq"},"max_score": 1,"hits": [{"_index": "test","_id": "fq1X2oYBD3T716opeiv4","_score": 1,"_source": {"text": "Geo-point as an object","location": {"lat": 41.12,"lon": -71.34}}},{"_index": "test","_id": "f61X2oYBD3T716opeiv4","_score": 1,"_source": {"text": "Geo-point as a string","location": "41.12,-71.34"}},{"_index": "test","_id": "gK1X2oYBD3T716opeiv4","_score": 1,"_source": {"text": "Geo-point as a geohash","location": "drm3btev3e86"}},{"_index": "test","_id": "ga1X2oYBD3T716opeiv4","_score": 1,"_source": {"text": "Geo-point as an array","location": [-71.34,41.12]}},{"_index": "test","_id": "gq1X2oYBD3T716opeiv4","_score": 1,"_source": {"text": "Geo-point as a WKT POINT primitive","location": "POINT (-71.34 41.12)"}}]}
}
表示为数组或字符串的 geo-point:
- 请注意,字符串格式的 geo-point 按 lat,lon排序,而数组格式的 geo-point 按相反顺序排序:lon,lat。
- 最初,lat,lon顺序都在数组和字符串中使用,但是数组格式在早期被改变以符合 GeoJSON 使用的格式。
一个地理位置坐标点可以表示为一个geohash值。
- geohash 是经纬度交织的base32编码的字符串。 geohash 中的每个字符都会为精度增加5比特位。 所以 hash 越长,就越精确。 出于索引的目的,geohash被转换成纬度-经度对。 在此过程中,仅使用前12个字符,因此在 geohash 中指定超过12个字符并不会提高精度。 12个字符提供了60个比特位,这应该会将可能的误差减少到小于2厘米。
geo_shape
数据类型便于索引和搜索任意地理形状,如矩形和多边形。 当被索引的数据或被执行的查询包含形状而不仅仅是坐标点时,应该使用它。
可以使用 geo_shape查询
来查询使用此类型的文档。
这个就不多说了,参考 官方文档 吧。
在Elasticsearch中,没有专用的array(数组)数据类型。 默认情况下,任何字段都可以包含零个或多个值,但是数组中的所有值必须具有相同的数据类型。 例如:
[ "one", "two" ]
[ 1, 2 ]
[ 1, [ 2, 3 ]]
,其等价于 [ 1, 2, 3 ]
[ { "name": "Mary", "age": 12 }, { "name": "John", "age": 10 }]
对象数组并不像能你期望的那样工作:不能独立于数组中的其他对象独立查询每个对象。 如果你需要能够这样做,那么应该使用 nested 数据类型而不是object(对象)数据类型。
更多详情在nested(嵌套)中解释了。
当动态添加字段时,数组中的第一个值决定字段的类型(type
)。 所有后续的值必须是相同的数据类型,或者至少可以将后续的值强制转换为相同的数据类型。
不支持混合类型的数组:[ 10, "some string" ]
数组可能包含 null
值,这些值要么被配置的 null_value
替换,要么被完全跳过。 空数组 []
被视为缺失字段,即没有值的字段。
不需要预先配置任何东西就可以在文档中使用数组,它们是开箱即用的,我们来添加一个数组测试一下:
PUT test/_doc/1
{"message": "some arrays in this document...","tags": [ "elasticsearch", "wow" ], "lists": [ {"name": "prog_list","description": "programming list"},{"name": "cool_list","description": "cool stuff list"}]
}
查看一下动态映射:
{"test": {"mappings": {"properties": {"lists": { # lists 数组,由于lists数组内第一个元素是对象,所以映射成对象类型"properties": { # lists 的属性关键字"description": { # lists 的 description 字段,映射类型就是字符串的动态映射。"type": "text","fields": {"keyword": {"type": "keyword","ignore_above": 256}}},"name": { # name 映射同 description"type": "text","fields": {"keyword": {"type": "keyword","ignore_above": 256}}}}},"message": {"type": "text","fields": {"keyword": {"type": "keyword","ignore_above": 256}}},"tags": { # tags 的第一个元素是字符串,所以他是映射成字符串类型"type": "text","fields": {"keyword": {"type": "keyword","ignore_above": 256}}}}}}
}
查看映射后我们可以看到 lists
映射为对象类型,tags
是字符串类型,由此验证在动态添加字段时,数组中的第一个值决定字段的类型(type
)。
现在再索引一个文档,tags
是字符串,lists
是对象,看看能否添加成功。
// 索引文档
PUT test/_doc/2 # 不包含数组,但是可以索引到相同的字段中。
{"message": "no arrays in this document...","tags": "elasticsearch","lists": { "name": "prog_list","description": "programming list"}
}
// 返回成功:
{"_index": "test","_id": "2","_version": 1,"result": "created","_shards": {"total": 2,"successful": 1,"failed": 0},"_seq_no": 1,"_primary_term": 1
}// 搜索测试:
GET test/_search
{"query": {"match": {"tags": "elasticsearch" # 在字段tags中查找elasticsearch,两个文档都能匹配到。}}
}
//搜索结果:
{"took": 19,"timed_out": false,"_shards": {"total": 1,"successful": 1,"skipped": 0,"failed": 0},"hits": {"total": {"value": 2,"relation": "eq"},"max_score": 0.21110919,"hits": [{"_index": "test","_id": "2","_score": 0.21110919,"_source": {"message": "no arrays in this document...","tags": "elasticsearch","lists": {"name": "prog_list","description": "programming list"}}},{"_index": "test","_id": "1","_score": 0.160443,"_source": {"message": "some arrays in this document...","tags": ["elasticsearch","wow"],"lists": [{"name": "prog_list","description": "programming list"},{"name": "cool_list","description": "cool stuff list"}]}}]}
}
再索引一个其他类型,看看能不能成功:
//索引文档:
PUT test/_doc/3
{"message": "no arrays in this document...","tags": 10,"lists": "programming"
}
//返回:
{"error": {"root_cause": [{"type": "mapper_parsing_exception","reason": "object mapping for [lists] tried to parse field [lists] as object, but found a concrete value"}],"type": "mapper_parsing_exception","reason": "object mapping for [lists] tried to parse field [lists] as object, but found a concrete value"},"status": 400
}
由上测试我们可以知道,所有后续的值必须是相同的数据类型,或者至少可以将后续的值强制转换为相同的数据类型。
JSON文档本质上是分层的:文档可能包含内部对象,而内部对象本身又可能包含内部对象:
PUT test/_doc/1
{ "region": "US","manager": { "age": 30,"name": { "first": "John","last": "Smith"}}
}
在内部,这个文档被索引为一个简单的、扁平的键值对列表,比如下面这样:
{"region": "US","manager.age": 30,"manager.name.first": "John","manager.name.last": "Smith"
}
所以JSON 对象的子级也可以说使用 .
来代替 {}
。
查看一下对象的映射:
{"test": {"mappings": {"properties": { # 顶级映射定义中的属性。"manager": { # 字段manager是一个内部object字段。"properties": {"age": {"type": "long"},"name": { # 字段manager.name是在字段manager内的一个内部object字段。"properties": {"first": { # manager.name.first 是字段manager.name内的一个内部object字段。"type": "text","fields": {"keyword": {"type": "keyword","ignore_above": 256}}},"last": { # manager.name.last 是字段manager.name内的一个内部object字段。"type": "text","fields": {"keyword": {"type": "keyword","ignore_above": 256}}}}}}},"region": {"type": "text","fields": {"keyword": {"type": "keyword","ignore_above": 256}}}}}}
}
nested
类型是 object
数据类型的一个特殊版本,它允许以一种可以相互独立查询的方式对对象数组进行索引。
当摄入包含大量任意键的键值对时,可以考虑将每个键值对建模为包含
key
和value
字段的嵌套文档。 相反,可以考虑使用flattened
数据类型,它将整个对象映射为单个字段,并允许对其内容进行简单的搜索。 嵌套的文档及其查询的代价通常是很高的,因此对于这个用例使用flattened
数据类型是一个更好的选择。
Elasticsearch没有内部对象的概念。 因此,它将对象层次结构简化为字段名称和值的简单列表。 例如,考虑以下文档:
PUT my_index/_doc/1
{"group" : "fans","user" : [ # 字段user是动态映射的,数组的第一个元素类型为其字段类型:object 类型。{"first" : "John","last" : "Smith"},{"first" : "Alice","last" : "White"}]
}
上面的文档将在内部转换成如下所示的文档:
{"group" : "fans","user.first" : [ "alice", "john" ],"user.last" : [ "smith", "white" ]
}
那么在映射为 object
类型的情况下,user.first
和 user.last
字段被展平为多值字段,alice
和 white
之间的关联丢失。 该文档将错误地匹配查询 alice
和 smith
:
GET test/_search
{"query": {"bool": {"must": [{ "match": { "user.first": "Alice" }},{ "match": { "user.last": "Smith" }}]}}
}
//搜索结果:
{"took": 240,"timed_out": false,"_shards": {"total": 1,"successful": 1,"skipped": 0,"failed": 0},"hits": {"total": {"value": 1,"relation": "eq"},"max_score": 0.5753642,"hits": [ # 正常来说是不能搜索到的,但依然可以搜索到{"_index": "test","_id": "1","_score": 0.5753642,"_source": {"group": "fans","user": [{"first": "John","last": "Smith"},{"first": "Alice","last": "White"}]}}]}
}
为了解决上面的问题,这就有了 nested
数据类型,如果需要索引对象数组并保持数组中每个对象的独立性,请使用 nested
数据类型而不是 object
数据类型。
在内部,嵌套对象将数组中的每个对象作为单独的隐藏文档进行索引,这意味着可以使用 nested查询 独立于其他对象来查询每个嵌套对象:
nested查询:
- 包裹另一个查询以搜索nested(嵌套)字段。
- nested查询搜索嵌套的字段对象,就好像它们被索引为单独的文档一样。 如果对象与搜索匹配,nested查询将返回根父文档。
//设置映射:
PUT test
{"mappings": {"properties": {"user": {"type": "nested" # user 字段映射为 nested 数据类型}}}
}// 索引文档:
PUT test/_doc/1
{"group" : "fans","user" : [ {"first" : "John","last" : "Smith"},{"first" : "Alice","last" : "White"}]
}
user
设置为 nested
数据类型,搜索是必须使用 nested查询 ,再搜索一次 Alice
和 Smith
,这时就搜索不到了:
GET test/_search
{"query": {"nested": {"path": "user","query": {"bool": {"must": [{ "match": { "user.first": "Alice" }},{ "match": { "user.last": "Smith" }} ]}}}}
}//搜索结果:
{"took": 0,"timed_out": false,"_shards": {"total": 1,"successful": 1,"skipped": 0,"failed": 0},"hits": {"total": {"value": 0,"relation": "eq"},"max_score": null,"hits": []}
}
再搜索一次 Alice
和 White
,可以搜索到:
GET test/_search
{"query": {"nested": {"path": "user","query": {"bool": {"must": [{ "match": { "user.first": "Alice" }},{ "match": { "user.last": "White" }} ]}}}}
}
//搜索结果:
{"took": 0,"timed_out": false,"_shards": {"total": 1,"successful": 1,"skipped": 0,"failed": 0},"hits": {"total": {"value": 1,"relation": "eq"},"max_score": 1.3862942,"hits": [{"_index": "test","_id": "1","_score": 1.3862942,"_source": {"group": "fans","user": [{"first": "John","last": "Smith"},{"first": "Alice","last": "White"}]}}]}
}
嵌套文档可以:
因为嵌套文档是作为单独的文档进行索引的,所以只能在
nested查询
、nested/reverse_nested聚合
或 nested inner hits 的范围内访问它们。
- 例如,如果嵌套文档中的字符串字段将 index_options 设置为
offsets
以允许在高亮阶段使用发布,则这些offsets
在高亮阶段(主要阶段之一)将不可用。 相反,高亮需要通过 nested inner hits 来执行。 在通过 docvalue_fields 或 stored_fields 进行搜索的过程中加载字段时,同样的注意事项也适用。
nested
字段接受下列参数:
dynamic
:(可选,字符串) 新属性(properties)是否应该被动态的添加到一个已有的嵌套对象中。 接受true(默认),false及strict。properties
:(可选,对象) 嵌套对象内的字段,可以认识任意一种数据类型,包含nested。 新属性可能会被添加到一个已有的嵌套对象。include_in_parent
:(可选,布尔) 如果为true,嵌套对象中的所有字段也将作为标准(扁平的)字段添加到父文档中。 默认为false。include_in_root
:(可选,布尔) 如果为true,嵌套对象中的所有字段也将作为标准(扁平的)字段添加到根文档中。 默认为false。如前所述,每个嵌套对象都作为一个单独的 Lucene 文档进行索引。 接着前面的例子,如果我们索引一个包含 100 个 user 对象的文档,那么将会创建 101 个 Lucene 文档:父文档一个,每个嵌套对象一个。 由于与nested映射相关的开销,Elasticsearch 进行了一些设置来防止性能问题:
index.mapping.nested_fields.limit
nested
映射的最大数量。 nested
类型应该只在特殊情况下使用,即当对象数组需要彼此独立地查询时。 为了防止映射设计不当,该设置限制了每个索引的唯一 nested
类型的数量。默认值为 50
。1
。index.mapping.nested_objects.limit
nested
类型中可以包含的嵌套 JSON 对象的最大数量。 当文档包含太多嵌套对象时,此限制有助于防止内存不足错误。 默认值为 10000
。comments
的 nested
类型。 对于每个文档,它包含的 user 和comment 对象的总数必须低于该限制。有关防止映射爆炸的其他设置,请参考防止 映射爆炸 的设置。
找到一个 ES7.7 版本的中文文档,速度很快。
更多类型查看官网:
Aggregate metric
Alias
Completion
Dense vector
Flattened
Histogram
Join
Percolator
Point
Rank feature
Rank features
Search-as-you-type
Shape
Token count
Unsigned long
Version
上一篇:中科图新智慧管网综合管理平台
下一篇:设计模式-第14章(适配器模式)