# Hive 数据类型操作

# Hive 内部表操作

# 针对基本类型建表

首先,在 master 机器的 /export/data 目录下创建 hivedata 目录,在改文件夹下创建 user.txt ,并添加如下数据内容:

测试数据内容
1
2
3
1,allen,18
2,tom,23
3,jerry,28

具体操作如下:

1
2
3
4
5
6
7
[root@master ~]# mkdir -p /export/data/hivedata
[root@master ~]# cd /export/data/hivedata/
[root@master hivedata]# vi user.txt
[root@master hivedata]# cat user.txt
1,allen,18
2,tom,23
3,jerry,28

启动 hadoop 和 hive, 具体如下操作

bash代码
1
2
[root@master data]# sh /usr/local/hadoop-2.6.5/sbin/start-all.sh
[root@master data]# sh /export/servers/apache-hive-1.2.1-bin/bin/hive

image-20211201232425173

在 hive 界面输入 set hive.cli.print.current.db=true 设置显示当前使用数据库,然后创建 zjdf 数据库,创建数据库后可以使用 show databases 命令进行查看,如果已经存在,可以使用 drop database database_name cascade 命令强制性删除,如果有表格,一定要添加 cascade 参数,用 use 切换到这个新建的数据库,并创建内部表 t_user , 具体操作如下:

hive界面命令
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
hive> set hive.cli.print.current.db=true;
hive (default)> show databases;
OK
default
Time taken: 0.023 seconds, Fetched: 1 row(s)
hive (default)> create database zjdf;
OK
Time taken: 0.182 seconds、
hive (default)> show databases;
OK
default
zjdf
Time taken: 0.016 seconds, Fetched: 2 row(s)
hive (default)> use zjdf;
OK
Time taken: 0.024 seconds
hive (zjdf)>

image-20211201233838557

针对 hivedata 目录准备的结构化文件 user.txt 先创建一个内部表 t_user,具体示例如下:

1
2
3
4
5
6
7
8
hive (zjdf)> create table t_user(id int,name string,age int) ROW FORMAT DELIMITED FIELDS TERMINATED BY ',';
OK
Time taken: 0.561 seconds
hive (zjdf)> show tables;
OK
t_user
Time taken: 0.037 seconds, Fetched: 1 row(s)
hive (zjdf)>

创建成功后,通过 WEB UI 界面打开 Hive 内部表所在 HDFS 路径(默认 /user/hive/warehouse/zidj.db/t_user)进行查看,如下图所示。

image-20211201234130736

复制 master 终端,然后点击选项卡排列,可以达到左右分屏的效果,如下所示:

image-20211201234439467

将准备好的 user.txt 移动到 hadoop 对应的 t_user 表的路径中,具体操作如下:

1
2
3
[root@master hivedata]# hadoop fs -put user.txt /user/hive/warehouse/zjdf.db/t_user
21/12/01 10:05:04 WARN util.NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable
[root@master hivedata]#

刷新 Hadoop 文件系统,如下图所示:

image-20211201234942510

在 hive 界面中查询 t_user 表数据,发现表中已经存在数据,具体命令如下:

1
2
3
4
5
6
7
hive (zjdf)> select * from t_user;
OK
1 allen 18
2 tom 23
3 jerry 28
Time taken: 0.476 seconds, Fetched: 3 row(s)
hive (zjdf)>

image-20211201235147810

# 针对复杂类型建表

例如,现有结构化数据文件 student.txt,文件内容如下所示。

1
2
1,zhangsan,唱歌:非常喜欢-跳舞:喜欢-游泳:一般般
2,lisi,打游戏:非常喜欢-篮球:不喜欢

通过对 student.txt 文件内容分析得出,可以设计为 3 列字段,即编号、姓名、兴趣,其中编号可以为 int 类型,姓名可以为 string 类型,而兴趣列还需要进一步分隔为 Map 类型,因此在创建 student.txt 文件对应的内部表语句如下所示:

1
2
3
4
5
6
7
8
9
hive (zjdf)> create table t_student(id int,name string,hobby map<string,string>) row format delimited fields terminated by ','  collection items terminated by '-' map keys terminated by ':';
OK
Time taken: 0.064 seconds
hive (zjdf)> show tables;
OK
t_student
t_user
Time taken: 0.03 seconds, Fetched: 2 row(s)
hive (zjdf)>

上述建表语句中,通过对 student.txt 文件结构化文件的分析,先通过逗号 “,” 对多个字段 fields 进行分隔;接着,针对 hobby 字段列,通过横线 “-” 进行集合列分隔;最后,再针对每一个爱好,通过冒号 “:” 进行分隔为最终的 “key:value” 形式。执行上述建表语句后,就会在默认的 /user/hive/warehouse/zjdf.db 文件夹下生成一个 t_student 文件夹。如下图所示:

image-20211201235638917

此时,还必须将前面的结构化文件 student.txt 上传到该文件夹下进行映射,才能生成对应的内部表数据,上传完成后再次查询生成的 t_student 表信息。

1
2
3
4
5
6
7
[root@master hivedata]# vi student.txt
[root@master hivedata]# cat student.txt
1,zhangsan,唱歌:非常喜欢-跳舞:喜欢-游泳:一般般
2,lisi,打游戏:非常喜欢-篮球:不喜欢
[root@master hivedata]# hadoop fs -put student.txt /user/hive/warehouse/zjdf.db/t_student
21/12/01 10:15:07 WARN util.NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable
[root@master hivedata]#

再次在 hive 中查询 t_student 数据表格,如下图所示:

1
2
3
4
5
6
hive (zjdf)> select * from t_student;
OK
1 zhangsan {"唱歌":"非常喜欢","跳舞":"喜欢","游泳":"一般般"}
2 lisi {"打游戏":"非常喜欢","篮球":"不喜欢"}
Time taken: 0.082 seconds, Fetched: 2 row(s)
hive (zjdf)>

# Hive 外部表操作

内部表,即不添加关键字 External,内部表与结构化数据文件要想产生关系映射,那么数据文件就必须在指定的内部表文件夹下,而当遇到大文件的情况时,移动数据文件非常耗时,这就需要我们来创建外部表,因为它不需要移动结构化数据文件。下面我们通过一个小案例来,对外部表进行讲解。

现有结构化数据文件 student.txt,且数据内容如文件所示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
95001,李勇,男,20,CS
95002,刘晨,女,19,IS
95003,王敏,女,22,MA
95004,张立,男,19,IS
95005,刘刚,男,18,MA
95006,孙庆,男,23,CS
95007,易思玲,女,19,MA
95008,李娜,女,18,CS
95009,梦圆圆,女,18,MA
95010,孔小涛,男,19,CS
95011,包小柏,男,18,MA
95012,孙花,女,20,CS
95013,冯伟,男,21,CS
95014,王小丽,女,19,CS
95015,王君,男,18,MA
95016,钱国,男,21,MA
95017,王风娟,女,18,IS
95018,王一,女,19,IS
95019,邢小丽,女,19,IS
95020,赵钱,男,21,IS
95021,周二,男,17,MA
95022,郑明,男,20,MA

首先,我们将 student.txt 文件上传至 HDFS 上的 /stu 路径下,用来模拟生产环境下的数据文件,具体命令如下所示:

1
2
3
4
5
6
[root@master ~]# vi student.txt 
[root@master ~]# hadoop fs -mkdir /stu
21/12/01 10:21:27 WARN util.NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable
[root@master ~]# hadoop fs -put student.txt /stu
21/12/01 10:22:05 WARN util.NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable
[root@master ~]#

在 hive 中创建外部表

1
2
3
4
5
6
7
8
9
10
hive (zjdf)> create external table student_ext(Sno int,Sname string,Sex string,Sage int,Sdept string) row format delimited fields terminated by ',' location '/stu';
OK
Time taken: 0.041 seconds
hive (zjdf)> show tables;
OK
student_ext
t_student
t_user
Time taken: 0.027 seconds, Fetched: 3 row(s)
hive (zjdf)>

从结果发现,student_ext 外部表已经创建成功,最后,HQL(HiveQL)对数据表的内容的查看、增加、删除以及修改的语句均与 SQL 语句一致。下面以查看数据表内容为例进行演示,具体语法如下所示:

1
hive (zjdf)> select * from student_ext;

结果如下

image-20211202001146733

小提示:Hive 创建内部表时,会将数据移动到数据库指向的路径;创建外部表时,仅记录数据所在的路径,不会对数据的位置做任何改变。在删除表的时候,内部表的元数据和数据会被一起删除,而外部表只删除元数据,不删除数据

# Hive 分区表操作

分区表是按照属性在文件夹层面给文件更好的管理,实际上就是对应一个 HDFS 文件系统上的独立文件夹,该文件夹下是该分区所有的数据文件。Hive 中的分区就是分目录,把一个大的数据集根据业务需要分割成小的数据集。在查询时通过 WHERE 子句中的表达式选择查询指定的分区,这样的查询效率会提高很多。Hive 分区表一共有两种,分别为普通分区和动态分区,我们下面就要对其分别进行介绍。

# Hive 普通分区

创建分区表分为两种,一种是单分区,也就是说在表文件夹目录下只有一级文件夹目录。另外一种是多分区,表文件夹下出现多文件夹嵌套模式,现在我们只针对单分区进行详解,若想学习多分区可以参考官网的官方文档。

现有结构化数据文件 user_p.txt,文件中的数据内容如文件所示。

1
2
3
1,allen
2,tom
3,jerry

1
2
3
4
5
6
7
8
[root@master ~]# mkdir -p /export/data/hivedata/
[root@master ~]# cd /export/data/hivedata/
[root@master hivedata]# ll
total 8
-rw-r--r--. 1 root root 109 Dec 1 10:14 student.txt
-rw-r--r--. 1 root root 31 Dec 1 09:43 user.txt
[root@master hivedata]# vi user_p.txt
[root@master hivedata]#

首先,创建分区表。语法格式如下所示:

1
hive (zjdf)> create table t_user_p(id int, name string) partitioned by (country string) row format delimited fields terminated by ',';

image-20211202002144514

其次,加载数据是将数据文件移动到与 Hive 表对应的位置,从本地(Linux)复制或移动到 HDFS 文件系统的操作。由于分区表在映射数据时不能使用 Hadoop 命令移动文件,需要使用 Load 命令,其语法格式如下所示:

1
2
LOAD DATA [LOCAL] INPATH 'filepath' [OVERWRITE] 
INTO TABLE table_name [PARTITION (partcol1=val1, partcol2=val2 ...)]

Load Data 是 HQL 固定的数据装载语句,下面针对部分关键字进行讲解。

  • Filepath:它可以引用一个文件(在这种情况下,Hive 将文件移动到表所对应的目录中),或者它可以是一个目录(在这种情况下,Hive 将把该目录中的所有文件移动到表所对应的目录中)。它可以是相对路径、绝对路径以及完整的 URI。
  • Local:如果指定了 Local 键字,Load 命令将在本地文件系统(Hive 服务启动方)中查找文件路径,将其复制到对应的 HDFS 路径下;如果没有指定 Local 关键字,它将会从 HDFS 中移动数据文件至对应的表路径下。
  • Overwrite:如果使用了 Overwrite 关键字,当加载数据时目标表或分区中的内容会被删除,然后再将 filepath 指向的文件或目录中的内容添加到表或分区中。简单的说就是覆盖表中已有数据;若不添加该关键字,则表示追加数据内容。

加载数据操作的语法格式如下所示:

1
2
3
4
5
6
hive (zjdf)> load data local inpath '/export/data/hivedata/user_p.txt' into table t_user_p partition(country='USA');
Loading data to table zjdf.t_user_p partition (country=USA)
Partition zjdf.t_user_p{country=USA} stats: [numFiles=1, numRows=0, totalSize=22, rawDataSize=0]
OK
Time taken: 0.618 seconds
hive (zjdf)>

从上述语句看出,Load Data 表示装载数据,Inpath 表示数据文件所在的本地系统路径,partition(country=‘USA’)为指定的分区,它需要与建表时设置的分区字段保持一致。执行完上述命令后,查看表内容的数据,效果如图所示:

1
2
3
4
5
6
7
hive (zjdf)> select * from t_user_p;
OK
1 allen USA
2 tom USA
3 jerry USA
Time taken: 0.274 seconds, Fetched: 3 row(s)
hive (zjdf)>

# Hive 动态分区

上面介绍了 Hive 普通分区的创建和 Load 命令加载数据的操作。在默认情况下,我们加载数据时,需要手动的设置分区字段,并且针对一个分区就要写一个插入语句。如果源数据量很大时(例如,现有许多日志文件,要求按照日期作为分区字段,在插入数据的时候无法手动的添加分区),就可以利用 Hive 提供的动态分区,可以简化插入数据时的繁琐操作,若想实现动态分区,则需要开启动态分区功能,具体命令如下所示:

1
2
hive> set hive.exec.dynamic.partition=true;
hive> set hive.exec.dynamic.partition.mode=nonstrict;

Hive 默认是不支持动态分区的,因此 hive.exec.dynamic.partition 默认值为 false,需要启动动态分区功能,可以将该参数设置为 true;其中 hive.exec.dynamic.partition.mode 的默认值是 strict,表示必须指定至少一个分区为静态分区,将此参数修改为 nonstrict,表示允许所有的分区字段都可以使用动态分区。

在 Hive 中 insert 语句是用于动态插入数据的,不同的是它主要是结合 select 查询语句使用,且非常适用于动态分区插入数据,语法格式如下所示:

1
2
3
hive> insert overwrite table table_name 
partition (partcol1[=val1], partcol2[=val2] ...)
select_statement FROM from_statement

现有原始表的结构化数据文件 dynamic_partition_table.txt,内容数据如文件所示。

1
2
3
4
5
6
2018-05-10,ip1
2018-05-10,ip2
2018-06-14,ip3
2018-06-14,ip4
2018-06-15,ip1
2018-06-15,ip2

1
2
3
4
5
6
7
8
9
[root@master hivedata]# vi dynamic_partition_table.txt
[root@master hivedata]# cat dynamic_partition_table.txt
2018-05-10,ip1
2018-05-10,ip2
2018-06-14,ip3
2018-06-14,ip4
2018-06-15,ip1
2018-06-15,ip2
[root@master hivedata]#

现在我们通过一个案例进行演示动态分区的数据插入操作。将 dynamic_partition_table 中的数据按照时间(day),插入到目标表 d_p_t 的相应分区中。

首先,创建原始表。语法格式如下所示:

1
2
3
4
5
6
7
8
9
10
11
hive (zjdf)> create table dynamic_partition_table(day string,ip string) row format delimited fields terminated by ",";
OK
Time taken: 0.05 seconds
hive (zjdf)> show tables;
OK
dynamic_partition_table
student_ext
t_student
t_user
t_user_p
Time taken: 0.033 seconds, Fetched: 5 row(s)

其次,加载数据文件至原始表,语法格式如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
hive (zjdf)> load data local inpath '/export/data/hivedata/dynamic_partition_table.txt' into table dynamic_partition_table;
Loading data to table zjdf.dynamic_partition_table
Table zjdf.dynamic_partition_table stats: [numFiles=1, totalSize=90]
OK
Time taken: 0.234 seconds
hive (zjdf)> select * from dynamic_partition_table;
OK
2018-05-10 ip1
2018-05-10 ip2
2018-06-14 ip3
2018-06-14 ip4
2018-06-15 ip1
2018-06-15 ip2
Time taken: 0.062 seconds, Fetched: 6 row(s)
hive (zjdf)>

再次,创建目标表,语法格式如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
hive (zjdf)> create table d_p_t(ip string) partitioned by (month string,day string);
OK
Time taken: 0.045 seconds
hive (zjdf)> show tables;
OK
d_p_t
dynamic_partition_table
student_ext
t_student
t_user
t_user_p
Time taken: 0.023 seconds, Fetched: 6 row(s)
hive (zjdf)>

依次,动态插入,语法格式如下所示:

1
hive (zjdf)> insert overwrite table d_p_t partition (month,day) select ip,substr(day,1,7) as month,day from dynamic_partition_table;

image-20211202004106877

最后,查看目标表中的分区数据,语法格式如下所示:

1
2
3
4
5
6
7
hive (zjdf)> show partitions d_p_t;
OK
month=2018-05/day=2018-05-10
month=2018-06/day=2018-06-14
month=2018-06/day=2018-06-15
Time taken: 0.449 seconds, Fetched: 3 row(s)
hive (zjdf)>

# Hive 桶表操作

为了将表进行更细粒度的范围划分,我们可以创建桶表。桶表,是根据某个属性字段把数据分成几个桶(我们这里设置为 4,默认值是 - 1,可自定义),也就是在文件的层面上把数据分开。下面通过一个案例进行桶表相关操作的演示。

首先,我们先开启分桶功能,命令如下所示。

1
2
3
4
hive> set hive.enforce.bucketing = true;
//由于HQL最终会转成MR程序,所以分桶数与ReduceTask数保持一致,
//从而产生相应的文件个数
hive> set mapreduce.job.reduces=4;

其次,创建桶表,语法格式如下所示:

1
hive> create table stu_buck(Sno int,Sname string,Sex string,Sage int,Sdept string) clustered by(Sno) into 4 buckets row format delimited fields terminated by ',';

执行上述语句后,桶表 stu_buck 创建完成,并且以学生编号(Sno)分为 4 个桶,以 “,” 为分隔符的桶表。

再次,在 HDFS 的 /stu/ 目录下已有结构化数据文件 student.txt,我们需要将 student.txt 文件的复制到 /hivedata 目录下。然后,加载数据到桶表中,由于分桶表加载数据时,不能使用 Load Data 方式导入数据(原因在于该 Load Data 本质上是对数据文件进行复制或移动到 Hive 表所对应的地址中),因此在分桶表导入数据时需要创建临时的 student 表,该表与 stu_buck 表的字段必须一致,语法格式如下所示:

1
2
hive> create table student_tmp(Sno int,Sname string,Sex string,Sage int,Sdept string) 
row format delimited fields terminated by ',';

依次,加载数据至 student 表,语法格式如下所示

1
hive> load data local inpath '/hivedata/student.txt' into table student_tmp;

最后,将数据导入 stu_buck 表,语法格式如下所示:

1
hive> insert overwrite table stu_buck select * from student_tmp cluster by(Sno);

总体来说,分桶表是把表所映射的结构化数据分得更细致,且分桶规则与 MapReduce 分区规则一致,Hive 采用对目标列值进行哈希运算,得到哈希值再与桶个数取模的方式决定数据的归并,从而看出 Hive 与 MapReduce 存在紧密联系。使用分桶可以提高查询效率,例如执行 Join 操作时,两个表有相同的列字段,如果对这两张表都采取了分桶操作,那么就可以减少 Join 操作时的数据量,从而提高查询效率。它还能够在处理大规模数据集时,选择小部分数据集进行抽样运算,从而减少资源浪费。