
2.3 字段数据类型
通过映射类型的properties字段,可以定义映射类型包含的字段及其数据类型。例如在示例2-7中,age字段的数据类型为keyword,而address字段的数据类型则为text。Elasticsearch支持的数据类型包括字符串、数值、日期、布尔、二进制、范围等核心数据类型,还支持数组、对象等衍生类型,也支持嵌套、关联、地理信息等特殊类型。由于衍生类型和特殊类型基本都是从核心类型派生而来,所以下面先介绍一下核心数据类型。对于特殊数据类型,将在本书第8章结合具体应用介绍。
2.3.1 核心类型
核心数据类型是字段数据类型的基础,它们涵盖了大多数文档字段的应用场景。核心类型可以分为字符串、数值、日期、布尔等几种大的类型,每种大的类型下可能会包含一些更具体的数据类型。
1.字符串类型
字符串类型包括text和keyword两种类型,两者的区别在于text类型在存储前会做词项分析,而keyword类型则不会。所以text类型的字段可以通过analyzer参数设置该字段的分析器,而keyword类型字段则没有这个参数。由于词项分析,text类型字段在编入索引后可通过词项做检索,但不能通过字段整体值做检索;而keyword类型字段则刚好相反,只能通过字段整体值来做检索而不能用词项做检索。所以text类型的字段一般用于存储全文数据,比如日志信息、文章正文、邮件内容等;而keyword类型则用于存储结构化的文本数据,如邮编、地址、电话等。
由于text类型存储的是全文本数据,所以它编入索引的信息包括文档ID、词频、词序等信息,而keyword类型则只编入文档ID。当然,这可以通过index_option参数修改。在存储方面,keyword类型默认就支持通过文档值机制保存字段原始值,可通过doc_values参数关闭这个机制。text类型则不支持文档值机制,所以text类型不能参与文档排序、过滤、聚集等操作,除非打开它的fielddata机制。
2.数值类型
数值类型对应一个具体的数字值,例如1024、3.14等。Elasticsearch支持包括整型、浮点类型在内的8种数值类型,它们的主要区别体现的数值精确度上,具体见表2-4。
表2-4 数值类型

(续)

在这些类型中比较特殊的一类是scaled_float,它虽然是浮点数据类型,但在存储上却是使用long类型来表示。其基本思想是通过一个换算系数将浮点数放大为整型再保存,例如设置3.14的换算系数为100,则换算结果为3.14×100=314,最终保存的值就是314。由于使用整型保存浮点数不仅不会损失精度还能提升运算效率,所以非常适合小数位数固定的数值,比如货币金额通常就只有两个小数位。设置scaled_float的换算系数时可使用scaling_factor,例如

示例2-8 scaling_factor
对于整型数据来说,应该在满足需求的前提下选择尽可能小的数据类型,这对于提升索引和搜索性能都有帮助。如果不清楚最终数值的范围,可以不显式设置它们的类型而由Elasticsearch自主判断,以防止实际数值范围溢出。
3.日期类型
Elasticsearch有两种日期类型,分别是date和date_nanos。由于JSON并没有日期类型,所以这两种类型在文档中表现出来的仍然是带有日期格式的字符串。但在Elasticsearch内部存储它们时,会将它们转换为该日期与计算机纪元(1970年1月1日0点)的时间差值。其中,date类型会按毫秒计算差值,而date_nanos则会按纳秒计算差值。由于Elasticsearch在实现上使用long类型保存这个差值,这使得date_nanos类型能够保存的时间最多只能到2262年。所以在使用date_nanos类型时,要注意时间范围是否超标。
不管是哪一种日期类型,它们都必须满足一定的格式要求。如果没有指定格式默认会使用ISO 8601定义的标准时间格式,类似于“yyyy-MM-ddTHH:mm:ss”的形式。当然,这种格式有比较严格的定义,可以在相关文档中找到它们的规范。除此之外,默认格式也支持使用毫秒数直接表示日期。通过format参数还可以自定义日期格式,支持使用类似“yyyy-MM-ddTHH:mm:ss”的JODA格式来描述。相信有一定Java开发经验的读者应该不难理解,这里不再赘述。除此之外,Elasticsearch还内置了一组常用日期格式,可直接使用这些日期格式的名称来定义日期。这些日期格式非常多,详细请参考:https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-date-format.html。
4.布尔类型
布尔类型的类型关键字是boolean,它只有两个值,即true和false。但也接收以字符串描述的true和false,即使用 "true" 和 "false" 也是合法的。
5.字节类型
字节类型接收以Base64编码后表示的二进制字节流,所以尽管它的类型是字节类型,但在文档中它表现出来的仍然是字符串。字节类型的字段在默认情况下不会被存储,也不会被检索到。
6.范围类型
范围类型要求字段的值描述的是一个数值、日期或IP地址的范围,添加文档时可以使用gte、gt、lt、lte分别表示大于等于、大于、小于、小于等于。数值范围类型包括integer_range、float_range、long_range、double_range,日期范围类型和IP范围类型分别为date_range和ip_range。例如在示例2-9中先定义了age_range字段的类型为integer_range,然后又添加了一个age_range范围为[7, 23)的文档。所以在检索age_range为10时就会返回添加的新文档。


示例2-9 范围类型
在使用date_range和ip_range时需要注意它们的格式,对于日期可以使用format定义日期格式,而IP地址除了可以使用上述方式表示以外,还可以使用CIDR表示法描述一个网段。
2.3.2 衍生类型
衍生类型从核心类型衍生而来,包括数组和对象两种。严格来说,它们并不是独立的数据类型,因为它们并不像核心数据类型那样有专门的类型名称,而是通过特定的JSON格式确认它们的类型。
1.数组
如果要定义一个字段为数组类型,不需要使用类似array这样的名称声明它的类型,而是通过在添加文档时使用“[]”来确认该字段为数组。Elasticsearch也没有定义array这种数据类型,数组是在核心数据类型基础上的一种扩展。只要字段声明为某种核心数据类型,那么它就可以接收以“[]”表示的数组。惟一的限制就是数组中的元素必须是同一种类型,或者它们至少可以转换为同一种类型。如果在索引定义中声明了属性的数据类型,则数组元素的类型必须要与这种类型一致,或者可以转换为这种类型。例如:

示例2-10 数组类型
在示例2-10中,["12","2"]虽然为字符串的数组,但由于它们可以转换为整型,所以可以赋值给整型字段。
2.对象
与数组类似,Elasticsearch中也没有定义object这种数据类型,它是在添加文档时使用“{}”的格式来确认字段类型为对象。例如:


示例2-11 对象类型
在示例2-11中,address就是一个对象,包含了country和city两个字段。与数组类型不同的是,对象类型在定义索引的映射关系时可以声明。具体方式是使用嵌套的properties属性,例如在示例2-11中的colleges索引可以这样定义:

示例2-12 声明对象类型
2.3.3 多数据类型
有些文档字段可能经常会以不同的方式检索,如果文档字段只以一种方式编入索引,检索性能就会受到影响。比如文章的标题,在多数情况下可能是通过文章标题中的词项做检索,但在标题比较短并且知道整个标题内容时也是有可能使用整个标题做检索的。如果将标题字段的类型设置为text,那么标题在编入索引时就会被提取词项而不能使用整个标题做检索,而如果设置为keyword则不能使用词项做检索。事实上更为重要的是text类型并不支持文档值机制,所以通过text类型做排序或聚集就必须开启fielddata机制,而这种机制对于内存的消耗又非常大。
所以针对字符串类型text和keyword,Elasticsearch专门提供了一个用于配置字段多数据类型的参数fields,它能让一个字段同时具备两种数据类型的特征。示例2-13就是将articles索引的title字段设置为两种数据类型:

示例2-13 多数据类型
在示例2-13中,title字段的类型被设置为text,同时通过fields参数又为该字段添加了两个子字段。其中一个子字段名称为raw,它的类型被设置为keyword类型;另一个子字段名称为length,它的类型则为token_count。使用fields设置的子字段,在添加文档时不需要单独设置值,它们与title共享相同的字段值,只是会以不同方式处理字段值。同样,在检索时,它们也不会单独显示在结果中。所以它们一般只是在检索中以查询条件的形式出现,以减少检索时的性能开销。例如对于title.raw来说,title字段在编入索引时会将字段值做分析并提取词项,而title.raw则按keyword类型将整个值编入索引。所以如果需要根据词项做检索时应使用title字段,而如果需要使用整个值做检索或是在排序和聚集时则可以使用title.raw字段。在默认情况下,如果没有明确定义字符串类型时,添加到索引中的字符串都会以示例2-13的形式设置为多类型。
再来看另外一个字段title.length,它被设置为一种新的数据类型token_count。这种数据类型保存的值为整型,但实际接收的内容却是title字段的字符串。它会将字符串做分析并提取词项,然后将词项的数量保存下来,所以token_count类型字段必须要通过analyzer参数设置提取词项使用的分析器。由于title.length字段也是通过fields参数添加进来的,所以它在检索结果中也不会出现。需要注意的是,即使token_count类型不是通过fields参数添加进来的字段,它在检索结果的_source字段中仍然是原始的字符串,因为_source字段保存的是源文档而不是字段实际编入索引的值。如果一定要查看token_count类型字段保存的实际值,可以使用第4章第4.4.3节介绍的docvalue_fields查询方式。