- 分桶
1.1什么是hash值
将分桶之前,先讲一下什么是hash值(哈希值)?(比如 一个数或一个字符串的hash值,如5这个数的hash值。)
在这里,“5的hash值”这句话要中隐含着三个关键点:
- hash函数
- 被hash函数处理的元素
- hash函数处理后的结果值
例子:5 %4 = 1
其中
- “%4”就是一个hash函数 当然hash函数有各式各样的 如 md5、crc 等等hash函数
- “5” 就是被hash函数处理的元素
- “1” 就是被hash函数处理后的结果值
1.2 hash值得意义
hash一般用来数据查找。
比如有一个数组{1,2,200,300,123,444,555,666,777,222,211,22,33,4,5,33}
如何查找这个数组呢,最简单的当然是遍历数组,一个个比。但是如果这个数组很大,这样效率肯定很低。
为了更好的查找数组,我们可以把这个数组分成多个子数组,比如分成2个数组,那么可以简单的奇数一个数组,偶数一个数组,如果分成4个,那么可以根据最低端2位来,或者说v%4。
v%4这个就是hash值。对整数数据来说,取模是个很简单的hash函数。
那如果是字符串呢,常见的有crc,md5等。如md5,无论字符串多长,都计算出32字节的md5值,那么比较32字节会比比较大字符串快很多。
hash值相等,原值不一定相等。但是hash值不相等,原值必定不相等。这个特性也用来验证源码是否被修改,如发布一个程序,计算出md5值,那么使用者验证下md5值,如果不相等,则肯定被人修改过了,说不定就被人安装了木马。那么如果相等,则也有可能被修改过的,但是修改过之后md5要一样,就有点难,几乎不可能出现。
1.3 什么是分桶
简单说:就是将表中的某个字段,进行hash取值,将相同的hash值得的记录放入到同一个桶中,一个桶就是一个文件。
Hive以表中的某一列作为分桶的依据,桶的个数有用户设置,这里以用户表中的id字段来划分桶,划分4个桶。Hive会计算分桶列的hash值再以桶的个数取模来计算某条记录属于那个桶。
分桶的优点在于,将数据大致平均的、随机的放入多个桶中,这样方便对海量的数据做抽样调查、分析。
1.4 分桶操作
创建分桶表 (1)创建分桶表
上述语句,定义了一个按 id进行分桶,同时根据price进行排序,放入2个桶里 向该表进行插入数据时,也要体现上述三个特性 Id分桶 Price排序 2个桶 下面的(2)语句体现了 桶的个数 (3)语句体现了 price排序 和 id分桶
(2)向表中插入数据 -- 先设置变量,设置分桶为true, 设置reduce数量是分桶的数量个数 set hive.enforce.bucketing=true; set mapreduce.job.reduces=2;
(3)向表中插入数据 -- 我们从另外一个表t_tmp查询数据放到该表中 开始往创建的分桶表插入数据 (插入数据需要是已分桶, 且排序的) 注意这两点
往分桶表导入数据时,一般不适用load方式,而是使用insert。。。Select方式。 (4)分桶表和表的内容对应的文件存放在hfds的位置是: /user/hive/warehouse/bucket_db.db/t_order_buck/00000_0 /user/hive/warehouse/bucket_db.db/t_order_buck/00001_0 00000_0和00001_0最终对应的表数据的文件 因为set mapreduce.job.reduces=2,所以 两个 文件
向分桶表中插入数据方式: 1.上面向分桶表中插入数据的关键语句是:distribute by(id) sort by(price),可见按id字段进行分桶;按price字段进行桶内排序。排序和分桶的字段不相同 2.当排序和分桶的字段相同的时候也可以使用Cluster by(字段),代替上面的例子中distribute by(id) sort by(price)语句。
如
注意使用cluster by 就等同于分桶+排序(sort) 如果分桶和sort字段是同一个时,此时,cluster by = distribute by + sort by 可以尝试以下几种方式: insert into table stu_buc select id,name,score from student distribute by(id) sort by(id asc);
上面这个例子的“排序”和“分桶”的字段是相同的,都是id,因此可cluster by 方式向分桶表中写入数据 insert overwrite table stu_buck
insert overwrite table stu_buck
注:1、order by 会对输入做全局排序,因此只有一个reducer,会导致当输入规模较大时,需要较长的计算时间。 2、sort by不是全局排序,其在数据进入reducer前完成排序。因此,如果用sort by进行排序,并且设置mapred.reduce.tasks>1,则sort by只保证每个reducer的输出有序,不保证全局有序。 3、distribute by(字段)根据指定的字段将数据分到不同的reducer,且分发算法是hash散列。 4、Cluster by(字段) 除了具有Distribute by的功能外,还会对该字段进行排序。 5、创建分桶表并不意味着load进数据也是分桶的,你必须先分好桶,然后再放到表中。 |
1.5 分桶的意义
分桶表的作用:最大的作用是用来提高join操作的效率;但是两者的分桶数要相同或者成倍数。
(这句话的意思:一般join操作是两个表(a表,b表),a的分桶数 == b的分桶数 || a的分桶数 % b的分桶数 == 0)
为什么可以提高join操作的效率呢?因为按照MapReduce的分区算法,是Id的HashCode值模上ReduceTaskNumbers,所以一个ID会分到同一个桶中,这样合并就不用整个表遍历求笛卡尔积了,对应的桶合并就可以了。
分桶基本原理
就是往“分桶表”插入的记录,根据计算id的值hash值,插入到2个不同的数据源中。
把表(或者分区)组织成桶(Bucket)有两个理由:
(1)获得更高的查询处理效率。桶为表加上了额外的结构,Hive 在处理有些查询时能利用这个结构。具体而言,连接两个在(包含连接列的)相同列上划分了桶的表,可以使用 Map 端连接 (Map-side join)高效的实现。比如JOIN操作。对于JOIN操作两个表有一个相同的列,如果对这两个表都进行了桶操作。那么将保存相同列值的桶进行JOIN操作就可以,可以大大较少JOIN的数据量。
(2)使取样(sampling)更高效。在处理大规模数据集时,在开发和修改查询的阶段,如果能在数据集的一小部分数据上试运行查询,会带来很多方便。按我的理解,所谓Hive中的分桶,实际就是指的MapReduce中的分区。根据Reduce的数量,分成不同个数的文件。
- 数据类型
2.1基本数据类型
例子:
create table t_dataType( i1 tinyint , b boolean)
> row format delimited
> fields terminated by ',';
insert into t_dataType2 values(1,true); #正常
insert into t_dataType2 values(1000,true); #1000超过tinyint的范围
向i1中插入超过tinyint范围的值,则该字段的值是null
查找当前日期
Select current_date; #2018-06-1
Select current_timestamp; #2018-06-17 04:53:32.25
Select unix_timestamp(); #1529182469
Select from_unixtime(unix_timestamp()); #2018-06-17 04:55:10
create table t_base if not exists base(
salary double,
salary1 float,
dt timestamp #识别’2018-06-11 11:11:12’ 这样格式的字符串
)
row format delimited
fields terminated by ', ';
2.2复杂数据类型
例子:
STRUCT
类似于C、C#语言,Hive中定义的struct类型也可以使用点来访问。从文件加载数据时,文件里的数据分隔符要和建表指定的一致。
建表 create table IF NOT EXISTS t_struct (id int,info struct<name:string,hobby:string>) ROW FORMAT DELIMITED FIELDS TERMINATED BY ',' COLLECTION ITEMS TERMINATED BY ':' STORED AS TEXTFILE;
创建一个文本文件struct_data.txt 1,aa:football 2,bb:basketball 3,cc:sleeping
导入数据 LOAD DATA LOCAL INPATH '/data/struct_data.txt' OVERWRITE INTO TABLE t_struct;
查询数据 select * from t_struct;
1 {"name":"aa","hobby":"football"} 2 {"name":"bb","hobby":"basketball"}
Select id,info.name from t_struct
问题: 查询 姓名是zs 的用户id、用户名 、爱好 |
ARRAY
ARRAY表示一组相同数据类型的集合,下标从零开始,可以用下标访问
CREATE TABLE t_array (id int,name array<STRING>) ROW FORMAT DELIMITED FIELDS TERMINATED BY ',' COLLECTION ITEMS TERMINATED BY ':';
创建一个文本文件array_data.txt 1,cys:changyansong 2,gh:guanhang 3,cgjj:changguanjiujiu
导入数据 LOAD DATA LOCAL INPATH '/data/array_data.txt' OVERWRITE INTO TABLE t_array;
查询数据 select * from t_array; 1 ["cys","changyansong"] 2 ["gh","guanhang"]
Select id,name[0],name[1] from t_array;
问题: 数组长度大等于2 查询姓名和第一个元素 |
MAP
MAP是一组键值对的组合,可以通过KEY访问VALUE,键值之间同样要在创建表时指定分隔符。
create table if not exists t_map( uname string, mp1 Map<String,Double> ) row format delimited fields terminated by ' ' collection items terminated by ',' map keys terminated by ':';
创建一个文本文件map_data.txt zs high:180,weight:130 li high:177,weight:120
加载数据 LOAD DATA LOCAL INPATH '/data/map_data.txt' OVERWRITE INTO TABLE t_map;
查询数据 select * from map_1; zs {"high":180.0,"weight":130.0} li {"high":177.0,"weight":120.0}
Select mp1[‘hight’],mp1[‘weight’] from t_map;
问题: 查询map长度大于等于2 身高大于178的用户的名字 、 升高 、体重 |
UINON TYPES
Hive除了支持STRUCT、ARRAY、MAP这些原生集合类型,还支持集合的组合,不支持集合里再组合多个集合。
简单示例MAP嵌套ARRAY,手动设置集合格式的数据非常麻烦,建议采用INSERT INTO SELECT 形式构造数据再插入UNION表。
创建DUAL表,插入一条记录,用于生成数据 create table dual(d string); insert into dual values('X');
创建UNION表 CREATE TABLE IF NOT EXISTS t_union ( id int, info map<STRING,array<STRING>> ) ROW FORMAT DELIMITED FIELDS TERMINATED BY ',' COLLECTION ITEMS TERMINATED BY '-' MAP KEYS TERMINATED BY ':'
插入数据 insert overwrite table t_union select 1 as id,map('english',array(99,21,33)) as info from dual union all select 2 as id,map('english',array(44,33,76)) as info from dual union all select 3 as id,map('english',array(76,88,66)) as info from dual;
查询数据 select * from uniontype_1; 3 {"german":[76,88,66]} 2 {"chinese":[44,33,76]} 1 {"english":[99,21,33]}
select * from uniontype_1 where info['english'][2]>30; 1 {"english":[99,21,33]}
|
复杂数据类型综合练习:
若干公司中,员工的五险一金的扣款情况,以及扣款的所属片区。
- 函数
3.1内置函数
- 随机数函数 rand()
select rand(); //取得一个0-1的一个随机数
2、切分函数 split(st r , splitor) #注意特殊分割符的转义
select split(5.0,"\\.")[0];
select split(rand()*100,"\\.")[0];
3、substr(str,x,y) substring() # x表示起始位置(0/1是相同效果),y 表示获取字符串的长度
select substr(rand()*100,0,2);
select substring(rand()*100,0,2);
4、if(expr,truestament,falsestament)
select if(100>10,"this is true","this is false");
select if(2=1,"男","女");
select if(1=1,"男",(if(1=2,"女","不知道")));
select if(3=1,"男",(if(3=2,"女","不知道")));
5、case i when v ...else end; case when i<expr when ..else end;
select
case 6
when 1 then "100"
when 2 then "200"
when 3 then "300"
when 4 then "400"
else "others"
end;
create table if not exists cw(
flag int
);
load data local inpath '/home/flag' into table cw;
select
case c.flag
when 1 then "100"
when 2 then "200"
when 3 then "300"
when 4 then "400"
else "others"
end
from cw c;
select
case
when 1=c.flag then "100"
when 2=c.flag then "200"
when 3=c.flag then "300"
when 4=c.flag then "400"
else "others"
end
from cw c;
6、正则替换 regexp_replace(str,regex,"替换成什么样")
select regexp_replace("1.jsp",".jsp",".html");
7、cast : 类型转换 cast(str as double)
select 1;
select cast(1 as double); #将1转换为 double类型
select cast("12" as int); #将字符串12 转化为int类型
8、字符串连接 concat(str1,str2,...) concat_ws()
select "千峰" + 06 + "班级";
select concat("千峰",06,"班级");
#concat_ws表示在 "千峰"、"1603"和"班级"之间添加一个"|"分隔符
select concat_ws("|","千峰","1603","班级");
- 排名函数:
解决top n的问题
ROW_NUMBER() OVER函数的基本用法
语法:ROW_NUMBER() OVER(PARTITION BY COLUMN ORDER BY COLUMN)
简单的说row_number()从1开始,为每一个分组记录返回一个数字,这里的ROW_NUMBER() OVER (ORDER BY xlh DESC) 是先把xlh列降序,再为降序以后的每条xlh记录返回一个序号。
示例:
xlh row_num
1700 1
1500 2
1085 3
710 4
row_number() OVER (PARTITION BY COL1 ORDERBY COL2) 表示根据COL1分组,在分组内部根据 COL2排序,而此函数计算的值就表示每组内部排序后的顺序编号(该编号在组内是连续并且唯一的) 。
用实际例子演示 row_number()函数的效果
示例数据:
pId dept sal
1 a 10
2 a 12
3 b 13
4 b 12
5 a 14
6 a 15
7 a 13
8 b 11
9 a 16
10 b 17
11 a 14
执行查询语句
select pid,
Dept,
sal,
row_number()over(partition by dept order by sal )
from f_test;
查询后的输出效果:
看到row_number的效果了吧。
那这个row_number有什么用?
hive中一般取top n时,row_number(),函数就派上用场了,
如本例中求出每个部门中工资前三名的员工编号。
Select * from
(
select pid,
dept,
sal,
row_number()over(partition by dept order by sal desc) as rmp
from f_test
) as t_t_n
Where t_t_n.rmp <=3;
与row_number相近的函数还有,rank,dense_ran(),那这个两个函数有什么效果呢?
在上面事例数据的基础上执行如下语句:
select id,
name,
sal,
rank()over(partition by name order by sal desc ) rp,
dense_rank() over(partition by name order by sal desc ) drp,
row_number()over(partition by name order by sal desc) rmp
from f_test
效果为:
从结果看出
rank() 排序相同时会重复,总数不会变
dense_rank()排序相同时会重复,总数会减少
row_number() 会根据顺序计算
- 聚合函数:min() max() count() count(distinct ) sum() avg()
count(1):不管整行有没有值,只要出现就累计1
count(*):整行值只要有一个不为空就给累计1
count(col):col列有值就累计1
count(distinct col):col列有值并且不相同才累计1
11.其他
######
几乎任何数和 NULL操作都返回NULL
&& 和 || 不支持 使用and 和 or代替
size() 取数组或map数据类型的大小
round() 四舍五入取整
select length("cys"); #求字符串长度
locat(string substr ,string str) #求子字符串substr在字符串str中的位置
trim() #去除字符串中前后空格
parse_url() #解析url中的各个部分 http://localhost:8880/aa/bb/1.html
hash()
select from_unixtime(timestamp,"yyyy-MM-dd") #根据时间戳的值,获取相应的日期格式
select unix_timestamp(); #获取当前系统时间的时间戳值
select unix_timestamp("2017-04-19 00:00:00"); #把指定日期,转化为时间戳的值
select year("2017-04-19 00:00:00"); #根据指定日期,获取年
select month("2017-04-19 00:00:00");
select day("2017-04-19 00:00:00");
select hour("2017-04-19 00:00:00");
select weekofyear("2017-04-19 00:00:00");
其他函数参见 doc\Hive函数大全.pdf
3.2自定义函数和Transform
当Hive提供的内置函数无法满足你的业务处理需要时,此时就可以考虑使用用户自定义函数(UDF:user-defined function)。
3.2.1 自定义函数类别
UDF 作用于单个数据行,产生一个数据行作为输出。(数学函数,字符串函数)
UDAF(用户定义聚集函数):接收多个输入数据行,并产生一个输出数据行。(count,max)
3.2.2 UDF开发实例
0、先导入相应的jar包 (位置 当天资料里面software/lib-开发)
1、先开发一个java类,继承UDF,并重载evaluate方法
Package com.qianfeng.udf; import org.apache.hadoop.hive.ql.exec.UDF; import org.apache.hadoop.io.Text;
public final class Lower extends UDF{ public Text evaluate(final Text s){ if(s==null){return null;} return new Text(s.toString().toLowerCase()); } } |
2、打成jar包上传到服务器
3、将jar包添加到hive的classpath,在hive命令行下执行下面语句
hive>add JAR /home/hadoop/udf.jar;
4、创建临时函数与开发好的java class关联
Hive>create temporary function Lower as 'com.qianfeng.udf.Lower';
5、即可在hql中使用自定义的函数strip
Select Lower(name),age from t_test;
- 如果该函数确定不再使用时可以删除
Drop temporary function Lower;
案例:
根据生日得到年龄,使用自定义函数实现。
4.3.3 Transform实现
Hive的 TRANSFORM 关键字提供了在SQL中调用自写脚本的功能
适合实现Hive中没有的功能又不想写UDF的情况
有一个u_data表,其内容如下:
用户在几点看了电影,并给电影进行了评分
movieid(电影id) |
rating(评分) |
unixtime(时间戳) |
userid(用户id) |
1193 |
5 |
978300760 |
1 |
661 |
3 |
978302109 |
1 |
需求:将时间戳变为周几
1.创建一个新表u_data_new CREATE TABLE u_data_new ( movieid INT, rating INT, weekday INT, #新表字段存放“周几型日期” userid INT) ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t'; 2.将phyton脚本加入hive add FILE weekday_mapper.py; 3.使用脚本 INSERT OVERWRITE TABLE u_data_new SELECT TRANSFORM (movieid, rating, unixtime,userid) #要转换的字段,相当于是脚本的输入字段 USING 'python weekday_mapper.py' #使用脚本转换 AS (movieid, rating, weekday,userid) #脚本返回的值放入这些字段中 FROM u_data; |
其中weekday_mapper.py内容如下
#!/bin/python import sys import datetime
for line in sys.stdin: //从标准输入一行 line = line.strip() //得到一行的数据 movieid, rating, unixtime,userid = line.split('\t') weekday = datetime.datetime.fromtimestamp(float(unixtime)).isoweekday() print '\t'.join([movieid, rating, str(weekday),userid]) |
使用示例2:下面的例子则是使用了shell的cat命令来处理数据
FROM invites a INSERT OVERWRITE TABLE events SELECT TRANSFORM(a.foo, a.bar) AS (oof, rab) USING '/bin/cat' WHERE a.ds > '2008-08-09'; |