hive是基于Hadoop构建的一套数据仓库分析系统,它提供了丰富的SQL查询方式来分析存储在Hadoop分布式文件系统中的数据。
可以将结构化的数据文件映射为一张数据库表,并提供完整的SQL查询功能。
可以将SQL语句转换为MapReduce任务运行,通过自己的SQL查询分析需要的内容,这套SQL简称Hive SQL,使不熟悉mapreduce的用户可以很方便地利用SQL语言查询、汇总和分析数据。
可扩展: Hive可以自由的扩展集群的规模,一般情况下不需要重启服务。
延展性: Hive支持用户自定义函数,用户可以根据自己需求来实现自己的函数。
容错:良好的容错性,节点出现问题SQL仍可完成执行。
CLI(hive shell)
JDBC/ODBC(java访问hive)
WEBUI(浏览器访问hive)
解析器(SQL Parser):将SQL字符串转换成抽象语法树AST,这一步一般都用第三方工具库完成,比如antlr;对AST进行语法分析,比如表是否存在、字段是否存在、SQL语义是否有误。
编译器(Physical Plan):将AST编译生成逻辑执行计划。
优化器(Query Optimizer):对逻辑执行计划进行优化。
执行器(Execution):把逻辑执行计划转换成可以运行的物理计划。对于Hive来说,就是MR/Spark。
Hive通过给用户提供的一系列交互接口,接收到用户的指令(SQL),使用自己的Driver,结合元数据(MetaStore),将这些指令翻译成MapReduce,提交到Hadoop中执行,最后,将执行返回的结果输出到用户交互接口。
环境准备:安装jdk、hadoop、mysql(元数据管理使用)
mysql驱动:mysql-connector-java-[版本号].jar
Hive安装包
第一步
tar -zxvf [hive包] -C /解压到的路径
第二步
mkdir warehouse
第三步
vi /etc/profile
#增加内容
export HIVE_HOME=/HIVE的安装路径
export PATH=...:$HIVE_HOME/bin
配置完成后生效:
source /etc/profile
第四步
vi hive-env.sh
#增加内容:
export HADOOP_HOME=/opt/hadoop260 #hadoop所在路径
export HIVE_CONF_DIR=/opt/hive/conf #hive/conf路径
export HIVE_AUX_JARS_PATH=/opt/hive/lib #hive Jar包路径
第五步
vi hive-site.xml
配置文件#写入:
<configuration>
<!-- 设置warehouse目录的路径 -->
<property>
<name>hive.metastore.warehouse.dir</name>
<value>/opt/hive/warehouse</value>
</property>
<!-- 是否在本地 -->
<property>
<name>hive.metastore.local</name>
<value>true</value>
</property>
<!-- 设置连接数据库并创建hive库 -->
<property>
<name>javax.jdo.option.ConnectionURL</name>
<value>jdbc:mysql://192.168.**.**:3306/hive?createDatabaseIfNoExist=true</value>
</property>
<!-- MySQL驱动名称 -->
<property>
<name>javax.jdo.option.ConnectionDriverName</name>
<value>com.mysql.jdbc.Driver</value>
</property>
<!-- MySQL用户名称 -->
<property>
<name>javax.jdo.option.ConnectionUserName</name>
<value>root</value>
</property>
<!-- MySQL用户密码 -->
<property>
<name>javax.jdo.option.ConnectionPassword</name>
<value>ok</value>
</property>
</configuration>
第六步
第七步
第八步:
schematool -dbType mysql -initSchema
第九步
hive
启动成功后可以测试一些操作
测试建表:create table aaa(name varchar(10));
查看当前所有表:show tables;
插入数据:insert into aaa values(‘test’),(‘test2’);
HIveServer1 CLI启用:hive
设置一些基本参数,让hive使用起来更便捷,比如:
#进入hive后设置为本地运行模式,在学习的时候会减少运行时间:set hive.exec.mode.local.auto=true;
#让提示符显示当前库:hive>set hive.cli.print.current.db=true;
#显示查询结果时显示字段名称:hive>set hive.cli.print.header=true;
但是这样设置只对当前会话有效,重启hive会话后就失效,解决办法:
在linux的当前用户目录中,编辑一个.hiverc文件,将参数写入其中:
vi .hiverc
set hive.cli.print.header=true;
set hive.cli.print.current.db=true;
HiveServer2 Beeline启用(方式一):
bin/hiveserver2 start
beeline -u "jdbc:hive2://localhost:10000
HiveServer2 Beeline启用(方式二):
beeline
!connect jdbc:hive2://localhost:10000
大量的hive查询任务,如果用交互式shell来进行输入的话,显然效率及其低下,因此,生产中更多的是使用脚本化运行机制:
该机制的核心点是:hive可以用一次性命令的方式来执行给定的hql语句
hive -e "insert into table t_test select * from t_test_1;"
#创建文件Vim test.sql
#写入语句
Select count(1) from test;
Select name from test;
#执行
bin/hive -f test.sql
vi hive.sh
脚本命令
function check_process()
{
pid=$(ps -ef 2>/dev/null | grep -v grep | grep -i $1 | awk '{print $2}')
ppid=$(netstat -nltp 2>/dev/null | grep $2 | awk '{print $7}' | cut -d '/' -f 1)
echo $pid
[[ "$pid" =~ "$ppid" ]] && [ "$ppid" ] && return 0 || return 1
}
function hive_start()
{
metapid=$(check_process HiveMetastore 9083)
cmd="nohup hive --service metastore >$HIVE_LOG_DIR/metastore.log 2>&1 &"
cmd=$cmd" sleep 4; hdfs dfsadmin -safemode wait >/dev/null 2>&1"
[ -z "$metapid" ] && eval $cmd || echo "Metastroe服务已启动"
server2pid=$(check_process HiveServer2 10000)
cmd="nohup hive --service hiveserver2 >$HIVE_LOG_DIR/hiveServer2.log 2>&1 &"
[ -z "$server2pid" ] && eval $cmd || echo "HiveServer2服务已启动"
}
function hive_stop()
{
metapid=$(check_process HiveMetastore 9083)
[ "$metapid" ] && kill $metapid || echo "Metastore服务未启动"
server2pid=$(check_process HiveServer2 10000)
[ "$server2pid" ] && kill $server2pid || echo "HiveServer2服务未启动"
}
case $1 in
"start")
hive_start
;;
"stop")
hive_stop
;;
"restart")
hive_stop
sleep 2
hive_start
;;
"status")
check_process HiveMetastore 9083 >/dev/null && echo "Metastore服务运行正常" || echo "Metastore服务运行异常"
check_process HiveServer2 10000 >/dev/null && echo "HiveServer2服务运行正常" || echo "HiveServer2服务运行异常"
;;
*)
echo Invalid Args!
echo 'Usage: '$(basename $0)' start|stop|restart|status'
;;
esac
sh hive.sh start
sh hive.sh status
sh hive.sh stop
sh hive.sh restart
create database if not exists [库名];
use [库名];
show databases;
describe database [库名];
alter databases [库名] set owner user [用户名];
drop database [库名] cascade;
select current_database();
什么时候会使用内部表?
第一种:抽取过来的业务数据,其实用外部表或者内部表问题都不大,就算被误删,恢复起来也是很快的,如果需要对数据内容和元数据进行紧凑的管理, 那还是建议使用内部表
第二种:在做统计分析时候用到的中间表,结果表可以使用内部表,因为这些数据不需要共享,使用内部表更为合适。并且很多时候结果分区表我们只需要保留最近3天的数据,用外部表的时候删除分区时无法删除数据。
//字段后面需要跟逗号,最后一个字段跟括号,其他操作无需逗号与括号
//hive的注释是双横杠: --
//格式化后面可以自定义分隔符,具体怎么设定根据数据文件来
//举例当前表的数据格式:aaa|111|bbb,ccc|ddd:eee|
//如果相同名字的表已经存在,则抛出异常;用户可以用 IF NOT EXISTS 选项来忽略这个异常。
//示例
create table employee(
name string, --字段属性string
id int, --字段属性int
address array<string>, --字段属性array<string>
technol map<string,int>) --字段属性map<string,int>
row format delimited --格式化
fields terminated by '|' --fields:区分列之间的分隔符
collection items terminated by ',' --collection:定义array的分隔符
map keys terminated by ':' --map keys:定义map的分隔符
lines terminated by '\n'; --line:换行的分隔符
truncate table [表名];
DROP TABLE [表名] [With PERGE];
With PERGE直接删除(可选),否则会放到 .Trash目录ALTER TABLE [表名] RENAME TO [修改后的表名];
常用于数据备份什么时候会使用外部表?
第一种:管理元数据的时候,因为只读关系会使用外部表
第二种:分享数据,分享给其他用户,可以用外部表,因为外部表比较灵活,甚至可以设置到用户自己的hdfs路径下
第三种:日志数据,每天采集的ng日志和埋点日志,在存储的时候建议使用外部表,因为日志数据是采集程序实时采集进来的,一旦被误删,恢复起来非常麻烦。而且外部表方便数据的共享。
//示例
create external table 表名(
...
[字段]
...
)
...
[分隔符]
...
//外部表支持的文件格式:
//textfile(文本)、sequencefile、refile、orcfile
//其中TEXTFILE为默认格式,建表时不指定默认为这个格式,导入数据时会直接把数据文件拷贝到hdfs_上不进行处理
//SEQUENCEFILE, RCFILE, ORCFILE格式的表不能直接从本地文件导入数据,数据要先导入到textfile格式的表中,然后再从表中用insert导 入SequenceFile,RCFile,ORCFile表中
stored as [文件格式] -- 文件存储格式
location '/存储路径'; -- 数据存储路径
show partitions [表名];
select * from [表名] where [分区名]=[值] ;
什么是分区?
分区(partition)是用来增加表查询速度的最佳途径,那分区相对来说就是在现有的表数据的基础上进一步分离,这个分区实指的是在HDFS的目录下建立子文件夹,每个子文件夹对应着一个分区的名字,然后我们的数据按照子文件夹重新进行组织,那我们查询的时候呢会用到分区的这个列,这样查询就会直接到子文件夹,就避免扫描表文件夹下的所有数据,这就是为什么说分区可以加速查询的一个重要原因
然后分区有静态分区和动态分区,那么通常在插入数据的时候用静态分区会比较频繁,因为静态分区我们是可以手动的去建立起来,那么相反当我们在做数据处理的时,候呢我们通常会喜欢用动态分区,因为动态分区的分区情况是由数据本身决定的,所有说动态分区更灵活,而且动态分区产生的分区个数呢是由程序控制,不需要我们人为去写,比较方便,但是动态分区有一个重要的问题,就是这个动态分区会根据数据产生很多的子目录,如果对分区的逻辑不是很了解的时候,就会很容易把整个集群的元数据空间给耗费掉,会造成一个比较大的影响,所以用动态分区的时候需要格外的注意,这就是分区的
分区操作命令
//格式
CREATE TABLE employee_partitioned(
...
)
PARTITIONED BY ([分区名] [类型], [二级分区名] [类型],...) --通过PARTITIONED BY定义分区(可创建多级分区)
...;
//示例
CREATE TABLE wedw_tmp.lzc_test(
user_id string COMMENT 'id',
user_name string COMMENT '用户姓名',
user_age int COMMENT '用户年龄'
)
COMMENT'用户表'
partitioned by(date_id string,province_id string) //设置两级分区
row format delimited fields terminated by ',';
分区表如何插入数据?
分区表无法指定插入某个字段需要插入整行数据,可用values手动插入数据,也可用select 插入手动输入的数据、插入查询的结果数据
values:insert into [表名] partition ([分区名]=[值],…) values(“数据”);
select:insert into [表名] partition([分区名]=[值],…) select ‘手动输入数据’/查询结果的数据;
ALTER TABLE [表名] ADD PARTITION ([分区名]=[值],[二级分区名]=[值],...) PARTITION ([分区名]=[值],[二级分区名]=[值],...); //添加分区
ALTER TABLE [表名] DROP PARTITION ([分区名]=[值],[二级分区名]=[值],...);//删除分区
动态分区是根据数据查询自动分区,如果操作不当可能会造成成千上万的分区,所有风险较高,默认情况是无法操作的,需要手动开启
在hive下开启命令:
set hive.exec.dynamic.partition=true;
set hive.exec.dynamic.partition.mode=nonstrict;
在较大数据量时还需要设定一下动态分区最大数
set hive.exec.max.dynamic.partitions.pernode=10000;
set hive.exec.max.dynamic.partitions=50000;
//动态分区示例
insert into table employee_partitioned partition(year, month)//通过查询语句结果动态创建分区
select name,array('Toronto') as work_place,
named_struct("sex","male","age",30) as sex_age,
map("python",90) as skills_score,
map("r&d", array('developer')) as depart_title,
year(start_date) as year,//这里的year(字段)是函数,通过字段start_date获取年份,用做分区
month(start_date) as month//这里的month(字段)也是函数,通过字段start_date获取月份,用坐分区
from employee_hreh ;
什么是分桶?
分桶(BucketTable)其实是hive中非常有特点的一类表,之所以叫分桶呢,是因为我们希望能进一步,对数据查询性能进一步增强,如果说partition是在folder级别上进行数据分隔,那么BucketTable就是文件本身进行分隔,那么BucketTable的实质呢就是按照一定的选择规律,把相关数据放在小文件上,那么当我们查询数据的时候确定了分桶列,就会直接找到目标文件,也就不需要去扫描所有文件,从而也加速了查询的性能,那么BucketTable还有一个优势呢就是方便我们进行数据抽样,基于这个分桶列进行数据抽样,还有一个分桶表的用处就是支持ATC事务表
分桶抽样(Buckets)
//随机抽样基于整行数据
SELECT * FROM table_name TABLESAMPLE(BUCKET 3 OUT OF 32 ON rand()) s;//在3个桶中随机抽取32条整行数据
//随机抽样基于指定列(使用分桶列更高效)
SELECT * FROM table_name TABLESAMPLE(BUCKET 3 OUT OF 32 ON id) s;
//随机抽样基于block size
SELECT * FROM table_name TABLESAMPLE(10 PERCENT) s;
SELECT * FROM table_name TABLESAMPLE(1M) s;
SELECT * FROM table_name TABLESAMPLE(10 rows) s;
视图概述
通过隐藏子查询、连接和函数来简化查询的逻辑结构
虚拟表,从真实表中选取数据
只保存定义,不存储数据
如果删除或更改基础表,则查询视图将失败
视图是只读的,不能插入或装载数据
应用场景
将特定的列提供给用户,保护数据隐私
查询语句复杂的场景
视图操作命令
//创建视图支持 CTE, ORDER BY, LIMIT, JOIN, etc.
hive> CREATE VIEW view_name AS SELECT statement;//创建视图
//查找视图 (SHOW VIEWS 在 hive v2.2.0之后)
hive> SHOW TABLES;
//查看视图定义
hive> SHOW CREATE TABLE view_name;
//删除视图
hive> DROP view_name;
//更改视图属性
hive> ALTER VIEW view_name SET TBLPROPERTIES ('comment' = 'This is a view');
//更改视图定义
hive> ALTER VIEW view_name AS SELECT statement;
怎么知道一个表是不是视图?
第一种:show create table …
第二种:desc formatted …
select name,work_place,loc from employee lateral view outer explode(split(null,',')) a as loc;
select name,wps,skill,score from employee
lateral view explode(work_place) work_place_single as wps
lateral view explode(skills_score) sks as skill,score;
注意:导本地文件和导HDFS文件的区别:
本地文件导入表:复制
hdfs文件导入表:移动
LOAD:导入数据到表中
//load:将数据文件移动到目标表/分区下
//local:表示数据文件位于本地(没有local表示数据文件位于hdfs)
//OVERWRITE:表示覆盖现有表数据
//语法格式
//将本地目录下的数据文件追加到表目录下
hive> LOAD DATA LOCAL INPATH '文件全路径' INTO TABLE 表名;
//将将本地目录下的数据文件复制到表目录下并且覆盖所有数据
hive> LOAD DATA LOCAL INPATH '文件全路径' OVERWRITE INTO TABLE 表名;
//将本地目录下的数据文件复制到指定表分区目录下并且覆盖目录下的所有数据
hive> LOAD DATA LOCAL INPATH '文件全路径' OVERWRITE INTO TABLE 表名 PARTITION (分区字段=值,...);
//没有LOCAL,文件位于HDFS文件系统中
hive> LOAD DATA INPATH '文件全路径' OVERWRITE INTO TABLE 表名;
Directory:导出表数据到本地
//使用insert语句将数据插入导出到文件
//文件插入只支持OVERWRITE
//支持来自同一个数据源/表的多次插入
//LOCAL:写入本地文件系统
//默认数据以TEXT格式写入,列由^A分隔
//支持自定义分隔符导出文件为不同格式,CSV,JSON等
//从同一数据源插入本地文件,hdfs文件,表
//将查询表的结果覆盖到指定目录下
//示例一:同时导出至多位置
from ctas_employee //指定数据源
insert overwrite local directory '/tmp/out1' select * //指定导出至本地目录下
insert overwrite directory '/tmp/out1' select * //指定导出至HDFS目录下
insert overwrite table employee_internal select *; //指定导出至其他表中
//示例二:指定导出数据以逗号分隔
insert overwrite directory '/tmp/out3'
row format delimited fields terminated by ','
select * from ctas_employee;
//其他方式从表获取文件
hdfs dfs -getmerge <table_file_path>
IMPORT(导入)与EXPORT(导出)
//常用于数据迁移场景
//除数据库,可导入导出所有数据和元数据
//导出数据:EXPORT
EXPORT TABLE employee TO '/tmp/output3';
//导出指定表分区下数据
EXPORT TABLE employee_partitioned partition (year=2014, month=11) TO '/tmp/output5';
//导入数据:IMPORT
IMPORT TABLE employee FROM '/tmp/output3';
//将数据导入到指定分区下
IMPORT TABLE employee_partitioned partition (year=2014, month=11) FROM '/tmp/output5';
INSERT:都可以
//INSERT支持OVERWRITE(覆盖)和INTO(追加)
INSERT OVERWRITE/INTO TABLE 表名 ...;
//将hive表中的数据导入HDFS的文件
insert overwrite directory '/路径'
row format delimited
fields terminated by ','
select * from 表名;
//将hive表中的数据导入本地磁盘文件
insert overwrite local directory '/路径'
row format delimited
fields terminated by ','
select * from 表名 ;
//Hive支持从同一个表进行多次插入
//INSERT INTO中TABLE关键字是可选的
//INSERT INTO可以指定插入到哪些字段中(如:INSERT INTO t(x,y,z))
//INSERT INTO table_name VALUES,支持插入值列表
//数据插入必须与指定列数相同
//INSERT不支持的写法
INSERT OVERWRITE TABLE test select 'hello';
//通过查询语句插入
insert into employee select * from ctas_employee;
//多插入(重点),高性能:只需扫描一次输入数据
from ctas_employee
insert overwrite table employee select *
insert overwrite table employee_internal select *;
//插入到分区(分区值可以在select后设定,该操作属于动态分区)
//典型的ETL模式
from ctas_patitioned
insert overwrite table employee PARTITION (year, month)
select *,'2018','09';
//通过指定列插入(insert into可以省略table关键字)
insert into employee(name) select 'John' from test limit 1;
//通过指定值插入
insert into employee(name) value('Judy'),('John');
CREATE TABLE wedw_tmp.lzc_test(
subject_id string COMMENT '课程id',
subject_name array<string> COMMENT '课程名称' #创建一个array集合类型的列
)
COMMENT'课程表'
row format delimited fields terminated by ','
collection items terminated by ':';
stored as textfile;
数学运算函数
#数学运算函数
select round(5.4); ## 5 四舍五入
select round(5.1345,3) ; ##5.135
select ceiling(5.4) ; ## 6 向上取整
select floor(5.4); ## 5 向下取整
select abs(-5.4) ; ## 5.4 绝对值
select greatest(id1,id2,id3) ; ## 6 单行函数
select least(3,5,6) ; ##求多个输入参数中的最小值
select max(age) from t_person group by ..; #分组聚合函数
select min(age) from t_person group by...; #分组聚合函数
字符串函数
substr(string str, int start) ## 截取子串
substring(string str, int start)
#示例:
select substr("abcdefg",2) ;
substr(string, int start, int len)
substring(string, int start, int len)
#示例:
select substr("abcdefg",2,3) ; ## bcd
concat(string A, string B...) ## 拼接字符串
concat_ws(string SEP, string A, string B...)
collect_set() #将某个字段在一组中的所有值形成一个集合(数组)返回
#示例:
select concat("ab","xy") ; ## abxy
select concat_ws(".","192","168","33","44") ; ## 192.168.33.44
length(string A)
#示例:
select length("192.168.33.44"); ## 13
split(string str, string pat) ## 切分字符串,返回数组
#示例:
select split("192.168.33.44",".") ; 错误的,因为.号是正则语法中的特定字符
select split("192.168.33.44","\\.") ;
upper(string str) ##转大写
lower(string str) ##转小写
时间函数
select current_timestamp(); ## 返回值类型:timestamp,获取当前的时间戳(详细时间信息)
select current_date; ## 返回值类型:date,获取当前的日期
#unix时间戳转字符串格式——from_unixtime
from_unixtime(bigint unixtime[, string format])
#示例:
select from_unixtime(unix_timestamp());
select from_unixtime(unix_timestamp(),"yyyy/MM/dd HH:mm:ss");
#字符串格式转unix时间戳——unix_timestamp:返回值是一个长整数类型
#如果不带参数,取当前时间的秒数时间戳long--(距离格林威治时间1970-1-1 0:0:0秒的差距)
select unix_timestamp();
unix_timestamp(string date, string pattern)
select unix_timestamp("2020-07-26 17:50:30");
select unix_timestamp("2020-07-26 17:50:30","yyyy-MM-dd HH:mm:ss");
#将字符串转成日期date
select to_date("2020-07-26 16:58:32");
#日期其他函数
#返回开始日期startdate增加days天后的日期date_sub (string startdate,int days) datediff(string enddate,string startdate) --日期差
date_add(string startdate, intdays)
#返回当前月份的第一天
trunc(string date,'MM')
select date_add('2020-06-27',2) as dt;
select date_sub('2015-05-14',7);
select datediff('2020-05-13','2020-05-02') as dd;
select trunc('2020-05-13','MM');
条件控制函数
IF语句
select id,if(age>25,'working','worked') from t_user;
CASE WHEN语句
#语法:
CASE [ expression ]
WHEN condition1 THEN result1
WHEN condition2 THEN result2
...
WHEN conditionn THEN resultn
ELSE result
END
COALESCE语句
SELECT COALESCE(field_name,'0') as value from table;
集合函数
array(3,5,8,9) #构造一个整数数组
array(‘hello’,’moto’,’semense’,’chuizi’,’xiaolajiao’) #构造一个字符串数组
array_contains(Array<T>, value) #返回boolean值
size(Map<K.V>) #返回一个imap的元素个数,int值
size(array<T>) #返回一个数组的长度,int值
map_keys(Map<K.V>) #返回一个map字段的所有key,结果类型为:数组
map_values(Map<K.V>) #返回一个map字段的所有value,结果类型为:数组
分组聚合函数
sum(字段) : 求这个字段在一个组中的所有值的和
avg(字段) :求这个字段在一个组中的所有值的平均值
max(字段) :求这个字段在一个组中的所有值的最大值
min(字段) :求这个字段在一个组中的所有值的最小值
CTE(Common Table Expression):多表查询
//CTE语法
//CTE适合用于多表查询以及子查询使得语句更加简洁明了
WITH
[临时表名] AS (SELECT …) //将查询结果命名为临时表名,并且查询语句中可以使用其他临时表
[临时表名] AS (SELECT …)
...
SELECT ... //最后汇总查询数据
正则匹配查询
//匹配正则表达式默认是关闭的需要手动开启
//开启命令:SET hive.support.quoted.identifiers = none;
//匹配表中的列名
SELECT `[正则表达式]` FROM [表名];
虚拟列(Virtual Columns)
//两个连续下划线,用于数据验证
INPUT__FILE__NAME//的输入文件名称
LOCK__OFFSET__INSIDE__FILE//当前全局文件位置
关联查询
//指对多表进行联合查询
//类似于SQL JOIN,但是Hive仅支持等值连接
INNER JOIN//内连接
OUTER JOIN//外连接 RIGHT JOIN, LEFT JOIN, FULL OUTER JOIN
CROSS JOIN//交叉连接
Implicit JOIN//隐式连接
MapJoin
//MapJoin:在Map端完成join操作
//通常用于小表关联大表、不等值连接
//若使用MapJoin需要配置自动将连接转换为MapJoin
set hive.auto.convert.join = true(默认值)
//MapJoin操作不支持以下操作
//不可在这些操作后面
UNION ALL, LATERAL VIEW, GROUP BY/JOIN/SORT BY/CLUSTER BY/DISTRIBUTE BY
//不可在这些操作之前
UNION, JOIN, MAPJOIN
Union与Union all
//所有子集数据必须具有相同的名称和类型
//UNION:合并后删除重复项(v1.2之后)
//UNION ALL:合并后保留重复项
//示例
select * from 表1
union all
select * from 表2
//order by、sort by、cluster by、distribute by和limit适用于合并后的整个结果
//集合其他操作可以使用join/outer join来实现(差集、交集)
COALESCE
//当expr1为空时使用expr2为结果值,若expr2也为空,使用expr3为结果值
//解释:遇到非null值即停止并返回该值。如果所有的表达式都是空值,最终将返回一个空值
select COALESCE(expr1, expr2, expr3)
ORDER BY:全局排序
//只使用一个Reducer执行全局数据排序
//速度慢,应提前做好数据过滤
//支持使用CASE WHEN或表达式
//查询字段需要包含排序字段,否则会报错
//示例
select * from 表名 order by 字段;
//支持按位置编号排序
//执行前需要操作以下命令
set hive.groupby.orderby.position.alias=true;
//语法格式
select * from offers order by case when offerid = 1 then 1 else 0 end;
select * from offers order by 1;
SORT BY:分区排序
//SORT BY对每个Reducer中的数据进行排序
//当Reducer数量设置为1时,等于ORDER BY
DISTRIBUTE BY:类似Group By
//确保具有匹配列值的行被分区到相同的Reducer
//不会对每个Reducer的输出进行排序
//通常使用在SORT BY语句之前
SELECT ID ,NAME,SOURCE FROM STUDENT DISTRIBUTE BY ID SORT BY SOURCE DESC;//默认ASC,正序DESC,倒序
CLUSTER BY
//CLUSTER BY = DISTRIBUTE BY + SORT BY
//不支持ASC|DESC
//排序列必须出现在SELECT column列表中
//为了充分利用所有的Reducer来执行全局排序,可以先使用CLUSTER BY,然后使用ORDER BY
//示例
SELECT name, source FROM student CLUSTER BY name;
GROUP BY:分组
//Hive基本内置聚合函数与GROUP BY一起使用
//如果没有指定GROUP BY子句,则默认聚合整个表
//除聚合函数外,所选的其他列也必须包含在GROUP BY中
//GROUP BY支持使用CASE WHEN或 表达式
//示例
select name, max(source) from student group by name;
//支持按位置编号分组
//执行前需要操作以下命令
set hive.groupby.orderby.position.alias=true;
select * from 表名 group by 1;
HAVING:数据过滤
//HAVING:对GROUP BY聚合结果的条件过滤
//可以避免在GROUP BY之后使用子查询
//HAVING之后可以使用表达式,但不建议
//示例
select name,source from student group by name having count(source) > 250;
面试题:having与where的区别?
having是在group by之后过滤数据,where是在group by之前过滤数据,而且where的效率比having更高,相对来说having更消耗资源,但where更占内存
Collect_set/list
//返回每个组列中的对象集/列表
//collect_set, collect_list
//示例
select collect_list(字段) from 表名;
示例数据(转换前)
1 a
1 b
1 c
2 d
2 e
示例数据(转换后)
1 [a,b,c]
2 [d,e]
语法
Function (arg1,..., arg n) OVER ([PARTITION BY <...>] [ORDER BY <....>] [<window_clause>])
//PARTITION BY类似于GROUP BY,未指定则按整个结果集
//只有指定ORDER BY子句之后才能进行窗口定义
//可同时使用多个窗口函数
//过滤窗口函数计算结果必须在外面一层
窗口函数 - 序列
ROW_NUMBER():对所有数值输出不同的序号,序号唯一连续(1,2,3)
RANK():对相同数值,输出相同的序号,下一个序号跳过(1,1,3)
DENSE_RANK():对相同数值,输出相同的序号,下一个序号连续(1,1,2)
NLITE(n):将有序的数据集合平均分配到n个桶中, 将桶号分配给每一行,根据桶号,选取前或后 n分之几的数据
PERCENT_RANK():(目前排名- 1)/(总行数- 1),值相对于一组值的百分比排名
//示例
SELECT name, dept_num, salary,
ROW_NUMBER() OVER () AS row_num,
RANK() OVER (PARTITION BY dept_num ORDER BY salary) AS rank,
DENSE_RANK() OVER (PARTITION BY dept_num ORDER BY salary) AS dense_rank,
PERCENT_RANK() OVER(PARTITION BY dept_num ORDER BY salary) AS percent_rank,
NTILE(2) OVER(PARTITION BY dept_num ORDER BY salary) AS ntile
FROM employee_contract ORDER BY dept_num, salary;
窗口函数 - 聚合
COUNT():计数,可以和DISTINCT一起用
SUM():求和
AVG():平均值
MAX():最大
MIN():小值
//从Hive 2.1.0开始在OVER子句中支持聚合函数
//示例
SELECT ename, deptno, sal,
COUNT(*) OVER (PARTITION BY deptno) AS row_cnt,
SUM(sal) OVER(PARTITION BY deptno ORDER BY deptno) AS deptTotal,
AVG(sal) OVER(PARTITION BY deptno) AS avgDept,
MIN(sal) OVER(PARTITION BY deptno) AS minDept,
MAX(sal) OVER(PARTITION BY deptno) AS maxDept
FROM emp ORDER BY deptno, ename;
窗口函数 - 分析
CUME_DIST:小于等于当前值的行数/分组内总行数
LEAD/LAG(col,n):某一列进行往前/后第n行值(n可选,默认为1)
FIRST_VALUE:当前列的第一个值
LAST_VALUE:到目前行为止的最后一个值
//示例
SELECT name, dept_num, salary,
LEAD(salary, 2) OVER() AS lead,
LAG(salary, 2, 0) OVER() AS lag,
FIRST_VALUE(salary) OVER () AS first_value,
LAST_VALUE(salary) OVER () AS last_value_default,
FROM employee_contract ORDER BY dept_num, salary;
窗口函数 - 窗口字句
//支持两类窗口子句:行类型窗口、范围类型窗口
//不支持与RANK、NTILE、DENSE_RANK、CUME_DIST、PERCENT_RANK、LEAD、LAG和ROW_NUMBER函数一起使用
//------------------------行类型窗口-------------------------------------
//行窗口:根据当前行之前或之后的行号确定的窗口:ROWS BETWEEN AND
//可以为下列值
//UNBOUNDED PRECEDING : 窗口起始位置(分组第一行)
//CURRENT ROW:当前行
//NPRECEDING/FOLLOWING:当前行之前/之后n行
//可以为下列值
//UNBOUNDED FOLLOWING : 窗口结束位置(分组最后一行)
//CURRENT ROW:当前行
//NPRECEDING/FOLLOWING:当前行之前/之后n行
//示例
SELECT
name, dept_num AS dept, salary AS sal,
MAX(salary) OVER (PARTITION BY dept_num ORDER BY name ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) win1,
MAX(salary) OVER (PARTITION BY dept_num ORDER BY name ROWS BETWEEN 2 PRECEDING AND UNBOUNDED FOLLOWING) win2,
MAX(salary) OVER (PARTITION BY dept_num ORDER BY name ROWS BETWEEN 1 PRECEDING AND 2 FOLLOWING) win3,
MAX(salary) OVER (PARTITION BY dept_num ORDER BY name ROWS BETWEEN 2 PRECEDING AND 1 PRECEDING) win4,
MAX(salary) OVER (PARTITION BY dept_num ORDER BY name ROWS BETWEEN 1 FOLLOWING AND 2 FOLLOWING) win5,
MAX(salary) OVER (PARTITION BY dept_num ORDER BY name ROWS BETWEEN CURRENT ROW AND CURRENT ROW) win6,
MAX(salary) OVER (PARTITION BY dept_num ORDER BY name ROWS BETWEEN CURRENT ROW AND 1 FOLLOWING) win7,
MAX(salary) OVER (PARTITION BY dept_num ORDER BY name ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) win8,
MAX(salary) OVER (PARTITION BY dept_num ORDER BY name ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) win9,
MAX(salary) OVER (PARTITION BY dept_num ORDER BY name ROWS BETWEEN UNBOUNDED PRECEDING AND 1 FOLLOWING) win10,
MAX(salary) OVER (PARTITION BY dept_num ORDER BY name ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) win11,
MAX(salary) OVER (PARTITION BY dept_num ORDER BY name ROWS 2 PRECEDING) win12
FROM employee_contract ORDER BY dept, name;
//------------------------范围类型窗口--------------------------------
//范围窗口是取分组内的值在指定范围区间内的行
//该范围值/区间必须是数字或日期类型
//目前只支持一个ORDER BY列
//示例
SELECT name, dept_num AS dept, salary AS sal,
MAX(salary) OVER (PARTITION BY dept_num ORDER BY name ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) win1,
salary - 1000 as sal_r_start,
salary as sal_r_end,
MAX(salary) OVER (PARTITION BY dept_num ORDER BY name RANGE BETWEEN 1000 PRECEDING AND CURRENT ROW) win13
FROM employee_contract ORDER BY dept, name;
学习测验
解析:这个问题需要套用多层查询,核心是使用窗户函数dense_rank(),对分组查询过的结果进行排序,取最高位就是用户最常用/喜欢的xx数据
--这里的示例是查询出用户最常用的IP
select user_id,visit_ip
from
(select user_id,visit_ip,dense_rank() over(partition by user_id order by a.ip_cnt desc) rn
from
(select user_id,visit_ip,count(visit_ip) ip_cnt from snbap_dw.user_pc_click_partition
group by user_id,visit_ip) a
) b
where rn=1;
为什么要自定义函数
UDF,UDAF,UDTF的区别
UDF:返回对应值,一对一
UDAF:返回聚类值,多对一
UDTF:返回拆分值,一对多
流程步骤
示例:自定义UDF
<dependency>
<groupId>org.apache.hadoopgroupId>
<artifactId>hadoop-commonartifactId>
<version>2.6.0version>
dependency>
<dependency>
<groupId>org.apache.hivegroupId>
<artifactId>hive-execartifactId>
<version>1.1.0version>
dependency>
<dependency>
<groupId>org.apache.hivegroupId>
<artifactId>hive-jdbcartifactId>
<version>1.1.0version>
dependency>
<dependency>
<groupId>org.apache.hadoopgroupId>
<artifactId>hadoop-hdfsartifactId>
<version>2.6.0version>
dependency>
<dependency>
<groupId>log4jgroupId>
<artifactId>log4jartifactId>
<version>1.2.17version>
dependency>
dependencies>
<build>
<finalName>udf_demofinalName>
<plugins>
<plugin>
<artifactId>maven-assembly-pluginartifactId>
<configuration>
<archive>
<manifest>
<mainClass>mainClass>
manifest>
archive>
<descriptorRefs>
<descriptorRef>jar-with-dependenciesdescriptorRef>
descriptorRefs>
configuration>
plugin>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-compiler-pluginartifactId>
<configuration>
<source>7source>
<target>7target>
configuration>
plugin>
plugins>
build>
project>
import org.apache.commons.lang.StringUtils;
import org.apache.hadoop.hive.ql.exec.UDF;
import sun.misc.BASE64Encoder;
import java.io.UnsupportedEncodingException;
/**
* Created by Liuzuochang on 2020/10/07.
* base64加密UDF
* */
public class Base64Encrypt extends UDF {
public String evaluate(String msg) throws Exception {
//判断传进来的参数是否为空
if(StringUtils.isBlank(msg)){
return "";
}
//base64加密
byte[] bt = null;
String newMsg = null;
try {
bt = msg.getBytes("utf-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
if( bt != null){
newMsg = new BASE64Encoder().encode(bt);
}
if(newMsg.contains("\r\n")){
newMsg = newMsg.replace("\r\n","");
}else if(newMsg.contains("\r")){
newMsg = newMsg.replace("\r","");
}else if(newMsg.contains("\n")){
newMsg = newMsg.replace("\n","");
}
return newMsg;
}
}
import org.apache.commons.lang.StringUtils;
import org.apache.hadoop.hive.ql.exec.UDF;
import sun.misc.BASE64Decoder;
/**
* Created by Liuzuochang on 2020/10/07.
* base64解密UDF
*/
public class Base64Decrypt extends UDF {
public String evaluate(String msg) throws Exception {
//判断传进来的参数是否为空
if(StringUtils.isBlank(msg)){
return "";
}
//base64解密
byte[] bt = null;
String result = null;
if(msg != null){
BASE64Decoder decoder = new BASE64Decoder();
try {
bt = decoder.decodeBuffer(msg);
result = new String(bt, "utf-8");
} catch (Exception e) {
e.printStackTrace();
}
}
return result;
}
}
使用创建函数可能会出现一些奇怪报错
建议创建前先执行下面操作:
yum install -y zip
zip -d [jar包名].jar ‘META-INF/.SF’ ‘META-INF/*SF’
hive> add jar /Jar包路径/udf_demo.jar;
hive> create temporary function test_en as 'com.wedoctor.Base64Encrypt';
hive> create temporary function test_de as 'com.wedoctor.Base64Decrypt';
#测试加密UDF
hive> select test_en('1234');
#输出
MTIzNA==
#测试解密UDF
hive> select test_de('MTIzNA==');
#输出
1234
创建永久函数
hdfs dfs -mkdir -p /apps/hive/functions
hdfs dfs -put [/路径/包名.jar] /apps/hive/functions/
create function [函数名] as ['实现类完整的包路径'] using jar ['hdfs://IP地址:9000/JAR包在HDFS上的路径'];
永久函数删除的方法:drop function [函数名];
毫不夸张的说,有没有掌握hive调优,是判断一个数据工程师是否合格的重要指标
hive调优涉及到压缩和存储调优,参数调优,sql的调优,数据倾斜调优,小文件问题的调优等
在 HiveSQL 的 create table 语句中,可以使用 stored as … 指定表的存储格式。Apache Hive支持 Apache Hadoop 中使用的几种熟悉的文件格式,比如 TextFile、SequenceFile、RCFile、Avro、ORC、ParquetFile等。存储格式一般需要根据业务进行选择,在我们的实操中,绝大多数表都采用TextFile与Parquet两种存储格式之一。TextFile是最简单的存储格式,它是纯文本记录,也是Hive的默认格式。虽然它的磁盘开销比较大,查询效率也低,但它更多地是作为跳板来使用。RCFile、ORC、Parquet等格式的表都不能由文件直接导入数据,必须由TextFile来做中转。Parquet和ORC都是Apache旗下的开源列式存储格式。列式存储比起传统的行式存储更适合批量OLAP查询,并且也支持更好的压缩和编码。创建表时,特别是宽表,尽量使用 ORC、ParquetFile 这些列式存储格式,因为列式存储的表,每一列的数据在物理上是存储在一起的,Hive查询时会只遍历需要列数据,大大减少处理的数据量。
TextFile
Sequence File
RC File
ORC File
Parquet File
推荐:orcfile、parquet
Hive 语句最终是转化为 MapReduce 程序来执行的,而 MapReduce 的性能瓶颈在与 网络IO 和 磁盘IO,要解决性能瓶颈,最主要的是 减少数据量,对数据进行压缩是个好方式。压缩虽然是减少了数据量,但是压缩过程要消耗 CPU,但是在 Hadoop 中,往往性能瓶颈不在于 CPU,CPU 压力并不大,所以压缩充分利用了比较空闲的 CPU。
如何选择压缩方式呢?
1、压缩比例
2、解压缩速度
3、是否支持split
支持切割的文件可以并行的有多个mapper程序处理大数据文件,一般我们选择的都是支持切分的!
压缩的优/缺点
计算密集型,不压缩,否则会进一步增加cpu的负担,真实的场景中hive对cpu的压力很小
网络密集型,推荐压缩,减小网络数据传输
配置
set hive.exec.compress.intermediate=true #默认false,设置为True开启压缩
set mapred.map.output.compression.codec= org.apache.hadoop.io.compress.SnappyCodec #指定解码器,优先选择一个低CPU开销的算法,这里推荐SnappyCodec
#不同的压缩对应的解码器也不同
#Lzo->com.hadoop.compression.lzo.LzoCodec
#Gzip->org.apache.hadoop.io.compress.Gzipcodec
#Bzip2->org.apache.hadoop.io.compress.BZip2codec
#Lz4->org.apache.hadoop.io.compress.Lz4codec
#Zlib->org.apache.hadoop.io.compress.Defaultcodec
set hive.exec.compress.output=true #默认值是false,设置为True开启压缩
set mapred.output.compression.codec=org.apache.hadoop.io.compress.SnappyCodec #指定解码器
# 当然,也可以在hive建表时指定表的存储格式和压缩格式
演示示例
[hadoop@hadoop004 data]$ hdfs dfs -put page_views.dat /data/click/
[hadoop@hadoop004 data]$ hdfs dfs -ls /data/click/
Found 1 items
-rw-r--r-- 1 hadoop supergroup 19014993 2019-04-19 12:26 /data/click/page_views.dat
[hadoop@hadoop004 data]$ hdfs dfs -du -s -h /data/click/page_views.dat
18.1 M 18.1 M /data/click/page_views.dat
hive> set hive.exec.compress.output=true
hive> set mapred.map.output.compression.codec= org.apache.hadoop.io.compress.SnappyCodec
hive> create table page_views(
> track_times string,
> url string,
> session_id string,
> referer string,
> ip string,
> end_user_id string,
> city_id string
> ) row format delimited fields terminated by '\t';
OK
Time taken: 0.741 seconds
hive> load data local inpath '/home/hadoop/data/page_views.dat' overwrite into table page_views;
Loading data to table default.page_views
Table default.page_views stats: [numFiles=1, numRows=0, totalSize=19014993, rawDataSize=0]
OK
Time taken: 0.595 seconds
[hadoop@hadoop004 data]$ hdfs dfs -du -s -h /user/hive/warehouse/page_views/page_views.dat
18.1 M 18.1 M /user/hive/warehouse/page_views/page_views.dat
hive> create table page_views_snappy as select * from page_views;
Query ID = hadoop_20190419141818_561ede29-e964-4655-9e48-1e4f5d6eeb5c
Total jobs = 3
Launching Job 1 out of 3
Number of reduce tasks is set to 0 since there's no reduce operator
Starting Job = job_1555643336639_0002, Tracking URL = http://hadoop004:8088/proxy/application_1555643336639_0002/
Kill Command = /home/hadoop/app/hadoop-2.6.0-cdh5.7.0/bin/hadoop job -kill job_1555643336639_0002
Hadoop job information for Stage-1: number of mappers: 1; number of reducers: 0
2019-04-19 14:30:50,994 Stage-1 map = 0%, reduce = 0%
2019-04-19 14:30:57,450 Stage-1 map = 100%, reduce = 0%, Cumulative CPU 2.7 sec
MapReduce Total cumulative CPU time: 2 seconds 700 msec
Ended Job = job_1555643336639_0002
Stage-4 is selected by condition resolver.
Stage-3 is filtered out by condition resolver.
Stage-5 is filtered out by condition resolver.
Moving data to: hdfs://hadoop004:9000/user/hive/warehouse/.hive-staging_hive_2019-04-19_14-30-44_413_8511625915872870114-1/-ext-10001
Moving data to: hdfs://hadoop004:9000/user/hive/warehouse/page_views_snappy
Table default.page_views_snappy stats: [numFiles=1, numRows=100000, totalSize=8814444, rawDataSize=18914993]
MapReduce Jobs Launched:
Stage-Stage-1: Map: 1 Cumulative CPU: 2.7 sec HDFS Read: 19018292 HDFS Write: 8814535 SUCCESS
Total MapReduce CPU Time Spent: 2 seconds 700 msec
OK
[hadoop@hadoop004 data]$ hdfs dfs -ls /user/hive/warehouse/page_views_snappy
Found 1 items
-rwxr-xr-x 1 hadoop supergroup 8814444 2019-04-19 14:30 /user/hive/warehouse/page_views_snappy/000000_0.snappy
[hadoop@hadoop004 data]$ hdfs dfs -du -s -h /user/hive/warehouse/page_views_snappy
8.4 M 8.4 M /user/hive/warehouse/page_views_snappy
1、当你意识到一个字段经常用来做where,就可以建分区表,使用这个字段当做分区字段
2、在查询的时候,使用分区字段来过滤,就可以避免全表扫描。只需要扫描这张表的一个分区的数据即可
#让可以不走mapreduce任务的,就不走mapreduce任务
hive> set hive.fetch.task.conversion=more;
#开启任务并行执行
set hive.exec.parallel=true;
#解释:当一个sql中有多个job时候,且这多个job之间没有依赖,则可以让顺序执行变为并行执行(一般为用到union all的时候)
#同一个sql允许并行任务的最大线程数
set hive.exec.parallel.thread.number=8;
#设置jvm重用
#JVM重用对hive的性能具有非常大的 影响,特别是对于很难避免小文件的场景或者task特别多的场景,这类场景大多数执行时间都很短。jvm的启动过程可能会造成相当大的开销,尤其是执行的job包含有成千上万个task任务的情况。
set mapred.job.reuse.jvm.num.tasks=10;
#合理设置reduce的数目
#方法1:调整每个reduce所接受的数据量大小
set hive.exec.reducers.bytes.per.reducer=500000000; (500M)
#方法2:直接设置reduce数量
set mapred.reduce.tasks = 20
# map端聚合,降低传给reduce的数据量
set hive.map.aggr=true
#开启hive内置的数倾优化机制
set hive.groupby.skewindata=true
where条件优化
select m.cid,u.id from order m join customer u on( m.cid =u.id )where m.dt='20180808';
select m.cid,u.id from (select * from order where dt='20180818') m join customer u on( m.cid =u.id);
union优化
count distinct优化
select count(1) from (select id from tablename group by id) tmp;
用in 来代替join
select id,name from tb1 a join tb2 b on(a.id = b.id);
select id,name from tb1 where id in(select id from tb2);
优化子查询
join 优化
Common/shuffle/Reduce JOIN 连接发生的阶段,发生在reduce 阶段, 适用于大表 连接 大表(默认的方式)
Map join :连接发生在map阶段 ,适用于小表连接大表,大表的数据从文件中读取,小表的数据存放在内存中(hive中已经自动进行了优化,自动判断小表,然后进行缓存)
set hive.auto.convert.join=true;
set hive.auto.convert.sortmerge.join=true;
set hive.optimize.bucketmapjoin = true;
set hive.optimize.bucketmapjoin.sortedmerge = true;
set hive.auto.convert.sortmerge.join.noconditionaltask=true;
表现:任务进度长时间维持在99%(或100%),查看任务监控页面,发现只有少量(1个或几个)reduce子任务未完成。因为其处理的数据量和其他reduce差异过大。
原因:某个reduce的数据输入量远远大于其他reduce数据的输入量
sql导致的倾斜
如果是在group by中产生了数据倾斜,是否可以讲group by的维度变得更细,如果没法变得更细,就可以在原分组key上添加随机数后分组聚合一次,然后对结果去掉随机数后再分组聚合
在join时,有大量为null的join key,则可以将null转成随机值,避免聚集
情形:某特殊值过多
后果:处理此特殊值的 reduce 耗时;只有一个 reduce 任务
解决方式:count distinct 时,将值为空的情况单独处理,比如可以直接过滤空值的行,
在最后结果中加 1。如果还有其他计算,需要进行 group by,可以先将值为空的记录单独处理,再和其他计算结果进行 union。
情形:比如用户表中 user_id 字段为 int,log 表中 user_id 字段既有 string 类型也有 int 类型。当按照 user_id 进行两个表的 Join 操作时。
后果:处理此特殊值的 reduce 耗时;只有一个 reduce 任务
默认的 Hash 操作会按 int 型的 id 来进行分配,这样会导致所有 string 类型 id 的记录都分配
到一个 Reducer 中。
解决方式:把数字类型转换成字符串类型
select * from users a
left outer join logs b
on a.usr_id = cast(b.user_id as string)
业务数据本身的特性(存在热点key)
key分布不均
可以在key上加随机数,或者增加reduceTask数量
开启数据倾斜时负载均衡
set hive.groupby.skewindata=true;
思想:就是先随机分发并处理,再按照 key group by 来分发处理。
操作:当选项设定为 true,生成的查询计划会有两个 MRJob。
第一个 MRJob 中,Map 的输出结果集合会随机分布到 Reduce 中,每个 Reduce 做部分聚合操作,并输出结果,这样处理的结果是相同的 GroupBy Key 有可能被分发到不同的Reduce 中,从而达到负载均衡的目的;
第二个 MRJob 再根据预处理的数据结果按照 GroupBy Key 分布到 Reduce 中(这个过程可以保证相同的原始 GroupBy Key 被分布到同一个 Reduce 中),最后完成最终的聚合操作。
控制空值分布
注:对于异常值如果不需要的话,最好是提前在 where 条件里过滤掉,这样可以使计算量大大减少
小文件的产生有三个地方,map输入,map输出,reduce输出,小文件过多也会影响hive的分析效率:
set mapred.max.split.size=256000000;
#一个节点上split的至少的大小(这个值决定了多个DataNode上的文件是否需要合并)
set mapred.min.split.size.per.node=100000000;
#一个交换机下split的至少的大小(这个值决定了多个交换机上的文件是否需要合并)
set mapred.min.split.size.per.rack=100000000;
#执行Map前进行小文件合并
set hive.input.format=org.apache.hadoop.hive.ql.io.CombineHiveInputFormat;
#设置map端输出进行合并,默认为true
set hive.merge.mapfiles = true
#设置reduce端输出进行合并,默认为false
set hive.merge.mapredfiles = true
#设置合并文件的大小
set hive.merge.size.per.task = 256*1000*1000
#当输出文件的平均大小小于该值时,启动一个独立的MapReduce任务进行文件merge。
set hive.merge.smallfiles.avgsize=16000000
#学会查看sql的执行计划,优化业务逻辑 ,减少job的数据量。对调优也非常重要
explain sql
mkdir -p /tmp/hivedemo/data
hive -f /tmp/hivedemo/data/setup_tables.sql
#进入demo库
use demo;
#查看里面的表
show tables;
+-----------------------+--+
| tab_name |
+-----------------------+--+
| emp_basic |
| emp_bef |
| emp_psn |
| employee |
| employee_contract |
| employee_external |
| employee_hr |
| employee_id |
| employee_id_buckets |
| employee_partitioned |
| shopping |
+-----------------------+--+
select name,dept_num,salary from employee_contract order by salary desc;
--输出
+----------+-----------+---------+--+
| name | dept_num | salary |
+----------+-----------+---------+--+
| Richard | 1002 | 8000 |
| Wei | 1002 | 7000 |
| Mike | 1001 | 6400 |
| Steven | 1000 | 6400 |
| Jess | 1001 | 6000 |
| Yun | 1002 | 5500 |
| Lucy | 1000 | 5500 |
| Lily | 1001 | 5000 |
| Michael | 1000 | 5000 |
| Wendy | 1000 | 4000 |
| Will | 1000 | 4000 |
+----------+-----------+---------+--+
--设置允许通过下标来确定操作的字段
--开启
set hive.groupby.orderby.position.alias=true
--order by第二个字段做为排序字段,即dept_num(下标为1)
select name,dept_num,salary from employee_contract order by 1 desc;
--输出
+----------+-----------+---------+--+
| name | dept_num | salary |
+----------+-----------+---------+--+
| Richard | 1002 | 8000 |
| Yun | 1002 | 5500 |
| Wei | 1002 | 7000 |
| Mike | 1001 | 6400 |
| Jess | 1001 | 6000 |
| Lily | 1001 | 5000 |
| Lucy | 1000 | 5500 |
| Steven | 1000 | 6400 |
| Wendy | 1000 | 4000 |
| Will | 1000 | 4000 |
| Michael | 1000 | 5000 |
+----------+-----------+---------+--+
--设置reduce数
set mapreduce.job.reduces=3;
select name,dept_num,salary from employee_contract sort by salary desc;
--输出
+----------+-----------+---------+--+
| name | dept_num | salary |
+----------+-----------+---------+--+
| Wei | 1002 | 7000 |
| Mike | 1001 | 6400 |
| Jess | 1001 | 6000 |
| Yun | 1002 | 5500 |
| Lucy | 1000 | 5500 |
| Lily | 1001 | 5000 |
| Richard | 1002 | 8000 |
| Steven | 1000 | 6400 |
| Wendy | 1000 | 4000 |
| Will | 1000 | 4000 |
| Michael | 1000 | 5000 |
+----------+-----------+---------+--+
--distribute by相当于group by
select name,dept_num,salary from employee_contract distribute by dept_num sort by salary;
--输出
+----------+-----------+---------+--+
| name | dept_num | salary |
+----------+-----------+---------+--+
| Yun | 1002 | 5500 |
| Wei | 1002 | 7000 |
| Richard | 1002 | 8000 |
| Wendy | 1000 | 4000 |
| Will | 1000 | 4000 |
| Michael | 1000 | 5000 |
| Lucy | 1000 | 5500 |
| Steven | 1000 | 6400 |
| Lily | 1001 | 5000 |
| Jess | 1001 | 6000 |
| Mike | 1001 | 6400 |
+----------+-----------+---------+--+
select name,dept_num,salary from employee_contract cluster by dept_num;
#输出
+----------+-----------+---------+--+
| name | dept_num | salary |
+----------+-----------+---------+--+
| Richard | 1002 | 8000 |
| Yun | 1002 | 5500 |
| Wei | 1002 | 7000 |
| Lucy | 1000 | 5500 |
| Steven | 1000 | 6400 |
| Wendy | 1000 | 4000 |
| Will | 1000 | 4000 |
| Michael | 1000 | 5000 |
| Mike | 1001 | 6400 |
| Jess | 1001 | 6000 |
| Lily | 1001 | 5000 |
+----------+-----------+---------+--+
--if:如果工资大于5000则为good,否则为bad,然后根据这个判断进行分区求和
select if(salary>5000,'good','bad') as a,sum(salary) from employee_contract group by if(salary>5000,'good','bad')
--输出
+-------+--------+--+
| a | _c1 |
+-------+--------+--+
| bad | 18000 |
| good | 44800 |
+-------+--------+--+
窗口函数
--row_number
select name,dept_num,salary, row_number() over(order by salary) as rn from employee_contract;
--输出
+----------+-----------+---------+-----+--+
| name | dept_num | salary | rn |
+----------+-----------+---------+-----+--+
| Wendy | 1000 | 4000 | 1 |
| Will | 1000 | 4000 | 2 |
| Lily | 1001 | 5000 | 3 |
| Michael | 1000 | 5000 | 4 |
| Yun | 1002 | 5500 | 5 |
| Lucy | 1000 | 5500 | 6 |
| Jess | 1001 | 6000 | 7 |
| Mike | 1001 | 6400 | 8 |
| Steven | 1000 | 6400 | 9 |
| Wei | 1002 | 7000 | 10 |
| Richard | 1002 | 8000 | 11 |
+----------+-----------+---------+-----+--+
--rank
select name,dept_num,salary,rank() over(order by salary) as rn from employee_contract;
--输出
+----------+-----------+---------+-----+--+
| name | dept_num | salary | rn |
+----------+-----------+---------+-----+--+
| Wendy | 1000 | 4000 | 1 |
| Will | 1000 | 4000 | 1 |
| Lily | 1001 | 5000 | 3 |
| Michael | 1000 | 5000 | 3 |
| Yun | 1002 | 5500 | 5 |
| Lucy | 1000 | 5500 | 5 |
| Jess | 1001 | 6000 | 7 |
| Mike | 1001 | 6400 | 8 |
| Steven | 1000 | 6400 | 8 |
| Wei | 1002 | 7000 | 10 |
| Richard | 1002 | 8000 | 11 |
+----------+-----------+---------+-----+--+
--dense_rank()
select name,dept_num,salary,dense_rank() over(order by salary) as rn from employee_contract;
--输出
+----------+-----------+---------+-----+--+
| name | dept_num | salary | rn |
+----------+-----------+---------+-----+--+
| Wendy | 1000 | 4000 | 1 |
| Will | 1000 | 4000 | 1 |
| Lily | 1001 | 5000 | 2 |
| Michael | 1000 | 5000 | 2 |
| Yun | 1002 | 5500 | 3 |
| Lucy | 1000 | 5500 | 3 |
| Jess | 1001 | 6000 | 4 |
| Mike | 1001 | 6400 | 5 |
| Steven | 1000 | 6400 | 5 |
| Wei | 1002 | 7000 | 6 |
| Richard | 1002 | 8000 | 7 |
+----------+-----------+---------+-----+--+
select name,dept_num,salary,
avg(salary) over(partition by dept_num) as avg,
max(salary) over(partition by dept_num) as max,
min(salary) over(partition by dept_num) as min
from employee_contract;
--输出
+----------+-----------+---------+--------------------+-------+-------+--+
| name | dept_num | salary | avg | max | min |
+----------+-----------+---------+--------------------+-------+-------+--+
| Richard | 1002 | 8000 | 6833.333333333333 | 8000 | 5500 |
| Yun | 1002 | 5500 | 6833.333333333333 | 8000 | 5500 |
| Wei | 1002 | 7000 | 6833.333333333333 | 8000 | 5500 |
| Lucy | 1000 | 5500 | 4980.0 | 6400 | 4000 |
| Steven | 1000 | 6400 | 4980.0 | 6400 | 4000 |
| Wendy | 1000 | 4000 | 4980.0 | 6400 | 4000 |
| Will | 1000 | 4000 | 4980.0 | 6400 | 4000 |
| Michael | 1000 | 5000 | 4980.0 | 6400 | 4000 |
| Mike | 1001 | 6400 | 5800.0 | 6400 | 5000 |
| Jess | 1001 | 6000 | 5800.0 | 6400 | 5000 |
| Lily | 1001 | 5000 | 5800.0 | 6400 | 5000 |
+----------+-----------+---------+--------------------+-------+-------+--+
--lead(若仅配置一位参数,取不到则为NULL)
select name,dept_num,salary,lead(salary,1) over(partition by dept_num order by salary) as lead from employee_contract;
--输出
+----------+-----------+---------+-------+--+
| name | dept_num | salary | lead |
+----------+-----------+---------+-------+--+
| Yun | 1002 | 5500 | 7000 |
| Wei | 1002 | 7000 | 8000 |
| Richard | 1002 | 8000 | NULL |
| Wendy | 1000 | 4000 | 4000 |
| Will | 1000 | 4000 | 5000 |
| Michael | 1000 | 5000 | 5500 |
| Lucy | 1000 | 5500 | 6400 |
| Steven | 1000 | 6400 | NULL |
| Lily | 1001 | 5000 | 6000 |
| Jess | 1001 | 6000 | 6400 |
| Mike | 1001 | 6400 | NULL |
+----------+-----------+---------+-------+--+
--lead(配第二位参数,取不到则为第二位配置的参数)
select name,dept_num,salary,lead(salary,1,0) over(partition by dept_num order by salary) as lead from employee_contract;
--输出
+----------+-----------+---------+-------+--+
| name | dept_num | salary | lead |
+----------+-----------+---------+-------+--+
| Yun | 1002 | 5500 | 7000 |
| Wei | 1002 | 7000 | 8000 |
| Richard | 1002 | 8000 | 0 |
| Wendy | 1000 | 4000 | 4000 |
| Will | 1000 | 4000 | 5000 |
| Michael | 1000 | 5000 | 5500 |
| Lucy | 1000 | 5500 | 6400 |
| Steven | 1000 | 6400 | 0 |
| Lily | 1001 | 5000 | 6000 |
| Jess | 1001 | 6000 | 6400 |
| Mike | 1001 | 6400 | 0 |
+----------+-----------+---------+-------+--+
--lag(若仅配置一位参数,取不到则为NULL)
select name,dept_num,salary,lag(salary,1) over(partition by dept_num order by salary) as lead from employee_contract;
--输出
+----------+-----------+---------+-------+--+
| name | dept_num | salary | lead |
+----------+-----------+---------+-------+--+
| Yun | 1002 | 5500 | NULL |
| Wei | 1002 | 7000 | 5500 |
| Richard | 1002 | 8000 | 7000 |
| Wendy | 1000 | 4000 | NULL |
| Will | 1000 | 4000 | 4000 |
| Michael | 1000 | 5000 | 4000 |
| Lucy | 1000 | 5500 | 5000 |
| Steven | 1000 | 6400 | 5500 |
| Lily | 1001 | 5000 | NULL |
| Jess | 1001 | 6000 | 5000 |
| Mike | 1001 | 6400 | 6000 |
+----------+-----------+---------+-------+--+
--lag(若配置第二位参数,取不到则为第二位配置的参数)
select name,dept_num,salary,lag(salary,1,0) over(partition by dept_num order by salary) as lead from employee_contract;
--输出
+----------+-----------+---------+-------+--+
| name | dept_num | salary | lead |
+----------+-----------+---------+-------+--+
| Wendy | 1000 | 4000 | 0 |
| Will | 1000 | 4000 | 4000 |
| Michael | 1000 | 5000 | 4000 |
| Lucy | 1000 | 5500 | 5000 |
| Steven | 1000 | 6400 | 5500 |
| Lily | 1001 | 5000 | 0 |
| Jess | 1001 | 6000 | 5000 |
| Mike | 1001 | 6400 | 6000 |
| Yun | 1002 | 5500 | 0 |
| Wei | 1002 | 7000 | 5500 |
| Richard | 1002 | 8000 | 7000 |
+----------+-----------+---------+-------+--+
--first_value :取当前列的第一个
--last_value:取当前列的最后一个(相对于每个数据本身来说)
select name,dept_num,salary,
first_value(salary) over(partition by dept_num order by salary) as fv,
last_value(salary) over(partition by dept_num order by salary) as lv
from employee_contract;
--输出
+----------+-----------+---------+-------+-------+--+
| name | dept_num | salary | fv | lv |
+----------+-----------+---------+-------+-------+--+
| Wendy | 1000 | 4000 | 4000 | 4000 |
| Will | 1000 | 4000 | 4000 | 4000 |
| Michael | 1000 | 5000 | 4000 | 5000 |
| Lucy | 1000 | 5500 | 4000 | 5500 |
| Steven | 1000 | 6400 | 4000 | 6400 |
| Lily | 1001 | 5000 | 5000 | 5000 |
| Jess | 1001 | 6000 | 5000 | 6000 |
| Mike | 1001 | 6400 | 5000 | 6400 |
| Yun | 1002 | 5500 | 5500 | 5500 |
| Wei | 1002 | 7000 | 5500 | 7000 |
| Richard | 1002 | 8000 | 5500 | 8000 |
+----------+-----------+---------+-------+-------+--+
--得到每个数据小于等于自身的占比
select name,dept_num,salary,
cume_dist() over(partition by dept_num order by salary) as cd
from employee_contract;
--输出
+----------+-----------+---------+---------------------+--+
| name | dept_num | salary | cd |
+----------+-----------+---------+---------------------+--+
| Wendy | 1000 | 4000 | 0.4 |
| Will | 1000 | 4000 | 0.4 |
| Michael | 1000 | 5000 | 0.6 |
| Lucy | 1000 | 5500 | 0.8 |
| Steven | 1000 | 6400 | 1.0 |
| Lily | 1001 | 5000 | 0.3333333333333333 |
| Jess | 1001 | 6000 | 0.6666666666666666 |
| Mike | 1001 | 6400 | 1.0 |
| Yun | 1002 | 5500 | 0.3333333333333333 |
| Wei | 1002 | 7000 | 0.6666666666666666 |
| Richard | 1002 | 8000 | 1.0 |
+----------+-----------+---------+---------------------+--+
将HBase作为Hive数据源
将Hive ETL数据存入HBase
构建低延时的数据仓库
集成原理
操作命令
-- 在hive中创建外部表
-- 字段个数需要与获取的数据列名个数一致
create external table customer(
order_id string,
order_name string,
order_numb string,
order_date string,
addr_city string,
addr_state string)
--实现命令标准格式
stored by 'org.apache.hadoop.hive.hbase.HBaseStorageHandler'
with serdeproperties (
-- key是主键对应第一个字段(order_id),key后面的代表需要获取hbase表中的对应列簇与列名的数据,与上面设置的字段一一对应
"hbase.columns.mapping"=":key,order:name,order:numb,order:date,addr:city,addr:state")
--指定获取customer表中的数据
tblproperties("hbase.table.name" = "customer");
-- Hive中只支持select和insert,不支持HBase中的版本控制
select * from customer;
select count(*) from customer where order_numb is not null;
--插入数据
insert into table customer values ('1','James','1121','2018-05-31','toronto','ON');
#Hive文件存储格式
#TEXTFILE
#SEQUENCEFILE
#RCFILE
#ORCFILE
create table 表名 stored as 文件格式 AS select * from HBase映射的外部表名;
scan 'customer','1' --查询rowkey=1的数据