0%

Hive学习笔记下

说在前面的话

由于Hadoop开发难度较高,并且非Java程序员有对HDFS的数据做MapReduce操作的需求,Hive应运而生。本文主要从分区分桶函数参数配置视图以及练习来介绍Hive

Hive分区

为了提高查询效率,因此将不同数据文件存到不同的目录。分区数量虽然没有限制,但是要结合具体的业务需求来建立,分区粒度细会造成文件的多次IO导致查询效率低,分区粒度粗也会造成大表扫描进而导致查询效率同样低,比如创建年月日时分秒分区的数据,针对只查询日级别分区的需求,那么最好的创建分区的方式就是只创建日分区的数据表。所以分区粒度的选择不能过粗也不能过细哦~🥳

分区表创建

我们首先创建带一个分区的Hive表,记住分区字段绝对不能出现在创建表的列中哦!!!

hive> CREATE TABLE psn4 (
id int,
name string,
-- age int,
likes array<string>,
address map<string, string>
)
PARTITIONED BY (age int)
ROW FORMAT DELIMITED
FIELDS TERMINATED BY ','
COLLECTION ITEMS TERMINATED BY '-'
MAP KEYS TERMINATED BY ':'
LINES TERMINATED BY '\n';

然后再试试创建带两个分区的Hive表

hive> CREATE TABLE psn5 (
id int,
name string,
likes array<string>,
address map<string, string>
)
PARTITIONED BY (age int, sex string)
ROW FORMAT DELIMITED
FIELDS TERMINATED BY ','
COLLECTION ITEMS TERMINATED BY '-'
MAP KEYS TERMINATED BY ':'
LINES TERMINATED BY '\n';

数据导入

首先将数据导入一个分区的分区表

hive> LOAD DATA LOCAL INPATH '/root/data'  INTO TABLE psn4 PARTITION (age=10);

将数据导入两个分区表,记住导入数据的时候需要将分区写完整

hive> LOAD DATA LOCAL INPATH '/root/data'  INTO TABLE psn5 PARTITION (age=10, sex='boy');

添加分区

添加分区的字段必须是在创建表的时候定义的

hive> ALTER TABLE psn4 ADD PARTITION (age=10);
hive> ALTER TABLE psn5 ADD PARTITION (age=20, sex='boy');
hive> ALTER TABLE psn5 ADD PARTITION (age=30, sex='girl');

下面这句话会报错,因为额外增加了分区的种类!!!

hive> ALTER TABLE psn4 ADD PARTITION (age=10, height=112);

删除分区

通过下面语句可以将所有分区值包含sex=’boy’的分区删除掉,假设age=10的分区中只有sex=’boy’的一个分区,那么age=10这个分区也会被删除

hive> ALTER TABLE psn5 DROP partition (sex='boy')

如果删除外部表的某个分区后HDFS上仍然保留分区数据,如果再次向同一分区导入新文件,会在查询该分区数据的时候将之前保留的老数据一起显示出来,如果导入同名文件会自动覆盖掉之前的老文件

动态分区

Hive支持两种类型的分区表,分别是静态分区表和动态分区表,二者区别在于导入数据时,静态分区表需要手动指定分区来进行数据导入,而动态分区通过判断位置进行数据导入

Step1. 创建原始表

CREATE TABLE psn(
id int,
name string,
age int,
sex string,
likes array<string>,
address map<string, string>
)
ROW FORMAT DELIMITED
FIELDS TERMINATED BY ','
COLLECTION ITEMS TERMINATED BY '-'
MAP KEYS TERMINATED BY ':'
LINES TERMINATED BY '\n';

Step2. 创建分区表

CREATE TABLE partipsn(
id int,
name string,
likes array<string>,
address map<string, string>
)
partitioned by(age int, sex string)
ROW FORMAT DELIMITED
FIELDS TERMINATED BY ','
COLLECTION ITEMS TERMINATED BY '-'
MAP KEYS TERMINATED BY ':'
LINES TERMINATED BY '\n';

Step3. 启动动态分区

将mode设置成nostrict, 如果是strict必须满足有一个分区是静态的

set hive.exec.dynamic.partition=true;
set hive.exec.dynamic.partition.mode=nostrict

Step4. 导入数据

# 使用静态分区时,需要指定分区的值
insert table partipsn partition (age=12, sex='man')
select name, ip from psn where age= 12 and sex='man';

# 使用动态分区
insert table partipsn partition(age, sex)
select id,name,likes,address,age,sex distribute by age, sex;

Hive分桶

分桶表是对列值取哈希值的方式,将不同数据放到不同文件中存储。一般用在两个场景,第一是数据抽样,因为分桶操作本质就是对数据进行了一次抽样,对桶内数据再抽样可以保证数据足够随机,第二是map-join,一般MapReduce根据bucket来分配reduce,指定了分桶之后,join过程可以减缓shuffle。本文只通过数据抽样来介绍分桶

Step1. 开启分桶

hive> set hive.enforce.bucketing=true;
# step5 通过分桶表进行抽样
hive> select * from psnbucket tablesample(bucket 1 outof 4 on columns);

# tablesample(bucket x out of y)语法说明
# 假设共有32个桶
# tablesample(bucket 2 out of 4)
# x=2表示从第2个桶开始取数据, y=4是桶的因子或者倍数
# 抽取的数据量为32/y=8, 即分别从2,6, 10, 14, 18, 22, 26, 30桶中取数据

# tablesample(bucket 3 out of 256)
# 从第3个桶开始取数据
# 抽取的数据量为32/256=0.125, 接下来从3号桶中取1/8个数据,即取1条数据

step2. 创建原始表

hive> create table psn(id int, name string, age int)
row format delimited
fields terminated by ',';

# 将数据数据导入原始表
hive> load data local inpath '/root/data' into table psn;

Step3. 创建分桶表并指定分桶

hive> create table psnbucket(id int, name string, age int)
clustered by (age) into 4 buckets
row format delimited
fields terminated by ',';

Step4. 将数据导入到分桶表

将原始表数据导入到分桶表要经过MapReduce,执行流程就是从HDFS取出数据,然后按照规则进行分桶,最后导入到psnpucket表中

hive> insert into table psnbucket 
select id, name, age from psn;

其中测试数据data

1,tom,11
2,cat,22
3,dog,33
4,hive,44
5,hbase,55
6,mr,66
7,alice,77
8,scala,88

在处理大量小文件的场景下,执行任务的时间大部分都消耗在资源分配和任务调度,所以分桶表使用场景很少

Hive函数

Hive中的内置函数比较多,本文只列举部分的函数

内置运算符

# 获取名叫A的array中index=n的值
hive> A[n]

# 获取名叫M的map中ke=name的值
hive> M[name]

# 获取名叫S的结构体中字段为x的值
hive> S.x

内置函数

数学函数

# 四舍五入
hive> round

# 向下取整
hive> floor

# 向上取整
hive> ceil

# 取随机数,seed表示随机种子
hive> rand(int seed)

# 指数函数
hive> exp(double a)

# 对数函数
hive> ln(double b)
hive> log(double a, double b)

# 幂函数
hive> pow

# 开方
hive> sqrt

# 三角函数
hive> sin
hive> cos

收集函数

# 获取集合的大小
hive> size(Map<K,V>)

类型转换函数

# 将某个字段转成指定类型
hive> cast(expr as <type>)

日期函数

# 将字符串格式的时间戳转成年月日
hive> to date(string timestamp)

# 获取日期中的月
hive> moonth(string date)

# 获取日期
hive> day

# 获取当前月的第几天
hive> dayofmonth

# 获取小时
hive> hour

# 获取分钟
hive> minute

# 获取秒
hive> second

条件函数

# 如果testCondition为true,则返回v1,否则返回v2或者空值
hive> if(boolean testCondition, T v1, T v2)

# 返回一组数据中,第一个不为null的值
hive> coalesce(T v1, T v2, ...)

# 当a=b时,返回c 当a=d的时,返回e 否则返回f
hive> case a when b then c when d then e else f

# 当a成立时返回b 当c成立时返回d 否则给f
hive> case when a then b when c then d else f

字符串函数

# 获取字符串长度
hive> length

# 颠倒字符串
hive> reverse

# 合并字符串
hive> concat

# 以指定连接符合并字符串
hive> concat_ws(string sep,string a)

# 取子串
hive> substr

# 变大写
hive> upper

# 变小写
hive> lower

# 去除左右空格
hive> trim

# 去除左侧空格
hive> ltrim

# 去除右侧空格
hive> rtrim

# 将字符串转成正则表达式
hive> regexp

聚合函数

# 取最小值
hive> min

# 取最大值
hive> max

# 无偏方差和对应的标准差
hive> var_pop
hive> stddev_samp

# 有偏方差和对应的标准差
hive> var_samp
hive> var_sample

表生成函数

# explode 
# 可以将map或者array拆成多行生成一列
hive> explode()

# get_json_object
# 获取单层值
hive> select get_json_object(data, '$.owner') from test;

# 获取多层值
hive> select get_json_object(data,'$.store.bicycle.price') from test;

# 获取数组值
hive> select get_json_object(data, '$.store.fruit[0]') from test;

其中test表中data字段的数据为

{
"store":
{
"fruit":[{"weight":8,"type":"apple"}, {"weight":9,"type":"pear"}],
"bicycle":{"price":19.95,"color":"red"}
},
"email":"amy@only_for_json_udf_test.net",
"owner":"amy"
}

自定义函数

自定义函数包含UDF,UDAF,UDTF,其中UDF是一进一出,UDAF多进一出,UDTF是一进多出

UDF开发

假设我们想将数据中的姓名进行脱敏

step1. 构建UDF,重写evaluate

import org.apache.hadoop.hive.ql.exec.UDF;
import org.apache.hadoop.io.Text;

public class TuoMin extends UDF{
public Text evaluate(final Text s){
if (s == null){
return null;
}
String str = s.toString().substring(0, 3) + "***";
return new Text(str);
}
}

step2. 将程序打jar包发送到服务器

step3. 添加jar包

hive> add jar /root/tumin.jar;

step4. 创建临时函数

hive> CREATE TEMPORARY FUNCTION tm as 'hive.udf.ADD';

step5. 用开发的临时函数进行查询

hive> select tm(name) from psn;

Hive参数配置

假设我们想执行Hive查询操作的时候需要打印表头,Hive参数配置共有以下四种方法

  1. 配置hive-site

    通过配置hive-site.xml,使得配置信息永久生效

  2. 配置.hiverc

    可以配置家目录下的.hiverc,因为在启动hive客户端的时候,会执行家目录下的.hiverc配置

  3. 命令行

    在命令行配置hive参数,局部改变配置,即当进入Hive后,退出再登录就失效

    # Hive中的参数,变量都是以命名空间开头,比如hiveconf
    hive --hiveconf hive.cli.print.header=true
  4. 客户端

    登录Hive客户端后进行配置,局部改变配置,即当退出Hive再登录就失效

    hive> set hive.cli.print.header=true

如果想查看Hive历史命令,可以通过.hivehistory来查询,如果没有该文件,就需要创建

Hive视图

Lateral View

通过UDTF函数拆分成多行,但是通过select查询只能存在单个UDTF,不能包含多个UDTF以及其他字段,Lateral View可以解决该问题,解决方法是将多行结果构成一个有别名的虚拟表。假设我们想要统计人员表中爱好以及城市种类数

hive> select count(distinct(col1)), count(distinct(col2)) from psn
lateral view explode(likes) psn as col1
lateral view explode(address) psn as col2, col3;

其中人员表psn

psn.id	psn.name	psn.age	psn.sex	psn.likes	psn.address
1 小明1 10 man ["lol","book","movie"] {"beijing":"\"shangxuetang\"","shanghai":"pudong"}
2 小明2 20 boy ["lol","book","movie"] {"beijing":"\"shangxuetang\"","shanghai":"pudong"}
3 小明3 10 man ["lol","book","movie"] {"beijing":"\"shangxuetang\"","shanghai":"pudong"}
4 小明4 20 man ["lol","book","movie"] {"beijing":"\"shangxuetang\"","shanghai":"pudong"}
5 小明5 10 boy ["lol","movie"] {"beijing":"\"shangxuetang\"","shanghai":"pudong"}
6 小明6 20 boy ["lol","book","movie"] {"beijing":"\"shangxuetang\"","shanghai":"pudong"}
7 小明7 10 man ["lol","book"] {"beijing":"\"shangxuetang\"","shanghai":"pudong"}
8 小明8 10 man ["lol","book"] {"beijing":"\"shangxuetang\"","shanghai":"pudong"}
9 小明9 20 boy ["lol","book","movie"] {"beijing":"\"shangxuetang\"","shanghai":"pudong"}

视图

和关系型数据库一样,Hive也支持视图,有以下三个特点

  1. 不支持物化视图,即视图不可持久化
  2. 只能做查询操作,不能做load数据操作
  3. 视图也是表,所以会保留元信息在MySQL,但是Hive中是不存数据的
 # 创建视图
# 视图也是表,即在mysql里面可以查到,但在HDFS里面没有数据
hive> create view v_psn as select id, name from psn;

Hive练习

掉话率统计

统计基站掉线率最高的前10个基站

Step1. 创建原始表

hive> create table call_monitor(
imei string, # 基站编号
drop_num int, # 掉话量
duration int # 通话量
)
row format delimited
fields terminated by ',';

# 导入数据
load data local inpath '/root/calldata' into table wc;

Step2. 创建结果表

hive> create table call_result(
imei string,
drop_num int,
duration int,
drop_rate double
)
row format delimited
fields terminated by ',';

Step3. 原始表数据导入结果表

hive> from call_monitor cm;
insert into call_result
select cm.imei, sum(cm.drop_num) sdrop, sum(cm.duration) sdura, sum(cm.drop_num)/sum(cm.duration) s_rate group by cm.imei order by s_rate desc limit 10

其中排序命令orderby放到一个reduce里进行全排序,而sortby放到不同的reduce里进行局部排序

WordCount

Step1. 创建原始表

# 创建原始表
hive> create table wc(
word string
);

# 导入数据
load data local inpath '/root/wcdata' into table wc;

Step2. 创建结果表

# 创建结果表
hive> create table wc_result(
word string,
count int
);

Step3. 原始表数据导入结果表

# 将结果导入结果表
from (select explode(split(word, ' ')) word from wc) t1
insert into wc_result
select t1.word, count(t1.word) group by t1.word;

其中wcdata数据为

hello world hive
hadoop hello hive
hello go hadoop
world