Hive支持关系型数据库中的大多数基本数据类型,同时支持关系型数据库中很少出现的3种集合数据类型,下面我们将简短地介绍一下这样做的原因。
其中一个需要考虑的因素就是这些数据类型是如何在文本文件中进行表示的,同时还要考虑文本存储中为了解决各种性能问题以及其他问题有哪些替代方案。和大多数的数据库相比,Hive具有一个独特的功能,那就是其对于数据在文件中的编码方式具有非常大的类活性。大多数的数据库对数据具有完全的控制,这种控制既包括对数据存储到磁盘的过程的控制,也包括对数据生命周期的控制。Hive将这些方面的控制权转交给用户,以便更加容易地使用各种各样的工具来管理和处理数据。
基本数据类型
Hive支持多种不同长度的整形和浮点型数据类型,支持布尔类型,也支持无长度限制的字符串类型,还有时间戳数据类型和二进制数组数据类型。
下表列举了Hive所支持的基本数据类型:
数据类型 | 长度 | 例子 |
TINYINT | 1byte有符号整数 | 20 |
SMALLINT | 2byte有符号整数 | 20 |
INT | 4byte有符号整数 | 20 |
BIGINT | 8byte有符号整数 | 20 |
BOOLEAN | 布尔类型,true或者false | TRUE |
FLOAT | 单精度浮点数 | 3.14159 |
DOUBLE | 双精度浮点数 | 3.14159 |
STRING | 字符序列。可以指定字符集。可以使用单引号或者双引号 | ‘now is the time’,’for all good men’ |
TIMESTAMP | 整数,浮点数或者字符串 | 1327882394(UNIX新纪元秒),1327882394.123456789(UNIX新纪元秒并跟随纳秒数)和’2016-03-13 16:23:30.123456789’(JDBC所兼容的java.sql.Timestamp时间格式) |
BINARY | 字节数组 | 请看后面的讨论 |
和其他SQL Dialect(方言)一样,这些都是保留字。
需要注意的是所有的这些数据类型都是对Java中的接口的实现,因此这些类型的具体行为细节和Java中对应的类型是完全一致的。例如,STRING类型实现的是Java中的String,FLOAT实现的是Java中的float,等等。
在其他SQL Dialect中,通常会提供限制最大长度的”字符数组”(也就是很多字符串)类型,但需要注意的是,Hive中不支持这种数据类型。关系型数据库提供这个功能是出于性能优化的考虑,因为定长的记录更容易进行建立索引,数据扫描,等等。在Hive所处的”宽松”的世界里,不一定拥有数据文件但必须能够支持使用不同的文件格式,Hive根据不同字段间的分隔符来对其进行判断。同时,Hadoop和Hive强调优化磁盘的读和写的性能,而限制列的值的长度相对来说并不重要。
数据类型TIMESTAMP的值可以是整数,也就是距离UNIX新纪元时间(1970年1月1日,午夜12点)的秒数;也可以是浮点数,即距离UNIX新纪元时间的秒数,精确到纳秒(小数点后保留9位数);还可以是字符串,即JDBC所约定的时间字符串格式,格式为:YYYY-MM-DD hh:mm:ss.fffffffff。
TIMESTAMP表示的是UTC时间。Hive本身提供了不同时区间互相转换的内置函数,也就是to_utc_timestamp函数和from_utc_timestamp函数。
BINARY数据类型和很多关系型数据库中的VARBINARY数据类型是类似的,但其和BLOB数据类型并不相同。因为BINARY的列是存储在纪录中的,而BLOB则不同。BINARY可以在纪录中包含任意字节,这样可以防止Hive尝试将其作为数字,字符串等进行解析。
需要注意的是,如果用户的目标是省略掉每行记录的尾部的话,那么是无需使用BINARY数据类型的。如果一个表的表结构指定的是3列,而实际数据文件每行记录包含有5个字段的话,那么在Hive中最后2列数据将会被省略掉。
如果用户在查询中将一个float类型的列和一个double类型的列作对比或者将一种整型类型的值和另一种整型类型的值作对比,Hive会隐式地将类型转换为两个类型中范围较大的那个类型,因此事实上,还是同类型比较。
如果用户希望将一个字符串类型的列转换为数值,可以显式地进行转换,如:假设s是一个字符串类型列,其值为数值,则可以使用…cast (s AS INT)…。
集合数据类型
Hive中的列支持使用struct、map和array集合数据类型。需要注意的是下表中语法示例实际上调用的是内置函数。
数据类型 | 描述 | 字面语法示例 |
STRUCT | 和C语言中的struct或者”对象”类似,都可以通过”点”符号访问元素内容。例如,如果某个列的数据类型是STRUCT{first STRING, ladt STRING},那么第1个元素可以通过字段名.first来引用 | struct(‘John’, ‘Doe’) |
MAP | MAP是一组键-值对元组集合,使用数组表示法(例如[‘key’])可以访问元素。例如,如果某个列的数据类型是MAP,其中键->值对是’first’->’John’和’last’->’Doe’,那么可以通过字段名[‘last’]获取值’Doe’ | map(‘first’, ‘John’, ‘last’, ‘Doe’) |
ARRAY | 数组是一组具有相同类型的变量的集合。这些变量称为数组的元素,每个数组元素都有一个编号,编号从零开始。例如,数组值为[‘John’, ‘Doe’],那么第2个元素可以通过数组名[1]进行引用 | ARRAY(‘John’, ‘Doe’) |
和基本数据类型一样,这些类型的名称同样是保留字。
大多数的关系型数据库并不支持这些集合数据类型,因为使用它们会趋向于破坏标准格式。例如,在传统数据模型中,structs可能需要由多个不同的表拼装而成,表间需要适当地使用外键来进行连接。
破坏标准格式所带来的一个实际问题是会增大数据冗余的风险,进而导致消耗不必要的磁盘空间,还有可能造成数据不一致,因为当数据发生改变时冗余的拷贝数据可能无法相应地同步。
然而,在大数据系统中,不遵循标准格式的一个好处是可以提供更高吞吐量的数据。当处理的数据的数量级是T或者P时,以最少的”头部寻址”来从磁盘上扫描数据时非常必要的。按数据集进行封装的话可以通过减少寻址次数来提高查询的速度。而如果根据外键关系关联的话则需要进行磁盘间的寻址操作,这样会有非常高的性能消耗。
文本文件数据编码
下面我们一起来研究文件格式。用户应该很熟悉以逗号或者制表符分隔的文本文件,也就是所谓的逗号分隔值(CSV)或者制表符分隔值(TSV)。只要用户需要,Hive是支持这些文件格式的。然而,这两种文件格式有一个共同的缺点,那就是用户需要对文本文件中那些不需要作为分隔符处理的逗号或者制表符格外小心。也因此,Hive默认使用了几个控制字符,这些字符很少出现在字段值中。Hive使用术语field来表示替换默认分隔符的字符。下表列举了Hive中默认的记录和字段分隔符:
分隔符 | 描述 |
\n | 对于文本文件来说,每行都是一条记录,因此换行符可以分隔记录 |
^A(Ctrl+A) | 用于分隔字段(列)。在CREATE TABLE语句中可以使用八进制编码\001表示 |
^B | 用于分隔ARRAY或者STRUCT中的元素,或用于MAP中键-值对之间的分隔。在CREATE TABLE语句中可以使用八进制编码\002表示 |
^C | 用于MAP中键和值之间的分隔。在CREATE TABLE语句中可以使用八进制编码\003表示 |
用户可以不使用这些默认的分隔符,而指定使用其他分隔符。当有其他应用程序使用不同的规则写数据时,这是非常必要的。下面这个表结构声明展示了如何明确地指定分隔符:
CREATE TABLE employees ( name STRING, salary FLOAT, subordinates ARRAY<STRING>, deductions MAP<STRING, FLOAT>, address STRUCT<street:STRING, city:STRING, state:STRING, zip:INT> ) ROW FORMAT DELIMITED FIELDS TERMINATED BY '\001' COLLECTION ITEMS TERMINATED BY '\002' MAP KEYS TERMINATED BY '\003' LINES TERMINATED BY '\n' STORED AS TEXTFILE;
ROW FORMAT DELIMITED这组关键字必须要写在其他子句(除了STORED AS…)子句之前。
事实上,Hive目前为止对于LINES TERMINATED BY…仅支持字符’\n’,也就是说行与行之间的分隔符只能为’\n’。