【Hive】03-数据类型和文件格式
Hive支持关系型数据库中的大多数基本数据类型,同时也支持关系型数据库中很少出现的3种集合数据类型,下面我们将简短地介绍一下这样做的原因。
其中一个需要考虑的因素就是这些数据类型是如何在文本文件中进行表示的,同时还要考虑文本存储中为了解决各种性能问题以及其他问题有哪些替代方案。和大多数的数据库相比,Hive具有一个独特的功能,那就是其对于数据在文件中的编码方式具有非常大的灵活性。大多数的数据库对数据具有完全的控制,这种控制既包括对数据存储到磁盘的过程的控制,也包括对数据生命周期的控制。Hive将这些方面的控制权转交给用户,以便更加容易地使用各种各样的工具来管理和处理数据。
1、基本数据类型
Hive支持多种不同长度的整型和浮点型数据类型,支持布尔类型,也支持无长度限制的字符串类型。Hive v0.8.0版本中增加了时间戳数据类型和二进制数组数据类型。
数据类型 | 长度 | 例子 |
TINYINT | 1byte有符号整数 | 20 |
SMALINT | 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新纪元秒), |
BINARY | 字节数组 | 请看后面的讨论 |
需要注意的是所有的这些数据类型都是对Java中的接口的实现,因此这些类型的具体行为细节和Java中对应的类型是完全一致的。例如,STRING类型实现的是Java中的Stnng,FLOAT实现的是Java中的float,等等。
在其他SQL方言中,通常会提供限制最大长度的“字符数组"(也就是很多字符串)类型,但需要注意的是Hive中不支持这种数据类型。关系型数据库提供这个功能是出于性能优化的考虑。因为定长的记录更容易进行建立索引,数据扫描,等等。在Hive所处的“宽松”的世界里,不一定拥有数据文件但必须能够支持使用不同的文件格式,Hive根据不同字段间的分隔符来对其进行判断。同时,Hadoop和Hive强调优化磁盘的读和写的性能,而限制列的值的长度相对来说并不重要。
新增数据类型TIMESTAMP的值可以是整数,也就是距离Unix新纪元时间1970年1月1日,午夜12点)的秒数;也可以是浮点数,即距离Unix新纪元时间的秒数,精确到纳秒(小数点后保留9位数);还可以是字符串,即JDBC所约定的时间字符串格式,格式为YYYY-MM-DD hh:mm:ss.ffffffff。
TIMESTAMPS表示的是UTC时间。Hive本身提供了不同时区间互相转换的内置函数,也就是to_utc_timestamp函数和from_utc_timestamp函数。
BINARY数据类型和很多关系型数据库中的VARBINARY数据类型是类似的,但其和BLOB数据类型不相同。因为BINARY的列是存储在记录中的,而BLOB则不同。BINARY可以在记录中包含任意字节,这样可以防止Hive尝试将其作为数字,字符串等进行解析。
需要注意的是如果用户的目标是省略掉每行记录的尾部的话,那么是无需使用BINARY数据类型的。如果一个表的表结构指定的是3列,而实际数据文件每行记录包含有5个字段的话,那么在Hive中最后2列数据将会被省略掉。
如果用户在查询中将一个类型的列和一个类型的列作对比或者将一种整型类型的值和另一种整型类型的值做对比,那么结果将会怎么样呢?Hive会隐式地将类型转换为两个整型类型中值较大的那个类型,也就是会将FLOAT类型转换为DOUBLE类型,而且如有必要,也会将任意的整型类型转换为DOUBLE类型,因此事实上是同类型之间的比较。
如果用户希望将一个字符串类型的列转换为数值呢?这种情况下用户可以显式地将一种数据类型转换为其他一种数据类型,后面会有这样的一个例子,例子中s是一个字符串类型列,其值为数值:
...cast(s AS INT) ...
(这里需要说明的是,ASINT是关键字,因此使用小写也是可以的。)
2、集合数据类型
Hive中的列支持使用struct,map和array集合数据类型。需要注意的是表中语法示例实际上调用的是内置函数。
数据类型 | 描述 | 字面语法示例 |
STRUCT | 和C语言中的struct或者“对象”类似,都可以通过“点”符号访问元素内容。例如,如果某个列的数据类型是STRUCT{first STRING,last STRING},那么第1个元素可以通过 字段名.first来引用 | struct('John','Doe') |
MAP | MAP是一组键.值对元组集合,使用数组表示法(例如['key'])可以访问元素。例如,如果某个列的数据类型是MAP,其中键->值对是'first'->'John'和'last'->'Doe',那么可以通过 字段名['last'] 获取最后1个元素 | map('first','JOIN','last','Doe') |
ARRAY | 数组是一组具有相同类型和名称的变量的集合。这些变量称为数组的元素,每个数组元素都有一个编号,编号从零开始。例如,数组值为['John','Doe'], 那么第2个元素可以通过 数组名[1] 进行引用 |
Array('John','Doe') |
和基本数据类型一样,这些类型的名称同样是保留字。
大多数的关系型数据库并不支持这些集合数据类型,因为使用它们会趋向于破坏标准格式。例如,在传统数据模型中,structs可能需要由多个不同的表拼装而成,表间需要适当地使用外键来进行连接。
破坏标准格式所带来的一个实际问题是会增大数据冗余的风险,进而导致消耗不必要的磁盘空间,还有可能造成数据不一致,因为当数据发生改变时冗余的拷贝数据可能无法进行相应的同步。
然而,在大数据系统中,不遵循标准格式的一个好处就是可以提供更高吞吐量的数据。当处理的数据的数量级是T或者P时,以最少的“头部寻址”来从磁盘上扫描数据是非常必要的。按数据集进行封装的话可以通过减少寻址次数来提供查询的速度。而如果根据外键关系关联的话则需要进行磁盘间的寻址操作,这样会有非常高的性能消耗。
提示
Hive中并没有健的概念。但是,用户可以对表建立索引。
这里有一个用于演示如何使用这些数据类型的表结构声明语句,这是一张虚构的人力资源应用程序中的员工表:
CREATE TABLE employees(
name STRING,
salary FLOAT
subordinates ARRAY<STRING>,
deductions MAP<STRING,FLOAT>,
address STRUCT<street:STRING,city:STRING,state:STRING,zip:INT>);
其中,name是一个简单的字符串;对于大多数雇员来说,salary(薪水)使用float浮点数类型来表示就已经足够了;subordinates(下属员工)列表是一个字符串值数组。在该数组中,我们可以认为name是“主键”,因此subordinates中的每一个元素都将会引用这张表中的另一条记录。对于没有下属的雇员,这个字段对应的值就是一个空的数组。在传统的模型中,将会以另外一种方式来表示这种关系,也就是雇员和雇员的经理这种对应关系。这里我们并非强调我们的模型对于Hive来说是最好的,而只是为了举例展示如何使用数组。
字段deductions是一个由键一值对构成的map,其记录了每一次的扣除额,这些钱将会在发薪水的时候从员工工资中扣除掉。map中的键是扣除金额项目的名称(例如,“国家税收”),而且键可以是一个百分比值,也可以完全就是一个数值。在传统数据模型中,这个扣除额项目的名称(这里也就是map的键)可能存在于不同的表中。这些表在存放特定扣除额值的同时,还有一个外键指向对应的雇员记录。最后,每名雇员的家庭住址使用struct数据类型存储,其中的每个域都被作了命名,并且具有一个特定的类型。
请注意后面是如何使用Java语法惯例来表示集合数据类型的。例如,MAP<STRING,FLOAT>表示map中的每个键都是STRING数据类型的,而每个值都是FLOAT数据类型的。对于ARRAY<STRING>,其中的每个条目都是STRING类型的。STRUCT可以混合多种不同的数据类型,但是STRUCT中一旦声明好结构,那么其位置就不可以再改变。
3、文本文件数据编码
下面我们一起来研究文件格式,首先举个最简单的例子,也就是文本格式文件。毫无疑问,用户应该很熟悉以逗号或者制表符分割的文本文件,也就是所谓的逗号分隔值(CSV)或者制表符分割值(TSV)。只要用户需要,Hive是支持这些文件格式的,在后面将会介绍其具体使用方式。然而,这两种文件格式有一个共同的缺点,那就是用户需要对文本文件中那些不需要作为分隔符处理的逗号或者制表符格外小心。也因此,Hive默认使用了几个控制字符,这些字符很少出现在字段值中。Hive使用术语field来表示替换默认分隔符的字符,稍后我们将可以看到。
分隔符 | 描述 |
\n | 对于文本文件来说,每行都是一条记录,因此换行符可以分割记录 |
^A(Ctrl+A) | 用于分隔字段(列)。在CREATE TABLE语句中可以使用八进制编码\001表示 |
^B | 用于分隔ARRARY或者STRUCT中的元素,或用于MAP中键.值对之间的分隔。在CREATE TABLE语句中可以使用八进制编码\002表示 |
^C | 用于MAP中键和值之间的分隔。在CREATE TABLE语句中可以使用八进制编码\003表示 |
前面介绍的employees表的记录看上去和下面展示的这个例子是一样的,其中使用到了^A等字符来作为字段分隔符。像Emacs这样的文本编辑器将会以这种形式来展示这些分隔符。因为页面篇幅有限,所以每行数据并非显示在同一行。为了能较清晰地展示每行记录,我们在行与行间额外增加了一个空行:
其可读性并不好,但是当然了,我们可以使用Hive来读取这些数据。我们先来看看第1行记录来了解一下这个结构。首先,如果使用JavaScript数据交换格式(JSON)来表示这条记录的话,那么如果增加了额表结构中的字段名称的话,样式将会和如下所示的一样:
这里,用户可能会发现在JSON中map类型和struct类型其实是一样的。
用户可以不使用这些默认的分隔符,而指定使用其他分隔符。当有其他应用程序使用不同的规则写数据时,这是非常必要的。下面这个表结构声明和之前的那个表是一样的,不过这里明确地指定了分隔符:
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...子句)之前。
字符\001是^A的八进制数。ROW FORMAT DELIMITED FIELDSTERMINATED BY '\001'这个子句表明Hive将使用字符作为列分割符。
同样地,字符\002是^B的八进制数。ROW FORMAT DELIMITED COLLECTION ITEMS TERMINATED BY '\002'这个子句表明Hive将使用^B作为集合元素间的分隔符。
最后,字符\003是^C的八进制数。ROW FORMATDE LIMITEDMAP KEYS TERMINATED BY '\003'这个子句表明Hive将使用^C作为map的键和值之间的分隔符。
子句LINES TERMINATED BY '...' 和 STORED AS ... 不需要ROW FORMAT DELIMITED关键字。
事实上,Hive到目前为止对于LINES TERMINATED BY ... 仅支持字符'\n'也就是说行与行之间的分隔符只能为'\n'。因此这个子句现在使用起来还是有限制的。用户可以重新指定列分割符及集合元素间分隔符,而map中键·值间分隔符仍然使用默认的文本文件格式,因此子句STORED AS TEXTFILE很少被使用到。本书中大多数情况下,我们使用的都是缺省情况下默认的TEXTFILE文件格式。
因此,虽然用户可以明确指定这些子句,但是在大多数情况下,大多子句还是使用默认的分割符的,只需要明确指定那些需要替换的分隔符即可。
警告:这些规则只会影响到Hive在读取文件后将如何进行划分。只有在一些比较极端的情况下,才需要用户手工去将数据按正确的格式写入文件。
例如,如下表结构声明定义的是一个表数据按照逗号进行分割的表。
CREATE TABLE some_data(
first FLOAT,
second FLOAT,
third FLOAT
)ROW FORMAT DELIMITED
FIELDS TERMINATED BY ',';
用户还可以使用'\t',(也就是制表键)作为字段分隔符。
本例并没有合理地处理csv榕式(值是逗号分隔的)和TSV榕式(值是制表键分隔的)文件通常使用的场景·CSV和TSV格式的文件中文件头中包含有列名,而列值字符串也有可能是包含在引号中的,而且其列值中也有可能包含有逗号或者制表键,第巧章将介绍如果更优雅地处理这类文件。
这种强大的可定制功能使得可以很容易地使用Hive来处理那些由其他工具和各种各样的ETL(也就是数据抽取、数据转换和数据装载过程)程序产生的文件。
4、读时模式
当用户向传统数据库中写人数据的时候,不管是采用装载外部数据的方式,还是采用将一个查询的输出结果写人的方式,或者是使用UPDATE语句,等等,数据库对于存储都具有完全的控制力。数据库就是“守门人”。传统数据库是写时模式〈schemaon write),即数据在写人数据库时对模式进行检查。
Hive对底层存储并没有这样的控制。对于Hive要查询的数据,有很多种方式对其进行创建、修改,甚至损坏。因此,Hive不会在数据加载时进行验证,而是在查询时进行,也就是读时模式(schemaon read)。
那么如果模式和文件内容并不匹配将会怎么样呢?Hive对此做的非常好,因为其可以读取这些数据。如果每行记录中的字段个数少于对应的模式中定义的字段个数的话,那么用户将会看到查询结果中有很多的null值。如果某些字段是数值型的,但是Hive在读取时发现存在非数值型的字符串值的话,那么对于那些字段将会返回null值。除此之外的其他情况下,Hive都极力尝试尽可能地将各种错误恢复过来。