Hive介绍、架构、实践以及调优

目录

  • 一、什么是Hive
    • 1.1 介绍
    • 1.2 特点
  • 二、架构
    • 2.1 架构图
    • 2.2用户接口:Client
    • 2.3 元数据:Metastore
    • 2.4 驱动器:Driver
  • 三、安装配置
  • 四、Hive连接方式
    • 交互模式
    • 命令行模式
    • Hive启动脚本
  • 五、Hive库(Database)
    • 常用命令
  • 六、Hive表(Table)
    • 6.1 内部表(管理表)
    • 6.2 外部表(External Tables)
    • 6.3 分区(Partitions)
    • 6.4 分桶(Buckets)
    • 6.5 视图(Views)
    • 6.6 侧视图(Lateral View)
  • 七.数据导入/导出
    • 八、数据类型
    • 九、Hive常用函数
  • 十、Hive查询与分析
    • 10.1 排序运算
    • 10.2 聚合运算
    • 10.3 窗口函数
  • 十一、自定义UDF
  • 十二、Hive调优
    • 12.1 存储格式
    • 12.2 数据压缩格式
    • 12.3 合理分区/分桶
    • 12.4参数优化
    • 12.5 SQL语句优化
    • 12.6 避免数据倾斜
    • 12.7 合并小文件
    • 12.8 查看Sql执行计划
  • 十三、Hive练习(附:数据包)
    • 数据包下载
    • 操作前的准备
    • 开始练习
  • 十四、集成HBase

一、什么是Hive

1.1 介绍

hive是基于Hadoop构建的一套数据仓库分析系统,它提供了丰富的SQL查询方式来分析存储在Hadoop分布式文件系统中的数据。

可以将结构化的数据文件映射为一张数据库表,并提供完整的SQL查询功能。

可以将SQL语句转换为MapReduce任务运行,通过自己的SQL查询分析需要的内容,这套SQL简称Hive SQL,使不熟悉mapreduce的用户可以很方便地利用SQL语言查询、汇总和分析数据。

1.2 特点

  • 可扩展: Hive可以自由的扩展集群的规模,一般情况下不需要重启服务。

  • 延展性: Hive支持用户自定义函数,用户可以根据自己需求来实现自己的函数。

  • 容错:良好的容错性,节点出现问题SQL仍可完成执行。

二、架构

2.1 架构图

Hive介绍、架构、实践以及调优_第1张图片

2.2用户接口:Client

  • CLI(hive shell)

  • JDBC/ODBC(java访问hive)

  • WEBUI(浏览器访问hive)

Hive介绍、架构、实践以及调优_第2张图片

2.3 元数据:Metastore

  • 记录数据仓库中模型的定义、各层级间的映射关系
  • 存储在关系数据库中
    内嵌模式:默认Derby, 轻量级内嵌SQL数据库,适合测试和演示,存储在.metastore_db目录中
    本地模式:实际生产一般存储在MySQL中,修改配置文件hive-site.xml
    远程模式:HCatalog
  • 将Hive元数据共享给其他应用程序
  • 元数据结构
    Hive介绍、架构、实践以及调优_第3张图片

2.4 驱动器:Driver

  • 解析器(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安装包

第一步

  • 解压hive包:tar -zxvf [hive包] -C /解压到的路径

第二步

  • 在hive目录下新建warehouse目录:mkdir warehouse

第三步

  • 修改etc/profile配置文件:vi /etc/profile
#增加内容
export HIVE_HOME=/HIVE的安装路径
export PATH=...:$HIVE_HOME/bin

配置完成后生效:source /etc/profile

第四步

  • 将hive/conf目录下的hive-env.sh.template文件改名为hive-env.sh
  • 修改配置文件hive-env.sh: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包路径

第五步

  • 进入hive/conf目录下:新建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>

第六步

  • 将mysql驱动包放在hive/lib目录下

第七步

  • 启动hadoop与mysql

第八步:

  • 执行初始化命令: schematool -dbType mysql -initSchema

第九步

  • 启动hive:hive

启动成功后可以测试一些操作
测试建表:create table aaa(name varchar(10));
查看当前所有表:show tables;
插入数据:insert into aaa values(‘test’),(‘test2’);

四、Hive连接方式

  • 有两种工具:Beeline和Hive命令行(CLI)
  • 有两种模式:命令行模式和交互模式

交互模式

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启用(方式一):

  • 先启动/hive目录下启动hiveServer2服务:bin/hiveserver2 start
  • 在使用URL连接:beeline -u "jdbc:hive2://localhost:10000

HiveServer2 Beeline启用(方式二):

  • 先输入beeline
  • 然后输入!connect jdbc:hive2://localhost:10000
  • 输入用户名
  • 输入密码

Hive介绍、架构、实践以及调优_第4张图片

命令行模式

  • 大量的hive查询任务,如果用交互式shell来进行输入的话,显然效率及其低下,因此,生产中更多的是使用脚本化运行机制:

  • 该机制的核心点是:hive可以用一次性命令的方式来执行给定的hql语句

Hive介绍、架构、实践以及调优_第5张图片

  • 示例:hive -e(执行语句)
hive -e "insert into table t_test select * from t_test_1;"
  • 示例:hive -f(执行文件)
#创建文件Vim test.sql
#写入语句
Select count(1) from test;
Select name from test;

#执行
 bin/hive -f test.sql

Hive启动脚本

  • 创建脚本: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

五、Hive库(Database)

  • 表的集合,HDFS中表现为一个文件夹,默认在hive.metastore.warehouse.dir属性目录下
  • 如果没有指定数据库,默认使用default数据库

常用命令

  • 建库:create database if not exists [库名];
  • 使用库:use [库名];
  • 显示所有库:show databases;
  • 显示库的存储路径以及所属用户:describe database [库名];
  • 修改库的所有者:alter databases [库名] set owner user [用户名];
  • 删除库以及库中的表:drop database [库名] cascade;
  • 查看当前所在的库:select current_database();

六、Hive表(Table)

6.1 内部表(管理表)

  • 具体表现为在HDFS中所在数据库目录下的子文件夹
  • 数据完全由Hive管理,删除表(元数据)会删除表文件夹以及数据

什么时候会使用内部表?
第一种:抽取过来的业务数据,其实用外部表或者内部表问题都不大,就算被误删,恢复起来也是很快的,如果需要对数据内容和元数据进行紧凑的管理, 那还是建议使用内部表
第二种:在做统计分析时候用到的中间表,结果表可以使用内部表,因为这些数据不需要共享,使用内部表更为合适。并且很多时候结果分区表我们只需要保留最近3天的数据,用外部表的时候删除分区时无法删除数据。

  • 建表:
  1. 默认内部表,会在指定的存储空间建立对应文件夹
  2. 只要把文件放入表路径下,表就可以读取到数据(需要和表结构匹配)
  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:换行的分隔符 
  • 新增字段:alter table [表名] add columns([字段名] [类型]);
  • 清空表数据:truncate table [表名];
  • 删除表:DROP TABLE [表名] [With PERGE]; With PERGE直接删除(可选),否则会放到 .Trash目录
  • 修改表名:ALTER TABLE [表名] RENAME TO [修改后的表名]; 常用于数据备份
  • 其他操作命令与MySQL基本相同

6.2 外部表(External Tables)

  • 数据所在路径由用户指定
  • Hive不完全管理数据,删除表(元数据)不会删除数据

什么时候会使用外部表?
第一种:管理元数据的时候,因为只读关系会使用外部表
第二种:分享数据,分享给其他用户,可以用外部表,因为外部表比较灵活,甚至可以设置到用户自己的hdfs路径下
第三种:日志数据,每天采集的ng日志和埋点日志,在存储的时候建议使用外部表,因为日志数据是采集程序实时采集进来的,一旦被误删,恢复起来非常麻烦。而且外部表方便数据的共享。

  • 建外部表(external):
//示例
 create external table 表名(
...
[字段]
...
)
...
[分隔符]
...
//外部表支持的文件格式:
//textfile(文本)、sequencefile、refile、orcfile
//其中TEXTFILE为默认格式,建表时不指定默认为这个格式,导入数据时会直接把数据文件拷贝到hdfs_上不进行处理
//SEQUENCEFILE, RCFILE, ORCFILE格式的表不能直接从本地文件导入数据,数据要先导入到textfile格式的表中,然后再从表中用insert导 入SequenceFile,RCFile,ORCFile表中
stored as [文件格式] -- 文件存储格式
location '/存储路径'; -- 数据存储路径

6.3 分区(Partitions)

  • 分区主要用于提高性能
  1. 分区列的值将表划分为segments(文件夹)
  2. 查询时使用“分区”列和常规列类似
  3. 查询时Hive自动过滤掉不用于提高性能的分区
  4. 分区(partitioned)会在表下创建文件夹,数据在各分区文件夹下
  • 分为静态分区和动态分区
  • 分区查询
    显示表的所有分区:show partitions [表名];
    查询指定分区下的数据:select * from [表名] where [分区名]=[值] ;

什么是分区?
分区(partition)是用来增加表查询速度的最佳途径,那分区相对来说就是在现有的表数据的基础上进一步分离,这个分区实指的是在HDFS的目录下建立子文件夹,每个子文件夹对应着一个分区的名字,然后我们的数据按照子文件夹重新进行组织,那我们查询的时候呢会用到分区的这个列,这样查询就会直接到子文件夹,就避免扫描表文件夹下的所有数据,这就是为什么说分区可以加速查询的一个重要原因
然后分区有静态分区和动态分区,那么通常在插入数据的时候用静态分区会比较频繁,因为静态分区我们是可以手动的去建立起来,那么相反当我们在做数据处理的时,候呢我们通常会喜欢用动态分区,因为动态分区的分区情况是由数据本身决定的,所有说动态分区更灵活,而且动态分区产生的分区个数呢是由程序控制,不需要我们人为去写,比较方便,但是动态分区有一个重要的问题,就是这个动态分区会根据数据产生很多的子目录,如果对分区的逻辑不是很了解的时候,就会很容易把整个集群的元数据空间给耗费掉,会造成一个比较大的影响,所以用动态分区的时候需要格外的注意,这就是分区的

分区操作命令

  1. 建表时定义分区:
//格式
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 ‘手动输入数据’/查询结果的数据;

  1. 静态分区增删操作
ALTER TABLE [表名] ADD PARTITION ([分区名]=[值],[二级分区名]=[值],...) PARTITION ([分区名]=[值],[二级分区名]=[值],...); //添加分区
ALTER TABLE [表名] DROP PARTITION ([分区名]=[值],[二级分区名]=[值],...);//删除分区
  1. 动态分区使用操作

动态分区是根据数据查询自动分区,如果操作不当可能会造成成千上万的分区,所有风险较高,默认情况是无法操作的,需要手动开启
在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 ;

6.4 分桶(Buckets)

  1. 分桶对应于HDFS中的文件
    更高的查询处理效率
    使抽样(sampling)更高效
    根据“桶列”的哈希函数将数据进行分桶
  2. 分桶只有动态分桶
    SET hive.enforce.bucketing = true;
  3. 定义分桶
    CLUSTERED BY (employee_id) INTO 2 BUCKETS
  4. 必须使用INSERT方式加载数据

什么是分桶?
分桶(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;

6.5 视图(Views)

  1. 视图概述
    通过隐藏子查询、连接和函数来简化查询的逻辑结构
    虚拟表,从真实表中选取数据
    只保存定义,不存储数据
    如果删除或更改基础表,则查询视图将失败
    视图是只读的,不能插入或装载数据

  2. 应用场景
    将特定的列提供给用户,保护数据隐私
    查询语句复杂的场景

视图操作命令

  1. 视图操作命令:CREATE、SHOW、DROP、ALTER
//创建视图支持 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 …

6.6 侧视图(Lateral View)

  1. 常与表生成函数结合使用,将函数的输入和输出连接
  2. OUTER关键字:即使output为空也会生成结果
select name,work_place,loc from employee lateral view outer explode(split(null,',')) a as loc;
  1. 支持多层级
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;
  1. 通常用于规范化行或解析JSON

七.数据导入/导出

注意:导本地文件和导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');

八、数据类型

  • 类似于SQL数据类型
    Hive介绍、架构、实践以及调优_第6张图片
    复合(集合)类型
  1. ARRAY(List数组):存储的数据为相同类型
  2. MAP:具有相同类型的键值对
  3. STRUCT:封装了一组字段
    Hive介绍、架构、实践以及调优_第7张图片
  • 示例
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;

九、Hive常用函数

数学运算函数

#数学运算函数
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语句

  • 这个参数使用的场合为:假如某个字段默认是null,你想其返回的不是null,而是比如0或其他默认值,可以使用这个函数
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(字段)  :求这个字段在一个组中的所有值的最小值

十、Hive查询与分析

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)

10.1 排序运算

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;

模拟图Hive介绍、架构、实践以及调优_第8张图片

10.2 聚合运算

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]

10.3 窗口函数

语法

Function (arg1,..., arg n) OVER ([PARTITION BY <...>] [ORDER BY <....>] [<window_clause>])
//PARTITION BY类似于GROUP BY,未指定则按整个结果集
//只有指定ORDER BY子句之后才能进行窗口定义
//可同时使用多个窗口函数
//过滤窗口函数计算结果必须在外面一层

窗口函数 - 序列

ROW_NUMBER():对所有数值输出不同的序号,序号唯一连续(123RANK():对相同数值,输出相同的序号,下一个序号跳过(1,1,3DENSE_RANK():对相同数值,输出相同的序号,下一个序号连续(1,1,2NLITE(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;

学习测验

  • 如何查询用户最常用/喜欢的XX数据

解析:这个问题需要套用多层查询,核心是使用窗户函数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

为什么要自定义函数

  • 有时候hive自带的函数不能满足当前需要,需要自定义函数来解决问题

UDF,UDAF,UDTF的区别

  • UDF:返回对应值,一对一

  • UDAF:返回聚类值,多对一

  • UDTF:返回拆分值,一对多

流程步骤

  • 继承UDF类或GenericUDF类
  • 重写evaluate()方法并实现函数逻辑
  • 编译打包为jar文件
  • 复制到正确的HDFS路径
  • 使用jar创建临时/永久函数
  • 调用函数

示例:自定义UDF

  • 创建Maven项目,配置pom.xml文件
        <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>
  • base64加密函数
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;
    }
}
  • base64解密函数
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所在的服务器
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
#测试加密UDF
hive> select test_en('1234');
#输出
MTIzNA==

#测试解密UDF
hive> select test_de('MTIzNA==');
#输出
1234

创建永久函数

  • 永久函数不可直接跨库使用,需要在函数名前加上创建时所在库的库名
  • 第一步(在HDFS上新建jar包存放路径):hdfs dfs -mkdir -p /apps/hive/functions
  • 第二步(将jar包上传至HDFS):hdfs dfs -put [/路径/包名.jar] /apps/hive/functions/
  • 第三步(创建永久函数):create function [函数名] as ['实现类完整的包路径'] using jar ['hdfs://IP地址:9000/JAR包在HDFS上的路径'];

永久函数删除的方法:drop function [函数名];

十二、Hive调优

毫不夸张的说,有没有掌握hive调优,是判断一个数据工程师是否合格的重要指标
hive调优涉及到压缩和存储调优,参数调优,sql的调优,数据倾斜调优,小文件问题的调优等

12.1 存储格式

在 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

  • 存储方式:行存储。默认格式,如果建表时不指定默认为此格式。,
  • 每一行都是一条记录,每行都以换行符"\n"结尾。数据不做压缩时,磁盘会开销比较大,数据解析开销也
    比较大。
  • 可结合Gzip、Bzip2等压缩方式一起使用(系统会自动检查,查询时会自动解压),推荐选用可切分的压
    缩算法。

Sequence File

  • 一种Hadoop API提供的二进制文件,使用方便、可分割、个压缩的特点。
  • 支持三种压缩选择:NONE、RECORD、BLOCK。RECORD压缩率低,一般建议使用BLOCK压缩

RC File

  • 存储方式:数据按行分块,每块按照列存储 。
    1、首先,将数据按行分块,保证同一个record在一个块上,避免读一个记录需要读取多个block。
    2、其次,块数据列式存储,有利于数据压缩和快速的列存取。
  • 相对来说,RCFile对于提升任务执行性能提升不大,但是能节省一些存储空间。可以使用升级版的ORC格
    式。

ORC File

  • 存储方式:数据按行分块,每块按照列存储
  • Hive提供的新格式,属于RCFile的升级版,性能有大幅度提升,而且数据可以压缩存储,压缩快,快速列存取。
  • ORC File会基于列创建索引,当查询的时候会很快。

Parquet File

  • 存储方式:列式存储。
  • Parquet对于大型查询的类型是高效的。对于扫描特定表格中的特定列查询,Parquet特别有用。
  • Parquet一般使用Snappy、Gzip压缩。默认Snappy。
  • Parquet支持Impala 查询引擎。
  • 表的文件存储格式尽量采用Parquet或ORC,不仅降低存储量,还优化了查询,压缩,表关联等性能

推荐:orcfile、parquet

12.2 数据压缩格式

Hive 语句最终是转化为 MapReduce 程序来执行的,而 MapReduce 的性能瓶颈在与 网络IO 和 磁盘IO,要解决性能瓶颈,最主要的是 减少数据量,对数据进行压缩是个好方式。压缩虽然是减少了数据量,但是压缩过程要消耗 CPU,但是在 Hadoop 中,往往性能瓶颈不在于 CPU,CPU 压力并不大,所以压缩充分利用了比较空闲的 CPU。

常见的压缩方式
Hive介绍、架构、实践以及调优_第9张图片
压缩比率

Hive介绍、架构、实践以及调优_第10张图片

如何选择压缩方式呢?
1、压缩比例
2、解压缩速度
3、是否支持split
支持切割的文件可以并行的有多个mapper程序处理大数据文件,一般我们选择的都是支持切分的!

压缩的优/缺点

  • 计算密集型,不压缩,否则会进一步增加cpu的负担,真实的场景中hive对cpu的压力很小

  • 网络密集型,推荐压缩,减小网络数据传输

配置

  • 在map输出阶段进行数据压缩 ,减小shuffle过程的IO瓶颈,在这个阶段,优先选择一个低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
  • 对 reduce输出结果进行压缩
set hive.exec.compress.output=true  #默认值是false,设置为True开启压缩
set mapred.output.compression.codec=org.apache.hadoop.io.compress.SnappyCodec #指定解码器
 
# 当然,也可以在hive建表时指定表的存储格式和压缩格式

演示示例

  • 上传文件到HDFS路径下
[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中设置开启结果输出压缩
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

12.3 合理分区/分桶

  • 分区是将表的数据在物理上分成不同的文件夹,以便于在查询时可以精准指定所要读取的分区目录,从来降低读取的数据量

1、当你意识到一个字段经常用来做where,就可以建分区表,使用这个字段当做分区字段
2、在查询的时候,使用分区字段来过滤,就可以避免全表扫描。只需要扫描这张表的一个分区的数据即可

  • 分桶是将表数据按指定列的hash散列后分在了不同的文件中,将来查询时,hive可以根据分桶结构,快速定位到一行数据所在的分桶文件,从来提高读取效率

12.4参数优化

#让可以不走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

12.5 SQL语句优化

where条件优化

  • 优化前(关系数据库不用考虑会自动优化)
select m.cid,u.id from order m join customer u on( m.cid =u.id )where m.dt='20180808';
  • 优化后(where条件在map端执行而不是在reduce端执行)
select m.cid,u.id fromselect * from order where dt='20180818') m join customer u on( m.cid =u.id);

union优化

  • 尽量不要使用union (union 去掉重复的记录)而是使用 union all 然后在用group by 去重

count distinct优化

  • 不要使用count (distinct cloumn) ,使用子查询
select count(1) from (select id from tablename group by id) tmp;

用in 来代替join

  • 如果需要根据一个表的字段来约束另为一个表,尽量用in来代替join . 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);

优化子查询

  • 消灭子查询内的 group by 、 COUNT(DISTINCT),MAX,MIN。可以减少job的数量。

join 优化

  • Common/shuffle/Reduce JOIN 连接发生的阶段,发生在reduce 阶段, 适用于大表 连接 大表(默认的方式)

  • Map join :连接发生在map阶段 ,适用于小表连接大表,大表的数据从文件中读取,小表的数据存放在内存中(hive中已经自动进行了优化,自动判断小表,然后进行缓存)

set hive.auto.convert.join=true;
  • SMB join: Sort -Merge -Bucket Join 对大表连接大表的优化,用桶表的概念来进行优化。在一个桶内发生笛卡尔积连接(需要是两个桶表进行join)
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;

12.6 避免数据倾斜

表现:任务进度长时间维持在99%(或100%),查看任务监控页面,发现只有少量(1个或几个)reduce子任务未完成。因为其处理的数据量和其他reduce差异过大。
原因:某个reduce的数据输入量远远大于其他reduce数据的输入量

sql导致的倾斜

  • group by

如果是在group by中产生了数据倾斜,是否可以讲group by的维度变得更细,如果没法变得更细,就可以在原分组key上添加随机数后分组聚合一次,然后对结果去掉随机数后再分组聚合
在join时,有大量为null的join key,则可以将null转成随机值,避免聚集

  • count(distinct)

情形:某特殊值过多
后果:处理此特殊值的 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)

  • mapjoin

业务数据本身的特性(存在热点key)

  • join的每路输入都比较大,且长尾是热点值导致的,可以对热点值和非热点值分别进行处理,再合并数据

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 中),最后完成最终的聚合操作。

控制空值分布

  • 将为空的 key 转变为字符串加随机数或纯随机数,将因空值而造成倾斜的数据分不到多个 Reducer。

注:对于异常值如果不需要的话,最好是提前在 where 条件里过滤掉,这样可以使计算量大大减少

12.7 合并小文件

小文件的产生有三个地方,map输入,map输出,reduce输出,小文件过多也会影响hive的分析效率:

  • 设置map输入的小文件合并
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输出和reduce输出进行合并的相关参数:
#设置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

12.8 查看Sql执行计划

#学会查看sql的执行计划,优化业务逻辑 ,减少job的数据量。对调优也非常重要
explain sql 

十三、Hive练习(附:数据包)

数据包下载

  • 下载地址:Hive数据包

操作前的准备

  • 解压数据包
  • 在Linux根目录下创建目录:mkdir -p /tmp/hivedemo/data
  • 上传数据到LInux的/tmp/hivedemo/data/目录下
  • 启动Hive服务
  • 执行数据包中的sql脚本:hive -f /tmp/hivedemo/data/setup_tables.sql
  • 进入hive交互界面
#进入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              |
+-----------------------+--+

开始练习

  • order by:排序
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    |
+----------+-----------+---------+--+


  • sort by:分区排序(根据reduce数量决定,当reduece为1时,相当于order by)
--设置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
--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    |
+----------+-----------+---------+--+

  • cluster by:相当于DISTRIBUTE BY + SORT BY
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
--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:对所有数值输出不同的序号,序号唯一连续
--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:对相同数值,输出相同的序号,下一个序号跳过(1,1,3)
--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:对相同数值,输出相同的序号,下一个序号连续(1,1,2)
--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   |
+----------+-----------+---------+-----+--+

  • avg、max、min:求平均、最大、最小
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:取当前位置的下一位
--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,10) 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:取当前位置的上一位
--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,10) 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
--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  |
+----------+-----------+---------+-------+-------+--+


  • cume_dist
--得到每个数据小于等于自身的占比
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

将HBase作为Hive数据源

  • 让HBase支持类SQL操作
    Hive介绍、架构、实践以及调优_第11张图片

将Hive ETL数据存入HBase

  • 便于快速查询
    Hive介绍、架构、实践以及调优_第12张图片

构建低延时的数据仓库

  • 利用HBase快速读写能力
  • 实现数据实时查询
    Hive介绍、架构、实践以及调优_第13张图片

集成原理

  • 通过两者本身对外的API接口互相通信完成
    1. 由Hive的lib目录中hive-hbase-handler-*.jar工具类实现
  • HBaseStorageHandler
    1. 实现Hive与HBase集成的类
    2. 对HiveStorageHandler接口的实现

操作命令

  • 创建外部表,指定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中查询、插入数据
-- 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映射的外部表名;
  • 在hbase中查看插入的数据
scan 'customer','1' --查询rowkey=1的数据

你可能感兴趣的:(菜鸟也学大数据,Hive,大数据,hive,数据仓库,hadoop)