数据模型
Hive 没有专门的数据存储格式,也没有为数据建立索引,用户可以非常自由的组织 Hive 中的表,只需要在创建表的时候告诉 Hive 数据中的列分隔符和行分隔符,Hive 就可以解析数据。Hive 中所有的数据都存储在 HDFS 中,Hive 中包含以下数据模型:表(Table),外部表(External Table),分区(Partition),桶(Bucket)。
注:如果不指定分割符的话,Hive默认的分隔符是\001
如图所示,表=》分区=》桶,它们的对数据的划分粒度越来越小。
表
Hive 中的 Table 和数据库中的 Table 在概念上是类似的,每一个 Table 在 Hive 中都有一个相应的目录存储数据。例如,一个表 pvs,它在 HDFS 中的路径为:/wh/pvs,其中,wh 是在 Hive-site.xml 中由 ${Hive.metastore.warehouse.dir} 指定的数据仓库的目录,所有的 Table 数据(不包括 External Table)都保存在这个目录中。
创建表
Create table test_tb1 (flied string);
Load data local inpath ‘home/Hadoop/test.txt’ into table test_tb1;
外部表
Table的创建过程和数据加载过程(这两个过程可以在同一个语句中完成),在加载数据的过程中,实际数据会被移动到数据仓库目录中;之后对数据对访问将会直接在数据仓库目录中完成。删除表时,表中的数据和元数据将会被同时删除。
External Table 指向已经在 HDFS 中存在的数据,可以创建 Partition。它和 Table 在元数据的组织上是相同的,而实际数据的存储则有较大的差异。External Table 只有一个过程,加载数据和创建表同时完成(CREATE EXTERNAL TABLE ……LOCATION),实际数据是存储在 LOCATION 后面指定的 HDFS 路径中,并不会移动到数据仓库目录中。当删除一个 External Table 时,仅删除元数据,表中的数据不会真正被删
创建外部表
Create external table external_tbl (flied string)
Location ‘/home/Hadoop/external_table’;
Load data local inpath ‘home/Hadoop/test.txt’ into table external_tbl;
大家看到了创建外部表时候table之前要加关键字external,同时还要用location命令指定文件存储的路径,如果不使用locaction数据文件也会放置到Hive的数据仓库里。
这两种表在使用的区别主drop命令上,drop是Hive删除表的命令,托管表执行drop命令的时候,会删除元数据和存储的数据,而外部表执行drop命令时候只删除元数据库里的数据,而不会删除存储的数据。另外我还要谈谈表的load命令,Hive加载数据时候不会对元数据进行任何检查,只是简单的移动文件的位置,如果源文件格式不正确,也只有在做查询操作时候才能发现,那个时候错误格式的字段会以NULL来显示。
分区
Partition 对应于数据库中的 Partition 列的密集索引,但是 Hive 中 Partition 的组织方式和数据库中的很不相同。在 Hive 中,表中的一个 Partition 对应于表下的一个目录,所有的 Partition 的数据都存储在对应的目录中。例如:pvs 表中包含 ds 和 city 两个 Partition,则对应于 ds = 20090801, ctry = US 的 HDFS 子目录为:/wh/pvs/ds=20090801/ctry=US;对应于 ds = 20090801, ctry = CA 的 HDFS 子目录为;/wh/pvs/ds=20090801/ctry=CA
注意:表中可以不包括Partition字段
创建分区
Create table logs(ts bigint,line string)
Partitioned by (dt string,country string);
加载数据:
Local data local inpath ‘/home/Hadoop/par/file01.txt’ into table logs partition (dt=’2018-01-09’,country=’cn’);
在Hive数据仓库里实际存储的路径如下所示:
/user/Hive/warehouse/logs/dt=2018-01-09/country=cn/file1.txt
/user/Hive/warehouse/logs/dt=2018-01-09/country=cn/file2.txt
/user/Hive/warehouse/logs/dt=2018-01-09/country=us/file3.txt
/user/Hive/warehouse/logs/dt=2018-01-09/country=us/file4.txt
我们看到在表logs的目录下有了两层子目录dt=2018-01-09和country=cn
查询操作:
Select ts,dt,line from logs where country=’cn’,
这个时候我们的查询操作只会扫描file1.txt和file2.txt文件。
桶
Buckets 对指定列计算 hash,根据 hash 值切分数据,目的是为了并行,每一个 Bucket 对应一个文件。例如将 user 列分散至 32 个 bucket,首先对 user 列的值计算 hash,对应 hash 值为 0 的 HDFS 目录为:/wh/pvs/ds=20180109/ctry=US/part-00000;hash 值为 20 的 HDFS 目录为:/wh/pvs/ds=20180109/ctry=US/part-00020
上面的table和partition都是目录级别的拆分数据,bucket则是对数据源数据文件本身来拆分数据。使用桶的表会将源数据文件按一定规律拆分成多个文件,要使用bucket,我们首先要打开Hive对桶的控制,命令如下:
set Hive.enforce.bucketing = true
示例:
建临时表student_tmp,并导入数据:
Hive> desc student_tmp;
OK
id int
age int
name string
stat_date string
Time taken: 0.102 seconds
Hive> select * from student_tmp;
OK
1 27 zzq 20180109
2 19 ly 20180109
3 26 lmz 20180109
4 27 gsq 20180109
5 27 ysl 20180109
6 26 yys 20180109
7 27 xg 20180109
Time taken: 0.116 seconds
建student表:
Hive>create table student(id INT, age INT, name STRING)
>partitioned by(stat_date STRING)
>clustered by(id) sorted by(age) into 2 bucket
>row format delimited fields terminated by ',';
设置环境变量:
>set Hive.enforce.bucketing = true;
插入数据:
>from student_tmp
>insert overwrite table student partition(stat_date="20180203")
>select id,age,name where stat_date="20180109" sort by age;
查看文件目录:
$ Hadoop fs -ls /user/Hive/warehouse/studentstat_date=20180203/
Found 2 items
-rw-r--r-- 1 work joe 31 2018-01-09 19:07 /user/Hive/warehouse/student/stat_date=20180203/000000_0
-rw-r--r-- 1 work joe 39 2018-01-09 19:07 /user/Hive/warehouse/student/stat_date=20180203/000001_0
物理上,每个桶就是表(或分区)目录里的一个文件,桶文件是按指定字段值进行hash,然后除以桶的个数例如上面例子2,最后去结果余数,因为整数的hash值就是整数本身,上面例子里,字段hash后的值还是字段本身,所以2的余数只有两个0和1,所以我们看到产生文件的后缀是*0_0和*1_0,文件里存储对应计算出来的元数据。
Hive的桶,我个人认为没有特别的场景或者是特别的查询,我们可以没有必要使用,也就是不用开启Hive的桶的配置。因为桶运用的场景有限,一个是做map连接的运算,一个就是取样操作:
查看sampling数据:
Hive> select * from student tablesample(bucket 1 out of 2 on id);
Total MapReduce jobs = 1
Launching Job 1 out of 1
.......
OK
2 19 ly 20180109
6 26 yys 20180109
4 27 gsq 20180109
Time taken: 18.253 seconds
tablesample是抽样语句,语法:TABLESAMPLE(BUCKET x OUT OF y)
y必须是table总bucket数的倍数或者因子。Hive根据y的大小,决定抽样的比例。例如,table总共分了64份,当y=32时,抽取 (64/32=)2个bucket的数据,当y=128时,抽取(64/128=)1/2个bucket的数据。x表示从哪个bucket开始抽取。例 如,table总bucket数为32,tablesample(bucket 3 out of 16),表示总共抽取(32/16=)2个bucket的数据,分别为第3个bucket和第(3+16=)19个bucket的数据。
数据类型
Hive支持两种数据类型,一类叫原子数据类型,一类叫复杂数据类型。
原子类型
原子数据类型包括数值型、布尔型和字符串类型,具体如下表所示:
由上表我们看到Hive不支持日期类型,在Hive里日期都是用字符串来表示的,而常用的日期格式转化操作则是通过自定义函数进行操作。
Hive是用java开发的,Hive里的基本数据类型和java的基本数据类型也是一一对应的,除了string类型。有符号的整数类型:TINYINT、SMALLINT、INT和BIGINT分别等价于java的byte、short、int和long原子类型,它们分别为1字节、2字节、4字节和8字节有符号整数。Hive的浮点数据类型FLOAT和DOUBLE,对应于java的基本类型float和double类型。而Hive的BOOLEAN类型相当于java的基本数据类型boolean。
对于Hive的String类型相当于数据库的varchar类型,该类型是一个可变的字符串,不过它不能声明其中最多能存储多少个字符,理论上它可以存储2GB的字符数。
Hive支持基本类型的转换,低字节的基本类型可以转化为高字节的类型,例如TINYINT、SMALLINT、INT可以转化为FLOAT,而所有的整数类型、FLOAT以及STRING类型可以转化为DOUBLE类型,这些转化可以从java语言的类型转化考虑,因为Hive就是用java编写的。当然也支持高字节类型转化为低字节类型,这就需要使用Hive的自定义函数CAST了。
复杂类型
复杂数据类型包括数组(ARRAY)、映射(MAP)和结构体(STRUCT),具体如下表所示:
1.说明:
ARRAY:ARRAY 类型是由一系列相同数据类型的元素组成,这些元素可以通过下标来访问。
比如有一个 ARRAY 类型的变量 fruits,它是由[‘apple’,‘orange’,‘mango’]组成,那么我们可以通过 fruits[1]来访问元素 orange,因为 ARRAY 类型的下标是从 0 开始的
MAP:MAP 包含 key->value 键值对,可以通过 key 来访问元素。比如”userlist”是一个 map类 型 , 其 中 username 是 key , password 是 value ; 那 么 我 们 可 以 通 过userlist[‘username’]来得到这个用户对应的 password
STRUCT:STRUCT 可以包含不同数据类型的元素。这些元素可以通过”点语法”的方式来得到所需要的元素,比如 user 是一个 STRUCT 类型,那么可以通过 user.address 得到这个用户的地址。
2.示例演示:
1)array
建表语句:
create table person(name string,work_locations string)
row format delimited fields terminated by '\t';
create table person1(name string,work_locations array<string>)
row format delimited fields terminated by '\t'
collection items terminated by ',';
数据:
huangbo beijing,shanghai,tianjin,hangzhou
xuzheng changchu,chengdu,wuhan
wangbaoqiang dalian,shenyang,jilin
导入数据:
load data local inpath '/home/hadoop/person.txt' into table person;
查询语句:
Select * from person;
Select name from person;
Select work_locations from person;
Select work_locations[0] from person;
2)map
建表语句:
create table score(name string, scores map<string,int>)
row format delimited fields terminated by '\t'
collection items terminated by ','
map keys terminated by ':';
数据:
huangbo yuwen:80,shuxue:89,yingyu:95
xuzheng yuwen:70,shuxue:65,yingyu:81
wangbaoqiang yuwen:75,shuxue:100,yingyu:75
导入数据:
load data local inpath '/home/hadoop/score.txt' into table score;
查询语句:
Select * from score;
Select name from score;
Select scores from score;
Select s.scores['yuwen'] from score s;
3)struct
建表语句:
create table structtable(id int,course struct<name:string,score:int>)
row format delimited fields terminated by '\t'
collection items terminated by ',';
数据:
1 english,80
2 math,89
3 chinese,95
导入数据:
load data local inpath '/ home/hadoop / structtable.txt' into table structtable;
查询语句:
Select * from structtable;
Select id from structtable;
Select course from structtable;
Select t.course.name from structtable t;
Select t.course.score from structtable t;