Hive-结构化数据存储

image-20220729220408172

通过SQL来操作数据,而不是MR,SQL YYDS,这是一项基本功。建议熟练掌握,以及调优等。

hive是基于Hadoop的一个数据仓库工具,可以将结构化的数据文件映射为一张数据库表。

并提供简单的sql查询功能,可以将sql语句转换为MapReduce任务进行运行。

其优点是学习成本低,可以通过类SQL语句快速实现简单的MapReduce统计,不必开发专门的MapReduce应用,十分适合数据仓库的统计分析。

Hive 简介及核心概念

简介

Hive 是一个构建在 Hadoop 之上的数据仓库,它可以将结构化的数据文件映射成表,并提供类似 SQL 的查询功能(HQL),HQL会被转化为 MapReduce 作业,然后提交到 Hadoop 上运行。

Hive由Facebook实现并开源。

为什么用hive?

  • 使用Hadoop MapReduce直接处理数据所面临的问题:人员学习成本太高 需要掌握java语言,MapReduce实现复杂查询逻辑开发难度太大
  • 使用Hive处理数据的好处
    • 操作接口采用类SQL语法,提供快速开发的能力(简单、容易上手
    • 避免直接写MapReduce,减少开发人员的学习成本
    • 支持自定义函数,功能扩展很方便
    • 背靠Hadoop,擅长存储分析海量数据集

特点

  1. 简单、容易上手 (提供了类似 sql 的查询语言 hql),使得精通 sql 但是不了解 Java 编程的人也能很好地进行大数据分析;
  2. 数据是存储在HDFS上的,Hive本身并不提供数据的存储功能,它可以使已经存储的数据结构化。
  3. Hive是将数据映射成数据库和一张张的表,库和表的元数据信息一般存在关系型数据库上(比如MySQL)。
  4. 灵活性高,可以自定义用户函数 (UDF) 和存储格式;
  5. 数据存储方面:为超大的数据集设计的计算和存储能力,集群扩展容易,可以直接访问存储在Apache HDFS或其他数据存储系统(如Apache HBase)中的文件。;
  6. 统一的元数据管理,可与 presto/impala/sparksql 等共享数据;
  7. 数据处理方面:因为Hive语句最终会生成MapReduce任务去计算。执行延迟高,不适合做数据的实时处理,但适合做海量数据的离线处理。
  8. Hive除了支持MapReduce计算引擎,还支持Spark和Tez这两种分布式计算引擎;
  9. 数据的存储格式有多种,比如数据源是二进制格式,普通文本格式等等

Hive和Hadoop关系

  • 从功能来说,数据仓库软件,至少需要具备下述两种能力:存储数据的能力、分析数据的能力

  • Apache Hive作为一款大数据时代的数据仓库软件,当然也具备上述两种能力。只不过Hive并不是自己实现了上述两种能力,而是借助Hadoop。

    Hive利用HDFS存储数据,利用MapReduce查询分析数据。

  • 这样突然发现Hive没啥用,不过是套壳Hadoop罢了。

    其实不然,Hive的最大的魅力在于让用户专注于编写HQL,Hive帮您转换成为MapReduce程序完成对数据的分析。

理解

Hive能将数据文件映射成为一张表,这个映射是指什么? 文件和表之间的对应关系

Hive软件本身到底承担了什么功能职责? SQL语法解析编译成为MapReduce

image-20230117222916090

出去做大数据离线分析,我们使用hive干活,而hive底层使用MR干活。

Hive的体系架构

版本1

image-20230117194811966

版本2(发展)

image-20230117224111948

客户端

command-line shell & thrift/jdbc & WebGUI

  • command-line shell:通过 hive 命令行的的方式来操作数据;
  • thrift/jdbc/odbc:通过 thrift 协议按照标准的 JDBC 的方式操作数据。
  • WebGUI是通过浏览器访问Hive

元数据:Metastore

在 Hive 中,表名、表结构、字段名、字段类型、表的分隔符等统一被称为元数据。所有的元数据默认存储在 Hive 内置的 derby 数据库中,但由于 derby 只能有一个实例,也就是说不能有多个命令行客户端同时访问,所以在实际生产环境中,通常使用 MySQL 代替 derby

Metastore服务的作用是管理metadata元数据,对外暴露服务地址,让各种客户端通过连接metastore服务,由metastore再去连接MySQL数据库来存取元数据。客户端不需要知道MySQL密码,而是链接metastore,一定程度上保证了hive元数据的安全。

Hive 进行的是统一的元数据管理,就是说你在 Hive 上创建了一张表,然后在 presto/impala/sparksql 中都是可以直接使用的,它们会从 Metastore 中获取统一的元数据信息,同样的你在 presto/impala/sparksql 中创建一张表,在 Hive 中也可以直接使用。

驱动器:Driver

(1)解析器(SQL Parser):将SQL字符转换成抽象语法树AST,这一步一般使用都是第三方工具库完成,比如antlr,对AST进行语法分析,比如表是否存在,字段是否存在,SQL语句是否有误

(2)编译器(Physical Plan):将AST编译生成逻辑执行计划

(3)优化器(Query Optimizer):对逻辑执行计划进行优化

(4)执行器(Execution):把逻辑执行计划转换成可以运行的物理计划,对于Hive来说,就是MR/Spark

存储

Hive本身并不存储数据文件,而是使用HDFS进行存储。

执行引擎

Hive本身并不直接处理数据文件。而是通过执行引擎处理。当下Hive支持MapReduce(慢,默认)、Tez、Spark3种执行引擎。

HQL的执行流程

Hive 在执行一条 HQL 的时候,会经过以下步骤:

  1. 语法解析:Antlr 定义 SQL 的语法规则,完成 SQL 词法,语法解析,将 SQL 转化为抽象 语法树 AST Tree;
  2. 语义解析:遍历 AST Tree,抽象出查询的基本组成单元 QueryBlock;
  3. 生成逻辑执行计划:遍历 QueryBlock,翻译为执行操作树 OperatorTree;
  4. 优化逻辑执行计划:逻辑层优化器进行 OperatorTree 变换,合并不必要的 ReduceSinkOperator,减少 shuffle 数据量;
  5. 生成物理执行计划:遍历 OperatorTree,翻译为 MapReduce 任务;
  6. 优化物理执行计划:物理层优化器进行 MapReduce 任务的变换,生成最终的执行计划。

关于 Hive SQL 的详细执行流程可以参考美团技术团队的文章:Hive SQL 的编译过程

Hive与传统数据库对比

hive具有sql数据库的外表,但应用场景完全不同,hive只适合用来做批量数据统计分析

image-20230221200910901

数据类型

整体分为两类:原生/基本数据类型(primitive data type)和复杂数据类型(complex data type)

工作当中最常用的数据类型是字符串String和数字类型Int

常用类型总结:

整数:int

小数:double/decimal(10,2)

字符串: string

日期: 注意:虽然支持日期,但是我们一般用用string,避免一些不必要的兼容性问题

​ 年月日:date yyyy-MM-dd

年月日时分秒: TIMESTAMP yyyy-MM-dd HH:mm:ss[.fffffffff] 会添加ffff不好用

日期直接用string就行了

字典值用TINYINT

ID主键可以用INT/STRING,其他表的外键可以用STRING

价格可以用DECIMAL(11,2)

创建时间修改时间用string

基本数据类型

Hive 表中的列支持以下基本数据类型:

大类 类型
Integers(整型) TINYINT—1 字节的有符号整数
SMALLINT—2 字节的有符号整数
INT—4 字节的有符号整数
BIGINT—8 字节的有符号整数
Boolean(布尔型) BOOLEAN—TRUE/FALSE
Floating point numbers(浮点型) FLOAT— 单精度浮点型
DOUBLE—双精度浮点型
Fixed point numbers(定点数) DECIMAL—用户自定义精度定点数,比如 DECIMAL(7,2)
String types(字符串) STRING—指定字符集的字符序列
VARCHAR—具有最大长度限制的字符序列
CHAR—固定长度的字符序列
Date and time types(日期时间类型) TIMESTAMP — 时间戳
TIMESTAMP WITH LOCAL TIME ZONE — 时间戳,纳秒精度
DATE—日期类型
Binary types(二进制类型) BINARY—字节序列

TIMESTAMP 和 TIMESTAMP WITH LOCAL TIME ZONE 的区别如下:

  • TIMESTAMP WITH LOCAL TIME ZONE:用户提交时间给数据库时,会被转换成数据库所在的时区来保存。查询时则按照查询客户端的不同,转换为查询客户端所在时区的时间。
  • TIMESTAMP :提交什么时间就保存什么时间,查询时也不做任何转换。
1
2
3
4
5
6
7
8
9
10
11
12
整形:   int
浮点型: float / double / decimal(10,2)
字符串: string (特别!!)
日期类型:
年月日:date
时分秒:time
年月日-时分秒:date_time
注意:如果是日期或者时间,则使用字符串可以避免一些不必要的兼容问题
复杂类型:
array:数组,集合
map :键值对集合
struct: 类

double,如果小数位为0,会去掉0保留,比如100.00,会保存为100,而100.12,则会保存为100.12。

隐式转换

Hive 中基本数据类型遵循以下的层次结构,按照这个层次结构,子类型到祖先类型允许隐式转换。例如 INT 类型的数据允许隐式转换为 BIGINT 类型。额外注意的是:按照类型层次结构允许将 STRING 类型隐式转换为 DOUBLE 类型。

image-20230114203005568

复杂类型

类型 描述 示例
STRUCT 类似于对象,是字段的集合,字段的类型可以不同,可以使用 名称.字段名 方式进行访问 STRUCT (‘xiaoming’, 12 , ‘2018-12-12’)
MAP 键值对的集合,可以使用 名称[key] 的方式访问对应的值 map(‘a’, 1, ‘b’, 2)
ARRAY 数组是一组具有相同类型和名称的变量的集合,可以使用 名称[index] 访问对应的值 ARRAY(‘a’, ‘b’, ‘c’, ‘d’)

复杂类型操作

用复杂类型就是为了可以使用hive中一些好用的函数去操作数据

  • array
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 源数据:name与locations之间制表符分隔,locations中元素之间逗号分隔
zhangsan beijing,shanghai,tianjin,hangzhou
wangwu changchun,chengdu,wuhan,beijing

# 建表
create external table hive_array(name string, work_locations array<string>)
row format delimited fields terminated by '\t'
collection items terminated by ',';

# 导入数据 从本地导入
load data local inpath '/export/data/hivedatas/work_locations.txt' overwrite into table hive_array;

# 常用查询:
-- 查询所有数据
select * from hive_array;
-- 查询work_locations数组中第一个元素
select name, work_locations[0] location from hive_array;
-- 查询location数组中元素的个数
select name, size(work_locations) location_size from hive_array;
-- 查询location数组中包含tianjin的信息
select * from hive_array where array_contains(work_locations,'tianjin');
  • map:描述key-value数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 源数据:字段与字段分隔符: “,”;需要map字段之间的分隔符:"#";map内部k-v分隔符:":"
1,zhangsan,father:xiaoming#mother:xiaohuang#brother:xiaoxu,28
2,lisi,father:mayun#mother:huangyi#brother:guanyu,22
3,wangwu,father:wangjianlin#mother:ruhua#sister:jingtian,29
4,mayun,father:mayongzhen#mother:angelababy,26

# 建表语句
create table hive_map(
id int, name string, members map<string,string>, age int
)
row format delimited
fields terminated by ','
collection items terminated by '#'
map keys terminated by ':';

# 导入数据
load data local inpath '/export/data/hivedatas/hive_map.txt' overwrite into table hive_map;

# 常用查询
select * from hive_map;
#根据键找对应的值
select id, name, members['father'] father, members['mother'] mother, age from hive_map;
#获取所有的键
select id, name, map_keys(members) as relation from hive_map;
#获取所有的值
select id, name, map_values(members) as relation from hive_map;
#获取键值对个数
select id,name,size(members) num from hive_map;
#获取有指定key的数据
select * from hive_map where array_contains(map_keys(members), 'brother');
#查找包含brother这个键的数据,并获取brother键对应的值
select id,name, members['brother'] brother from hive_map where array_contains(map_keys(members), 'brother');
  • struct
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 源数据: 字段之间#分割,第二个字段之间冒号分割
192.168.1.1#zhangsan:40
192.168.1.2#lisi:50
192.168.1.3#wangwu:60
192.168.1.4#zhaoliu:70

# 建表语句
create table hive_struct(
ip string, info struct<name:string, age:int>
)
row format delimited
fields terminated by '#'
collection items terminated by ':';

# 导入数据
load data local inpath '/export/data/hivedatas/hive_struct.txt' into table hive_struct;

# 常用查询
select * from hive_struct;
#根据struct来获取指定的成员的值
select ip, info.name from hive_struct;

示例

如下给出一个基本数据类型和复杂数据类型的使用示例:

1
2
3
4
5
6
7
8
CREATE TABLE students(
name STRING, -- 姓名
age INT, -- 年龄
subject ARRAY<STRING>, -- 学科
score MAP<STRING,FLOAT>, -- 各个学科考试成绩
address STRUCT<houseNumber:int, street:STRING, city:STRING, province:STRING> --家庭居住地址
)
ROW FORMAT DELIMITED FIELDS TERMINATED BY "\t";

内容格式

分隔符,因为hive是数据文件到表的映射,所以需要将文件安装一定的规律分割成字段。只有分隔符正确,解析数据成功,才能在表里看到数据。建表的时候用ROW FORMAT DELIMITED指定

当数据存储在文本文件中,必须按照一定格式区别行和列,如使用逗号作为分隔符的 CSV 文件 (Comma-Separated Values) 或者使用制表符作为分隔值的 TSV 文件 (Tab-Separated Values)。但此时也存在一个缺点,就是正常的文件内容中也可能出现逗号或者制表符。

所以 Hive 默认使用了几个平时很少出现的字符,这些字符一般不会作为内容出现在文件中。Hive 默认的行和列分隔符如下表所示。

分隔符 描述
\n 对于文本文件来说,每行是一条记录,所以可以使用换行符来分割记录
^A (Ctrl+A) 分割字段 (列),在 CREATE TABLE 语句中也可以使用八进制编码 \001 来表示
^B 用于分割 ARRAY 或者 STRUCT 中的元素,或者用于 MAP 中键值对之间的分割,
在 CREATE TABLE 语句中也可以使用八进制编码 \002 表示
^C 用于 MAP 中键和值之间的分割,在 CREATE TABLE 语句中也可以使用八进制编码 \003 表示

默认分隔符是 \001,特殊字符,使用的是ASCII编码的值,在vim中连续按下ctrl+v/ctrl+a即可输入\001,显示为^A或者在文本编辑器(nodepad++)中显示为SOH

使用示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
# LazySimpleSerDe是Hive默认的,包含4种子语法,分别用于指定字段之间、集合元素之间、map映射 kv之间、换行的分隔符号。在建表的时候可以根据数据的特点灵活搭配使用
ROW FORMAT DELIMITED
[FIELDS TERMINATED BY char] -> 字段之间分隔符
[COLLECTION ITEMS TERMINATED BY char] -> 集合元素之间分隔符
[MAP KEYS TERMINATED BY char] -> Map映射kv之间分隔符
[LINES TERMINATED BY char] -> 行数据之间分隔符

CREATE TABLE page_view(viewTime INT, userid BIGINT)
ROW FORMAT DELIMITED
FIELDS TERMINATED BY '\001'
COLLECTION ITEMS TERMINATED BY '\002'
MAP KEYS TERMINATED BY '\003'
STORED AS SEQUENCEFILE;

字段以\001分隔建表时很方便,那么采集、清洗数据时对数据格式追求有什么启发 => 优先考虑\001分隔符

数据压缩

在实际工作当中,hive当中处理的数据,一般都需要经过压缩,可以使用压缩来节省我们底层的MR处理的网络带宽

MR支持的压缩编码

image-20230304134252769

为了支持多种压缩/解压缩算法,Hadoop引入了编码/解码器,如下表所示

image-20230304134316676

压缩性能的比较

image-20230304135522704

http://google.github.io/snappy/

On a single core of a Core i7 processor in 64-bit mode, Snappy compresses at about 250 MB/sec or more and decompresses at about 500 MB/sec or more.

压缩配置参数

要在Hadoop中启用压缩,可以配置如下参数(mapred-site.xml文件中):

参数 默认值 阶段 建议
io.compression.codecs (在core-site.xml中配置) org.apache.hadoop.io.compress.DefaultCodec, org.apache.hadoop.io.compress.GzipCodec, org.apache.hadoop.io.compress.BZip2Codec,
org.apache.hadoop.io.compress.Lz4Codec
输入压缩 Hadoop使用文件扩展名判断是否支持某种编解码器
mapreduce.map.output.compress false mapper输出 这个参数设为true启用压缩
mapreduce.map.output.compress.codec org.apache.hadoop.io.compress.DefaultCodec mapper输出 使用LZO、LZ4或snappy编解码器在此阶段压缩数据
mapreduce.output.fileoutputformat.compress false reducer输出 这个参数设为true启用压缩
mapreduce.output.fileoutputformat.compress.codec org.apache.hadoop.io.compress.DefaultCodec reducer输出 使用标准工具或者编解码器,如gzip和bzip2
mapreduce.output.fileoutputformat.compress.type RECORD reducer输出 SequenceFile输出使用的压缩类型:NONE和BLOCK

map阶段压缩

开启map输出阶段压缩可以减少job中map和Reduce task间数据传输量。具体配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
案例实操:
1)开启hive中间传输数据压缩功能
hive(default)>set hive.exec.compress.intermediate=true;

2)开启mapreduce中map输出压缩功能
hive (default)>set mapreduce.map.output.compress=true;

3)设置mapreduce中map输出数据的压缩方式
hive (default)>set mapreduce.map.output.compress.codec= org.apache.hadoop.io.compress.SnappyCodec;

4)执行查询语句
select count(1) from score;

map端生效位置图:

image-20230414192814472

reduce阶段压缩

当Hive将输出写入到表中时,输出内容同样可以进行压缩。属性hive.exec.compress.output控制着这个功能。用户可能需要保持默认设置文件中的默认值false,这样默认的输出就是非压缩的纯文本文件了。用户可以通过在查询语句或执行脚本中设置这个值为true,来开启输出结果压缩功能。

1
2
3
4
5
6
7
8
9
10
11
案例实操:
-- 1)开启hive最终输出数据压缩功能
set hive.exec.compress.output=true;
-- 2)开启mapreduce最终输出数据压缩
set mapreduce.output.fileoutputformat.compress=true;
-- 3)设置mapreduce最终数据输出压缩方式
set mapreduce.output.fileoutputformat.compress.codec = org.apache.hadoop.io.compress.SnappyCodec;
-- 4)设置mapreduce最终数据输出压缩为块压缩
set mapreduce.output.fileoutputformat.compress.type=BLOCK;
-- 5)测试一下输出结果是否是压缩文件
insert overwrite local directory '/export/data/compress' select * from score distribute by sid sort by sscore desc;

可以去全局配置文件设置,但是一般不推荐,都是在脚本里临时设置,全局设置没法通用

存储格式

支持的存储格式

Hive 会在 HDFS 为每个数据库上创建一个目录,数据库中的表是该目录的子目录,表中的数据会以文件的形式存储在对应的表目录下。Hive 支持以下几种文件存储格式:

格式 说明
TextFile 存储为纯文本文件。 这是 Hive 默认的文件存储格式。这种存储方式数据不做压缩,磁盘开销大,数据解析开销大。
SequenceFile SequenceFile 是 Hadoop API 提供的一种二进制文件,它将数据以<key,value>的形式序列化到文件中。这种二进制文件内部使用 Hadoop 的标准的 Writable 接口实现序列化和反序列化。它与 Hadoop API 中的 MapFile 是互相兼容的。Hive 中的 SequenceFile 继承自 Hadoop API 的 SequenceFile,不过它的 key 为空,使用 value 存放实际的值,这样是为了避免 MR 在运行 map 阶段进行额外的排序操作。
RCFile RCFile 文件格式是 FaceBook 开源的一种 Hive 的文件存储格式,首先将表分为几个行组,对每个行组内的数据按列存储,每一列的数据都是分开存储。
ORC Files ORC 是在一定程度上扩展了 RCFile,是对 RCFile 的优化。
Avro Files Avro 是一个数据序列化系统,设计用于支持大批量数据交换的应用。它的主要特点有:支持二进制序列化方式,可以便捷,快速地处理大量数据;动态语言友好,Avro 提供的机制使动态语言可以方便地处理 Avro 数据。
Parquet Parquet 是基于 Dremel 的数据模型和算法实现的,面向分析型业务的列式存储格式。它通过按列进行高效压缩和特殊的编码技术,从而在降低存储空间的同时提高了 IO 效率。

以上压缩格式中 ORC 和 Parquet 的综合性能突出,使用较为广泛,推荐使用这两种格式

行式存储和列式存储

image-20230304135845521

行存储的特点: 查询满足条件的一整行数据的时候,列存储则需要去每个聚集的字段找到对应的每个列的值,行存储只需要找到其中一个值,其余的值都在相邻地方,所以此时行存储查询的速度更快

列存储的特点:因为每个字段的数据聚集存储,在查询只需要少数几个字段的时候,能大大减少读取的数据量;每个字段的数据类型一定是相同的,列式存储可以针对性的设计更好的设计压缩算法

相比于行式存储,列式存储在分析场景下有着许多优良的特性:

1)分析场景中往往需要读大量行但是少数几个列。在行存模式下,数据按行连续存储,所有列的数据都存储在一个block中,不参与计算的列在IO时也要全部读出,读取操作被严重放大。而列存模式下,只需要读取参与计算的列即可,极大的减低了IO开销,加速了查询。

行存储优点

  • 使用行查询效率高(select * from t_covid)
  • 使用列查询效率低(select dt_value,state from t_covid)

列存储优点

  • 使用列查询效率高(select dt_value,state from t_covid)
  • 使用行查询效率低(select * from t_covid)

由于在生产环境一般都是列查询(几百个字段,不大可能都查出来,而且一般不运行用select *),所以一般用列式存储,查询效率高。推荐用ORC。

2)同一列中的数据属于同一类型,压缩效果显著。列存储往往有着高达十倍甚至更高的压缩比,节省了大量的存储空间,降低了存储成本。

3)更高的压缩比意味着更小的数据空间,从磁盘中读取相应数据耗时更短。

4)自由的压缩算法选择。不同列的数据具有不同的数据类型,适用的压缩算法也就不尽相同。可以针对不同列类型,选择最合适的压缩算法。

TEXTFILE和SEQUENCEFILE的存储格式都是基于行存储的

ORC和PARQUET是基于列式存储的

ORC格式的表不能用laod 加载文本数据,要用insert into ... select from textfile表数据方法加载。而这一步一般第三方框架会封装文本数据加载到临时表的普通textfile表,然后插入到ORC表的操作。

指定存储格式

通常在创建表的时候使用 STORED AS 参数指定:

1
2
3
4
5
6
CREATE TABLE page_view(viewTime INT, userid BIGINT)
ROW FORMAT DELIMITED
FIELDS TERMINATED BY '\001'
COLLECTION ITEMS TERMINATED BY '\002'
MAP KEYS TERMINATED BY '\003'
STORED AS SEQUENCEFILE;

各个存储文件类型指定方式如下:

  • STORED AS TEXTFILE
  • STORED AS SEQUENCEFILE
  • STORED AS ORC
  • STORED AS PARQUET
  • STORED AS AVRO
  • STORED AS RCFILE

压缩比: ORC > Parquet > textFile

查询速度:ORC > textFile > Parquet

存储和压缩结合

ORC存储方式的压缩:

Key Default Notes
orc.compress ZLIB high level compression (one of NONE, ZLIB, SNAPPY)
orc.compress.size 262,144 number of bytes in each compression chunk
orc.stripe.size 67,108,864 number of bytes in each stripe
orc.row.index.stride 10,000 number of rows between index entries (must be >= 1000)
orc.create.index true whether to create row indexes
orc.bloom.filter.columns “” comma separated list of column names for which bloom filter should be created
orc.bloom.filter.fpp 0.05 false positive probability for bloom filter (must >0.0 and <1.0)

建表时指定非压缩的ORC存储方式

1
STORED AS orc tblproperties ("orc.compress"="NONE");

建表时指定SNAPPY压缩的ORC存储方式

1
STORED AS orc tblproperties ("orc.compress"="SNAPPY");

orc存储文件默认采用ZLIB压缩。比snappy压缩的小。

存储方式和压缩总结: (orc + snappy)

​ 在实际的项目开发当中,hive表的数据存储格式一般选择:orc或parquet。压缩方式一般选择snappy。

image-20230414195902707

内部表和外部表

内部表又叫做管理表 (Managed/Internal Table),创建表时不做任何指定,默认创建的就是内部表。想要创建外部表 (External Table),则需要使用 External 进行修饰。 内部表和外部表主要区别如下:

内部表 外部表
数据存储位置 内部表数据存储的位置由 hive.metastore.warehouse.dir 参数指定,默认情况下表的数据存储在 HDFS 的 /user/hive/warehouse/数据库名.db/表名/ 目录下 外部表数据的存储位置创建表时由 Location 参数指定;
导入数据 在导入数据到内部表,内部表将数据移动到自己的数据仓库目录下,数据的生命周期由 Hive 来进行管理 外部表不会将数据移动到自己的数据仓库目录下,只是在元数据中存储了数据的位置
删除表 删除元数据(metadata)和文件 只删除元数据(metadata)

Hive 环境安装

见Linux-大数据篇

metasotre配置方式

metastore服务配置有3种模式:内嵌模式、本地模式、远程模式

  • 区分3种配置方式的关键是弄清楚两个问题:
    • Metastore服务是否需要单独配置、单独启动?
    • Metadata是存储在内置的derby中,还是第三方RDBMS,比如MySQL。
  • 企业推荐模式–远程模式部署

image-20230117224803776

metastore远程模式

在生产环境中,建议用远程模式来配置Hive Metastore。在这种情况下,其他依赖hive的软件都可以通过Metastore访问hive。由于还可以完全屏蔽数据库层,因此这也带来了更好的可管理性/安全性。

image-20230117224903276

Beeline CLI访问HiveServer2,然后再访问MetaStore

安装前准备

由于Apache Hive是一款基于Hadoop的数据仓库软件,通常部署运行在Linux系统之上。因此不管使用何种方式配置Hive Metastore,必须要先保证服务器的基础环境正常,Hadoop集群健康可用

  • 服务器基础环境

集群时间同步、防火墙关闭、主机Host映射、免密登录、JDK安装

  • Hadoop集群健康可用

启动Hive之前必须先启动Hadoop集群。特别要注意,需等待HDFS安全模式关闭(大概30s)之后再启动运行Hive。

Hive不是分布式安装运行的软件,其分布式的特性主要借由Hadoop完成。包括分布式存储、分布式计算。

Hadoop和hive整合

  • 因为Hive需要把数据存储在HDFS上,并且通过MapReduce作为执行引擎处理数据;
  • 因此需要在Hadoop中添加相关配置属性,以满足Hive在Hadoop上运行。
  • 修改Hadoop中core-site.xml,并且Hadoop集群同步配置文件,重启生效。
1
2
3
4
5
6
7
8
9
<!-- 整合hive -->
<property>
<name>hadoop.proxyuser.root.hosts</name>
<value>*</value>
</property>
<property>
<name>hadoop.proxyuser.root.groups</name>
<value>*</value>
</property>

MySQL安装

注意MySQL只需要在一台机器安装并且需要授权远程访问。

  • 卸载Centos7自带的mariadb
1
2
3
4
5
6
[root@node3 ~]# rpm -qa|grep mariadb
mariadb-libs-5.5.64-1.el7.x86_64

[root@node3 ~]# rpm -e mariadb-libs-5.5.64-1.el7.x86_64 --nodeps
[root@node3 ~]# rpm -qa|grep mariadb
[root@node3 ~]#
  • 安装mysql
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
mkdir /export/software/mysql

#上传mysql-5.7.29-1.el7.x86_64.rpm-bundle.tar 到上述文件夹下 解压
tar xvf mysql-5.7.29-1.el7.x86_64.rpm-bundle.tar

#执行安装
yum -y install libaio

[root@node3 mysql]# rpm -ivh mysql-community-common-5.7.29-1.el7.x86_64.rpm mysql-community-libs-5.7.29-1.el7.x86_64.rpm mysql-community-client-5.7.29-1.el7.x86_64.rpm mysql-community-server-5.7.29-1.el7.x86_64.rpm

warning: mysql-community-common-5.7.29-1.el7.x86_64.rpm: Header V3 DSA/SHA1 Signature, key ID 5072e1f5: NOKEY
Preparing... ################################# [100%]
Updating / installing...
1:mysql-community-common-5.7.29-1.e################################# [ 25%]
2:mysql-community-libs-5.7.29-1.el7################################# [ 50%]
3:mysql-community-client-5.7.29-1.e################################# [ 75%]
4:mysql-community-server-5.7.29-1.e################ ( 49%)
  • mysql初始化设置
1
2
3
4
5
6
7
8
9
10
11
12
13
#初始化
mysqld --initialize

#更改所属组
chown mysql:mysql /var/lib/mysql -R

#启动mysql
systemctl start mysqld.service

#查看生成的临时root密码
cat /var/log/mysqld.log

[Note] A temporary password is generated for root@localhost: o+TU+KDOm004
  • 修改root密码 授权远程访问 设置开机自启动
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
[root@node2 ~]# mysql -u root -p
Enter password: #这里输入在日志中生成的临时密码
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 3
Server version: 5.7.29

Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql>


#更新root密码 设置为hadoop
mysql> alter user user() identified by "hadoop";
Query OK, 0 rows affected (0.00 sec)


#授权
mysql> use mysql;

mysql> GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY 'hadoop' WITH GRANT OPTION;

mysql> FLUSH PRIVILEGES;

#mysql的启动和关闭 状态查看 (这几个命令必须记住)
systemctl stop mysqld
systemctl status mysqld
systemctl start mysqld

#建议设置为开机自启动服务
[root@node2 ~]# systemctl enable mysqld
Created symlink from /etc/systemd/system/multi-user.target.wants/mysqld.service to /usr/lib/systemd/system/mysqld.service.

#查看是否已经设置自启动成功
[root@node2 ~]# systemctl list-unit-files | grep mysqld
mysqld.service enabled
  • Centos7 干净卸载mysql 5.7
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#关闭mysql服务
systemctl stop mysqld.service

#查找安装mysql的rpm包
[root@node3 ~]# rpm -qa | grep -i mysql
mysql-community-libs-5.7.29-1.el7.x86_64
mysql-community-common-5.7.29-1.el7.x86_64
mysql-community-client-5.7.29-1.el7.x86_64
mysql-community-server-5.7.29-1.el7.x86_64

#卸载
[root@node3 ~]# yum remove mysql-community-libs-5.7.29-1.el7.x86_64 mysql-community-common-5.7.29-1.el7.x86_64 mysql-community-client-5.7.29-1.el7.x86_64 mysql-community-server-5.7.29-1.el7.x86_64

#查看是否卸载干净
rpm -qa | grep -i mysql

#查找mysql相关目录 删除
[root@node1 ~]# find / -name mysql
/var/lib/mysql
/var/lib/mysql/mysql
/usr/share/mysql

[root@node1 ~]# rm -rf /var/lib/mysql
[root@node1 ~]# rm -rf /var/lib/mysql/mysql
[root@node1 ~]# rm -rf /usr/share/mysql

#删除默认配置 日志
rm -rf /etc/my.cnf
rm -rf /var/log/mysqld.log

Hive3的安装

上传安装包 解压

官网:http://archive.apache.org/dist/hive/hive-3.1.2

1
2
3
4
tar zxvf apache-hive-3.1.2-bin.tar.gz -C /usr/local/apps

# 重命名
# mv apache-hive-3.1.2-bin hive-3.1.2
  • 解决Hive与Hadoop之间guava版本差异
1
2
3
cd /usr/local/apps/apache-hive-3.1.2-bin/
rm -rf lib/guava-19.0.jar
cp /usr/local/apps/hadoop-3.3.0/share/hadoop/common/lib/guava-27.0-jre.jar ./lib/

修改配置文件

  • hive-env.sh
1
2
3
4
5
6
7
cd /usr/local/apps/apache-hive-3.1.2-bin/conf
mv hive-env.sh.template hive-env.sh

vim hive-env.sh
export HADOOP_HOME=/usr/local/apps/hadoop-3.3.0
export HIVE_CONF_DIR=/usr/local/apps/apache-hive-3.1.2-bin/conf
export HIVE_AUX_JARS_PATH=/usr/local/apps/apache-hive-3.1.2-bin/lib
  • hive-site.xml

vim hive-site.xml 新增文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<configuration>
<!-- 存储元数据mysql相关配置,包括数据库名hive3 -->
<property>
<name>javax.jdo.option.ConnectionURL</name>
<value>jdbc:mysql://node1:3306/hive3?createDatabaseIfNotExist=true&amp;useSSL=false&amp;useUnicode=true&amp;characterEncoding=UTF-8</value>
</property>

<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>root</value>
</property>

<!-- H2S运行绑定host -->
<property>
<name>hive.server2.thrift.bind.host</name>
<value>node1</value>
</property>

<!-- 远程模式部署metastore metastore地址 -->
<property>
<name>hive.metastore.uris</name>
<value>thrift://node1:9083</value>
</property>

<!-- 关闭元数据存储授权 -->
<property>
<name>hive.metastore.event.db.notification.api.auth</name>
<value>false</value>
</property>
<!-- 关闭元数据存储版本的验证 -->
<property>
<name>hive.metastore.schema.verification</name>
<value>false</value>
</property>
</configuration>

  • 添加环境变量
1
2
3
4
5
6
7
8
9
#添加环境变量
vim /etc/profile

export HIVE_HOME=/usr/local/apps/hive-3.1.2
export PATH=:$HIVE_HOME/bin:$PATH


#让环境变量生效
source /etc/profile
  • 上传mysql jdbc驱动到hive安装包lib下
1
2
# 上传到/usr/local/apps/apache-hive-3.1.2-bin/lib下
mysql-connector-java-5.1.32.jar
  • 初始化元数据
1
2
3
4
cd /usr/local/apps/apache-hive-3.1.2-bin/

bin/schematool -initSchema -dbType mysql -verbos
#初始化成功会在mysql中创建74张表
  • 在hdfs创建hive存储目录(如存在则不用操作)
1
2
3
4
hadoop fs -mkdir /tmp
hadoop fs -mkdir -p /user/hive/warehouse
hadoop fs -chmod g+w /tmp
hadoop fs -chmod g+w /user/hive/warehouse

启动hive

  • 1、启动metastore服务
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#前台启动  关闭ctrl+c
/usr/local/apps/apache-hive-3.1.2-bin/bin/hive --service metastore

#前台启动开启debug日志
/usr/local/apps/apache-hive-3.1.2-bin/bin/hive --service metastore --hiveconf hive.root.logger=DEBUG,console

#后台启动 进程挂起 关闭使用jps+ kill -9
nohup /usr/local/apps/apache-hive-3.1.2-bin/bin/hive --service metastore &

#nohup 命令,在默认情况下(非重定向时),会输出一个名叫 nohup.out 的文件到当前目录下

#验证是否安装成功!
jps -m 可以看进程信息
在Linux中输入hive命令,直接回车,出现一个终端,在该终端中可以输入sql命令:
show databases;

#如果hive没有安装成功,则需要做以下操作:
1、 检查参数是否有问题
2、 杀死两个junjar进程
3、 进入mysql删除hive数据库:drop database hive
4、 重新对hive初始化:schematool -initSchema -dbType mysql -verbos

前台启动,进程会一直占据终端,ctrl + c结束进程,服务关闭。可以根据需求添加参数开启debug日志,获取详细日志信息,便于排错。

  • 2、启动hiveserver2服务
1
2
3
nohup /usr/local/apps/apache-hive-3.1.2-bin/bin/hive --service hiveserver2 &

#注意 启动hiveserver2需要一定的时间 不要启动之后立即beeline连接 可能连接不上
  • 3、beeline客户端连接

HIVE有2代客户端

一代$HIVE_HOME/bin/hive(不推荐使用),是一个shellUtil,主要用于交互/批处理模式运行hive查询;以及hive相关服务启动,比如metastore服务。

二代$HIVE_HOME/bin/beeline,是个JDBC客户端,比一代性能更好,安全性更高。

​ 远程模式下beeline通过 Thrift 连接到单独的HiveServer2服务上,这也是官方推荐在生产环境中使用的模式。

​ HiveServer2支持多客户端的并发和身份认证,旨在为开放API客户端如JDBC、ODBC提供更好的支持。

bin/beeline客户端使用: 首先启动metastore服务,然后启动hiveserver2服务。

  • 拷贝node1安装包到beeline客户端机器上(node3)
1
scp -r /usr/local/apps/apache-hive-3.1.2-bin/ node3:/usr/local/apps/
  • 错误
1
Error: Could not open client transport with JDBC Uri: jdbc:hive2://node1:10000: Failed to open new session: java.lang.RuntimeException: org.apache.hadoop.ipc.RemoteException(org.apache.hadoop.security.authorize.AuthorizationException): User: root is not allowed to impersonate root (state=08S01,code=0)
  • 修改
1
2
3
4
5
6
7
8
9
在hadoop的配置文件core-site.xml中添加如下属性:
<property>
<name>hadoop.proxyuser.root.hosts</name>
<value>*</value>
</property>
<property>
<name>hadoop.proxyuser.root.groups</name>
<value>*</value>
</property>
  • 连接访问
1
2
3
4
5
6
7
/usr/local/apps/apache-hive-3.1.2-bin/bin/beeline

# 连接节点node1
beeline> ! connect jdbc:hive2://node1:10000
# 输入用户名和密码
beeline> root
beeline> 直接回车

错误解决:Hive3执行insert插入操作 statstask异常

  • 现象: 在执行insert + values操作的时候 虽然最终执行成功,结果正确。但是在执行日志中会出现如下的错误信息。

image-20201109144915808

  • 开启hiveserver2执行日志。查看详细信息
1
2
3
2020-11-09 00:37:48,963 WARN  [5ce14c58-6b36-476a-bab8-89cba7dd1706 main] metastore.RetryingMetaStoreClient: MetaStoreClient lost connection. Attempting to reconnect (1 of 1) after 1s. setPartitionColumnStatistics

ERROR [5ce14c58-6b36-476a-bab8-89cba7dd1706 main] exec.StatsTask: Failed to run stats task

image-20201109145136486

  • 但是 此错误并不影响最终的插入语句执行成功

分析原因和解决

  • statstask是一个hive中用于统计插入等操作的状态任务 其返回结果如下

image-20201109145304560

image-20201109145621381

  • 在mysql metastore中删除 PART_COL_STATS这张表即可

注释comment中文乱码

hive将数据保存到mysql中,默认不支持中文编码,所以comment注释都是乱码的。

image-20230119044315883

image-20230119044359128

修改hive元数据信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
--注意 下面sql语句是需要在MySQL中执行  修改Hive存储的元数据信息(metadata)
use hive3;
show tables;

-- 修改表字段注解和表注解
alter table hive3.COLUMNS_V2 modify column COMMENT varchar(256) character set utf8;
alter table hive3.TABLE_PARAMS modify column PARAM_VALUE varchar(4000) character set utf8;
-- 修改分区字段注解
alter table hive3.PARTITION_PARAMS modify column PARAM_VALUE varchar(4000) character set utf8 ;
alter table hive3.PARTITION_KEYS modify column PKEY_COMMENT varchar(4000) character set utf8;
-- 修改索引注解
alter table hive3.INDEX_PARAMS modify column PARAM_VALUE varchar(4000) character set utf8;
-- 修改数据库注解
alter table hive3.DBS modify column `DESC` varchar(4000) character set utf8 ;

重新创建表即可

image-20230119044612280

Hive CLI和Beeline命令行的基本使用

Hive CLI

Help

使用 hive -H 或者 hive --help 命令可以查看所有命令的帮助,显示如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
usage: hive
-d,--define <key=value> Variable subsitution to apply to hive
commands. e.g. -d A=B or --define A=B --定义用户自定义变量
--database <databasename> Specify the database to use -- 指定使用的数据库
-e <quoted-query-string> SQL from command line -- 执行指定的 SQL
-f <filename> SQL from files --执行 SQL 脚本
-H,--help Print help information -- 打印帮助信息
--hiveconf <property=value> Use value for given property --自定义配置
--hivevar <key=value> Variable subsitution to apply to hive --自定义变量
commands. e.g. --hivevar A=B
-i <filename> Initialization SQL file --在进入交互模式之前运行初始化脚本
-S,--silent Silent mode in interactive shell --静默模式
-v,--verbose Verbose mode (echo executed SQL to the console) --详细模式

交互式命令行

直接使用 Hive 命令,不加任何参数,即可进入交互式命令行。

执行SQL命令

在不进入交互式命令行的情况下,可以使用 hive -e 执行 SQL 命令。

1
hive -e 'select * from emp';

image-20230114203043745

执行SQL脚本

用于执行的 sql 脚本可以在本地文件系统,也可以在 HDFS 上。

1
2
3
4
5
# 本地文件系统
hive -f /usr/file/simple.sql; # 生产上比较常用

# HDFS文件系统
hive -f hdfs://hadoop001:8020/tmp/simple.sql;

其中 simple.sql 内容如下:

1
select * from emp;

配置Hive变量

可以使用 --hiveconf 设置 Hive 运行时的变量。

1
2
3
hive -e 'select * from emp' \
--hiveconf hive.exec.scratchdir=/tmp/hive_scratch \
--hiveconf mapred.reduce.tasks=4;

hive.exec.scratchdir:指定 HDFS 上目录位置,用于存储不同 map/reduce 阶段的执行计划和这些阶段的中间输出结果。

配置文件启动

使用 -i 可以在进入交互模式之前运行初始化脚本,相当于指定配置文件启动。

1
hive -i /usr/file/hive-init.conf;

其中 hive-init.conf 的内容如下:

1
set hive.exec.mode.local.auto = true;

hive.exec.mode.local.auto 默认值为 false,这里设置为 true ,代表开启本地模式。

用户自定义变量

--define <key=value> --hivevar <key=value> 在功能上是等价的,都是用来实现自定义变量,这里给出一个示例:

定义变量:

1
hive  --define  n=ename --hiveconf  --hivevar j=job;

在查询中引用自定义变量:

1
2
3
4
5
6
7
# 以下两条语句等价
hive > select ${n} from emp;
hive > select ${hivevar:n} from emp;

# 以下两条语句等价
hive > select ${j} from emp;
hive > select ${hivevar:j} from emp;

结果如下:

image-20230117223653154

Beeline

HiveServer2

Hive 内置了 HiveServer 和 HiveServer2 服务,两者都允许客户端使用多种编程语言进行连接,但是 HiveServer 不能处理多个客户端的并发请求,所以产生了 HiveServer2。

HiveServer2(HS2)允许远程客户端可以使用各种编程语言向 Hive 提交请求并检索结果,支持多客户端并发访问和身份验证。HS2 是由多个服务组成的单个进程,其包括基于 Thrift 的 Hive 服务(TCP 或 HTTP)和用于 Web UI 的 Jetty Web 服务器。

HiveServer2 拥有自己的 CLI(Beeline),Beeline 是一个基于 SQLLine 的 JDBC 客户端。由于 HiveServer2 是 Hive 开发维护的重点 (Hive0.15 后就不再支持 hiveserver),所以 Hive CLI 已经不推荐使用了,官方更加推荐使用 Beeline。

Beeline

Beeline 拥有更多可使用参数,可以使用 beeline --help 查看,完整参数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
Usage: java org.apache.hive.cli.beeline.BeeLine
-u <database url> the JDBC URL to connect to
-r reconnect to last saved connect url (in conjunction with !save)
-n <username> the username to connect as
-p <password> the password to connect as
-d <driver class> the driver class to use
-i <init file> script file for initialization
-e <query> query that should be executed
-f <exec file> script file that should be executed
-w (or) --password-file <password file> the password file to read password from
--hiveconf property=value Use value for given property
--hivevar name=value hive variable name and value
This is Hive specific settings in which variables
can be set at session level and referenced in Hive
commands or queries.
--property-file=<property-file> the file to read connection properties (url, driver, user, password) from
--color=[true/false] control whether color is used for display
--showHeader=[true/false] show column names in query results
--headerInterval=ROWS; the interval between which heades are displayed
--fastConnect=[true/false] skip building table/column list for tab-completion
--autoCommit=[true/false] enable/disable automatic transaction commit
--verbose=[true/false] show verbose error messages and debug info
--showWarnings=[true/false] display connection warnings
--showNestedErrs=[true/false] display nested errors
--numberFormat=[pattern] format numbers using DecimalFormat pattern
--force=[true/false] continue running script even after errors
--maxWidth=MAXWIDTH the maximum width of the terminal
--maxColumnWidth=MAXCOLWIDTH the maximum width to use when displaying columns
--silent=[true/false] be more silent
--autosave=[true/false] automatically save preferences
--outputformat=[table/vertical/csv2/tsv2/dsv/csv/tsv] format mode for result display
--incrementalBufferRows=NUMROWS the number of rows to buffer when printing rows on stdout,
defaults to 1000; only applicable if --incremental=true
and --outputformat=table
--truncateTable=[true/false] truncate table column when it exceeds length
--delimiterForDSV=DELIMITER specify the delimiter for delimiter-separated values output format (default: |)
--isolation=LEVEL set the transaction isolation level
--nullemptystring=[true/false] set to true to get historic behavior of printing null as empty string
--maxHistoryRows=MAXHISTORYROWS The maximum number of rows to store beeline history.
--convertBinaryArrayToString=[true/false] display binary column data as string or as byte array
--help display this message

常用参数

在 Hive CLI 中支持的参数,Beeline 都支持,常用的参数如下。更多参数说明可以参见官方文档 Beeline Command Options

参数 说明
-u <database URL> 数据库地址
-n <username> 用户名
-p <password> 密码
-d <driver class> 驱动 (可选)
-e <query> 执行 SQL 命令
-f <file> 执行 SQL 脚本
-i (or)–init <file or files> 在进入交互模式之前运行初始化脚本
–property-file <file> 指定配置文件
–hiveconf property*=*value 指定配置属性
–hivevar name*=*value 用户自定义属性,在会话级别有效

示例: 使用用户名和密码连接 Hive

1
$ beeline -u jdbc:hive2://localhost:10000  -n username -p password 

一键启动脚本

这里,我们写一个expect脚本,可以一键启动beenline,并登录到hive。expect是建立在tcl基础上的一个自动化交

互套件, 在一些需要交互输入指令的场景下, 可通过脚本设置自动进行交互通信。

1、安装expect

1
yum   -y install expect  

2、 创建脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
cd /export/server/hive-3.1.2/bin
vim beenline.exp

#!/bin/expect
spawn beeline
set timeout 5
expect "beeline>"
send "!connect jdbc:hive2://node1:10000\r"
expect "Enter username for jdbc:hive2://node1:10000:"
send "root\r"
expect "Enter password for jdbc:hive2://node1:10000:"
send "root\r"
interact

3、修改脚本权限

1
chmod 777  beenline.exp

4、启动脚本

1
expect beenline.exp

5、退出beeline

1
0: jdbc:hive2://node3:10000> !quit

6、创建shell脚本

1
2
3
4
5
6
7
vim /export/server/hive-3.1.2/bin/beeline2 

#!/bin/bash
expect /export/server/hive-3.1.2/bin/beeline.exp


chmod 777 /export/server/hive-3.1.2/bin/beeline2

7、最终调用

1
beeline2

Hive配置

可以通过三种方式对 Hive 的相关属性进行配置,分别介绍如下:

配置文件

方式一为使用配置文件,使用配置文件指定的配置是永久有效的。Hive 有以下三个可选的配置文件:

  • hive-site.xml :Hive 的主要配置文件;

  • hivemetastore-site.xml: 关于元数据的配置;

  • hiveserver2-site.xml:关于 HiveServer2 的配置。

示例如下,在 hive-site.xml 配置 hive.exec.scratchdir

1
2
3
4
5
<property>
<name>hive.exec.scratchdir</name>
<value>/tmp/mydir</value>
<description>Scratch space for Hive jobs</description>
</property>

hiveconf

方式二为在启动命令行 (Hive CLI / Beeline) 的时候使用 --hiveconf 指定配置,这种方式指定的配置作用于整个 Session。

1
hive --hiveconf hive.exec.scratchdir=/tmp/mydir

set

方式三为在交互式环境下 (Hive CLI / Beeline),使用 set 命令指定。这种设置的作用范围也是 Session 级别的,配置对于执行该命令后的所有命令生效。set 兼具设置参数和查看参数的功能。如下:

1
2
3
4
5
6
7
8
0: jdbc:hive2://hadoop001:10000> set hive.exec.scratchdir=/tmp/mydir;
No rows affected (0.025 seconds)
0: jdbc:hive2://hadoop001:10000> set hive.exec.scratchdir;
+----------------------------------+--+
| set |
+----------------------------------+--+
| hive.exec.scratchdir=/tmp/mydir |
+----------------------------------+--+

配置优先级

配置的优先顺序如下 (由低到高):
hive-site.xml - >hivemetastore-site.xml- > hiveserver2-site.xml - > -- hiveconf- > set

配置参数

Hive 可选的配置参数非常多,在用到时查阅官方文档即可AdminManual Configuration

Hive 可视化工具

DataGrip、Dbeaver、SQuirrel SQL Client等

可以在Windows、MAC平台中通过JDBC连接HiveServer2的图形界面工具

这类工具往往专门针对SQL类软件进行开发优化、页面美观大方,操作简洁,更重要的是SQL编辑环境优雅;

SQL语法智能提示补全、关键字高亮、查询结果智能显示、按钮操作大于命令操作;

DataGrip

DataGrip是由JetBrains公司推出的数据库管理软件,DataGrip支持几乎所有主流的关系数据库产品,如DB2、Derby、MySQL、Oracle、SQL Server等,也支持几乎所有主流的大数据生态圈SQL软件,并且提供了简单易用的界面,开发者上手几乎不会遇到任何困难。

1.windows创建工程文件夹

image-20230117233834980

2.DataGrip中创建新Project

image-20230117233849968

3.关联本地工程文件夹

image-20230117233917171

4.DataGrip连接Hive

image-20230117233939110

5.配置Hive JDBC连接驱动

image-20230117233958892

6.配置Hive JDBC连接驱动

image-20230117234025312

image-20230117234029253

7.返回,配置Hiveserver2服务连接信息

image-20230117234053191

8.返回,配置Hiveserver2服务连接信息

image-20230117234113619

HUE

HUE相关的使用

HUE: hadoop 用户体验

出现目的: 提升使用hadoop生态圈中相关软件便利性(不止能操作HIVE)

核心: 是将各类hadoop生态圈的软件的操作界面集成在一个软件中 (大集成者)

如何HUE界面呢?

image-20230531095131369

用户密码:hue/hue

image-20230531095147195

Hive 常用 DDL 操作

和mysql DDL语法类似

Database

在Hive中,默认的数据库叫做default,存储数据位置位于HDFS/user/hive/warehouse下。
用户自己创建的数据库存储位置是/user/hive/warehouse/database_namedb下

在hive中databases和schemas是一样的概念,都表示数据库。

查看数据列表

1
show databases;

image-20230117223034629

使用数据库

1
USE database_name;

新建数据库

语法:

1
2
3
4
CREATE (DATABASE|SCHEMA) [IF NOT EXISTS] database_name   --DATABASE|SCHEMA 是等价的
[COMMENT database_comment] --数据库注释
[LOCATION hdfs_path] --存储在 HDFS 上的位置
[WITH DBPROPERTIES (property_name=property_value, ...)]; --指定额外属性

示例:

1
2
3
CREATE DATABASE IF NOT EXISTS hive_test
COMMENT 'hive database for test'
WITH DBPROPERTIES ('create'='zuoer');

查看数据库信息

语法:

1
DESC DATABASE [EXTENDED] db_name; --EXTENDED 表示是否显示额外属性

示例:

1
DESC DATABASE  EXTENDED hive_test;

删除数据库

语法:

1
DROP (DATABASE|SCHEMA) [IF EXISTS] database_name [RESTRICT|CASCADE];
  • 默认行为是 RESTRICT,如果数据库中存在表则删除失败。要想删除库及其中的表,可以使用 CASCADE 级联删除。

示例:

1
DROP DATABASE IF EXISTS hive_test CASCADE;

创建表

建表语法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
CREATE [TEMPORARY] [EXTERNAL] TABLE [IF NOT EXISTS] [db_name.]table_name     --表名
[(col_name data_type [COMMENT col_comment],
... [constraint_specification])] --列名 列数据类型
[COMMENT table_comment] --表描述
[PARTITIONED BY (col_name data_type [COMMENT col_comment], ...)] --分区表分区规则
[
CLUSTERED BY (col_name, col_name, ...)
[SORTED BY (col_name [ASC|DESC], ...)] INTO num_buckets BUCKETS
] --分桶表分桶规则
[SKEWED BY (col_name, col_name, ...) ON ((col_value, col_value, ...), (col_value, col_value, ...), ...)
[STORED AS DIRECTORIES]
] --指定倾斜列和值
[
[ROW FORMAT row_format]
[STORED AS file_format]
| STORED BY 'storage.handler.class.name' [WITH SERDEPROPERTIES (...)]
] -- 指定行分隔符、存储文件格式或采用自定义存储格式
[LOCATION hdfs_path] -- 指定表的存储位置
[TBLPROPERTIES (property_name=property_value, ...)] --指定表的属性
[AS select_statement]; --从查询结果创建表

1.建表语句注释不要带分号

内部表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  CREATE TABLE emp(
empno INT,
ename STRING,
job STRING,
mgr INT,
hiredate TIMESTAMP,
sal DECIMAL(7,2),
comm DECIMAL(7,2),
deptno INT)
ROW FORMAT DELIMITED FIELDS TERMINATED BY "\t"; -- 自定字段之间的分隔符

1、内部表认为该表独占表数据文件,该文件不能共享
2、内部表对表文件有绝对的控制权
3、当删除内部表时,表文件会跟着一起删除(同时删除元数据和表数据)
4、所有的非共享表都可以设置为内部表

外部表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
  CREATE EXTERNAL TABLE emp_external(
empno INT,
ename STRING,
job STRING,
mgr INT,
hiredate TIMESTAMP,
sal DECIMAL(7,2),
comm DECIMAL(7,2),
deptno INT)
ROW FORMAT DELIMITED FIELDS TERMINATED BY "\t"
LOCATION '/hive/emp_external';

1、外部表认为该表不能独占表数据文件,文件可以共享
2、外部表对表文件没有绝对的控制权
3、当删除外部表时,表文件不会跟着一起删除(只会删除mysql中的hive元数据(映射信息),不会删除HDFS中的表数据)
4、所有的非共享表都可以设置为内部表
5、如果表数据需要被共享,则可以设置为外部表

使用 desc format emp_external 命令可以查看表的详细信息如下:

image-20230117223110155

分区表

比如你做数据分析时,本来100G的全量文件,可能分区后只需要查询1G的文件进行分析。查询速度会快很多。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
  CREATE EXTERNAL TABLE emp_partition(
empno INT,
ename STRING,
job STRING,
mgr INT,
hiredate TIMESTAMP,
sal DECIMAL(7,2),
comm DECIMAL(7,2)
)
PARTITIONED BY (deptno INT) -- 按照部门编号进行分区
ROW FORMAT DELIMITED FIELDS TERMINATED BY "\t"
LOCATION '/hive/emp_partition';

#分区就是分文件夹
1、分区表实际是就是对要进行分析的文件进行分类管理
2、本质是将相同特征的文件存放在同一个文件夹下,通过文件夹对数据进行分类
3、分区之后在查询时,可以通过添加条件,避免进行全表扫描,提高查询效率
4、分区表又分为静态分区和动态分区
5、分区表是一种优化手段,是锦上添花的东西,一张表可以没有分区,但是查询效率可能会比较低

分桶表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
  CREATE EXTERNAL TABLE emp_bucket(
empno INT,
ename STRING,
job STRING,
mgr INT,
hiredate TIMESTAMP,
sal DECIMAL(7,2),
comm DECIMAL(7,2),
deptno INT)
CLUSTERED BY(empno) SORTED BY(empno ASC) INTO 4 BUCKETS --按照员工编号散列到四个 bucket 中
ROW FORMAT DELIMITED FIELDS TERMINATED BY "\t"
LOCATION '/hive/emp_bucket';


1、分桶表和分区表没什么关系
2、分桶表是将表数据分到多个文件,分区表是将数据分到多个文件夹
3、分桶表底层就是MapReduce中分区
4、分桶和分区的区别
1)MapReduce的分区就是Hive的分桶
2)Hive的分桶就是MapReduce的分区
3)Hive的分区和MapReduce分区没什么关系
5、结论:分桶就是分文件
6、分桶的本质就是将大表进行拆分变成小表,小表好join

倾斜表

通过指定一个或者多个列经常出现的值(严重偏斜),Hive 会自动将涉及到这些值的数据拆分为单独的文件。在查询时,如果涉及到倾斜值,它就直接从独立文件中获取数据,而不是扫描所有文件,这使得性能得到提升。

1
2
3
4
5
6
7
8
9
10
11
12
CREATE EXTERNAL TABLE emp_skewed(
empno INT,
ename STRING,
job STRING,
mgr INT,
hiredate TIMESTAMP,
sal DECIMAL(7,2),
comm DECIMAL(7,2)
)
SKEWED BY (empno) ON (66,88,100) --指定 empno 的倾斜值 66,88,100
ROW FORMAT DELIMITED FIELDS TERMINATED BY "\t"
LOCATION '/hive/emp_skewed';

临时表

临时表仅对当前 session 可见,临时表的数据将存储在用户的暂存目录中,并在会话结束后删除。如果临时表与永久表表名相同,则对该表名的任何引用都将解析为临时表,而不是永久表。临时表还具有以下两个限制:

  • 不支持分区列;
  • 不支持创建索引。
1
2
3
4
5
6
7
8
9
10
CREATE TEMPORARY TABLE emp_temp(
empno INT,
ename STRING,
job STRING,
mgr INT,
hiredate TIMESTAMP,
sal DECIMAL(7,2),
comm DECIMAL(7,2)
)
ROW FORMAT DELIMITED FIELDS TERMINATED BY "\t";

CTAS创建表

支持从查询语句的结果创建表:

1
CREATE TABLE emp_copy AS SELECT * FROM emp WHERE deptno='20';

复制表结构

语法:

1
2
3
CREATE [TEMPORARY] [EXTERNAL] TABLE [IF NOT EXISTS] [db_name.]table_name  --创建表表名
LIKE existing_table_or_view_name --被复制表的表名
[LOCATION hdfs_path]; --存储位置

示例:

1
CREATE TEMPORARY EXTERNAL TABLE  IF NOT EXISTS  emp_co  LIKE emp

加载数据到表

加载数据到表中属于 DML 操作,这里为了方便大家测试,先简单介绍一下加载本地数据到表中:

1
2
3
4
5
6
7
8
9
10
11
-- 加载数据到 emp 表中
load data local inpath "/usr/file/emp.txt" into table emp;

#2、给内部表加载数据-从Linux本地-复制
#将Linux本地的文件复制到表 目录:/user/hive/warehouse/myhive.db/t_covid
load data local inpath '/root/test/covid19.dat' into table t_covid;

#2、给内部表加载数据-从HDFS加载-剪切
#将HDFS文件剪切到表目录:/user/hive/warehouse/myhive.db/t_covid
load data inpath '/input/covid19.dat' into table t_covid;

其中 emp.txt 的内容如下,你可以直接复制使用,也可以到本仓库的resources 目录下载:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
7369	SMITH	CLERK	7902	1980-12-17 00:00:00	800.00		20
7499 ALLEN SALESMAN 7698 1981-02-20 00:00:00 1600.00 300.00 30
7521 WARD SALESMAN 7698 1981-02-22 00:00:00 1250.00 500.00 30
7566 JONES MANAGER 7839 1981-04-02 00:00:00 2975.00 20
7654 MARTIN SALESMAN 7698 1981-09-28 00:00:00 1250.00 1400.00 30
7698 BLAKE MANAGER 7839 1981-05-01 00:00:00 2850.00 30
7782 CLARK MANAGER 7839 1981-06-09 00:00:00 2450.00 10
7788 SCOTT ANALYST 7566 1987-04-19 00:00:00 1500.00 20
7839 KING PRESIDENT 1981-11-17 00:00:00 5000.00 10
7844 TURNER SALESMAN 7698 1981-09-08 00:00:00 1500.00 0.00 30
7876 ADAMS CLERK 7788 1987-05-23 00:00:00 1100.00 20
7900 JAMES CLERK 7698 1981-12-03 00:00:00 950.00 30
7902 FORD ANALYST 7566 1981-12-03 00:00:00 3000.00 20
7934 MILLER CLERK 7782 1982-01-23 00:00:00 1300.00 10

加载后可查询表中数据:

image-20230117194928125

加载数据到表几种方式区分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
-- 如何给一张表加载数据
-- 1、创建表
drop table if exists myhive.score2;
create table if not exists myhive.score2
(
sid string,
cid string,
sscore int
)
row format delimited fields terminated by '\t';

-- 2、表加载数据
-- 方式1-insert into命令 #0颗星
insert into score2 values ('08','02',80),('09','02',80),('10','02',80);

-- 方式2-直接通过put命令将文件上传到表目录 #1颗星,测试用
hadoop fs -put score.txt /user/hive/warehouse/myhive.db/score
select * from score2;

-- 方式3-使用load命令加载数据 #4颗星,测试和生成都可以用
load data local inpath '/root/hive_data/test/score.txt' overwrite into table myhive.score2;

-- 方式4-使用insert into select .... #5颗星、保存结果
insert into score2
select * from score where sscore > 80;

-- 方式5-使用create table score5 as select * from score; #1颗星 测试用
-- 先创建表,表的字段和score字段相同,同时score表的数据插入到score3表
create table score3 as select * from score;

-- 方式6-使用第三方框架 #5颗星,生产环境
sqoop框架: MySQL/Oracle ===========> Hive表
Kettle框架: MySQL/Oracle ===========> Hive表

-- 方式7-HDFS先有数据,后有表 ,建表时使用location关键字
create external table t_covid2(
dt string comment '日期' ,
country string comment '县',
state string comment '州',
country_code string comment '县编码',
cases int comment '确诊人数',
deaths int comment '死亡任务'
)comment '美国新冠数据'
row format delimited fields terminated by ',' -- 自定字段之间的分隔符
location '/input/data';

查询的数据如何导出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
-- 方式1-使用命令导出到Linux本地目录中
-- 使用默认分隔符 '\001'
insert overwrite local directory '/root/hive_data/export_hive' select * from score where sscore > 85;

-- 手动指定分隔符 ','
insert overwrite local directory '/root/hive_data/export_hive'
row format delimited fields terminated by ','
select * from score where sscore > 85;

-- 方式2-使用命令导出到HDFS目录中(去掉local)
insert overwrite directory '/output/hive_data'
row format delimited fields terminated by ','
select * from score where sscore > 85;

-- 方式3-使用SQL导出到其他表 !!!!!!!!!!!!!!!!!!
insert overwrite into table 目标表
select 字段 from 原表 where 条件;

-- 方式4-使用第三方框架导出其他的存储平台(HBase、Spark、MySQL) !!!!!!!!!!!!
sqoop
Kettle
Datax
Presto

修改表

重命名表

语法:

1
ALTER TABLE table_name RENAME TO new_table_name;

示例:

1
ALTER TABLE emp_temp RENAME TO new_emp; --把 emp_temp 表重命名为 new_emp

修改列

语法:

1
2
ALTER TABLE table_name [PARTITION partition_spec] CHANGE [COLUMN] col_old_name col_new_name column_type
[COMMENT col_comment] [FIRST|AFTER column_name] [CASCADE|RESTRICT];

示例:

1
2
3
4
5
6
7
8
-- 修改字段名和类型
ALTER TABLE emp_temp CHANGE empno empno_new INT;

-- 修改字段 sal 的名称 并将其放置到 empno 字段后
ALTER TABLE emp_temp CHANGE sal sal_new decimal(7,2) AFTER ename;

-- 为字段增加注释
ALTER TABLE emp_temp CHANGE mgr mgr_new INT COMMENT 'this is column mgr';

新增列

示例:

1
ALTER TABLE emp_temp ADD COLUMNS (address STRING COMMENT 'home address');

清空表/删除表

清空表

语法:

1
2
-- 清空整个表或表指定分区中的数据
TRUNCATE TABLE table_name [PARTITION (partition_column = partition_col_value, ...)];
  • 目前只有内部表才能执行 TRUNCATE 操作,外部表执行时会抛出异常 Cannot truncate non-managed table XXXX

示例:

1
TRUNCATE TABLE emp_mgt_ptn PARTITION (deptno=20);

删除表

语法:

1
DROP TABLE [IF EXISTS] table_name [PURGE]; 
  • 内部表:不仅会删除表的元数据,同时会删除 HDFS 上的数据;
  • 外部表:只会删除表的元数据,不会删除 HDFS 上的数据;
  • 删除视图引用的表时,不会给出警告(但视图已经无效了,必须由用户删除或重新创建)。

其他命令

Describe

查看数据库:

1
DESCRIBE|Desc DATABASE [EXTENDED] db_name;  --EXTENDED 是否显示额外属性

查看表:

1
2
3
4
5
6
7
8
9
10
11
12
DESCRIBE|Desc [EXTENDED|FORMATTED] table_name --FORMATTED 以友好的展现方式查看表详情

desc t_team_ace_player; -- 只展示表字段,简单
desc formatted t_team_ace_player; -- 展示表字段和其他详细信息


#如何判断一张表是内部表还是外部表,通过元数据查看
desc formatted t_covid;
Table Type: ,EXTERNAL_TABLE #外部表
Table Type: ,MANAGED_TABLE #内部表

# Partition Infomation => 显示的是分区字段信息

Show

1. 查看数据库列表

1
2
3
4
5
-- 语法
SHOW (DATABASES|SCHEMAS) [LIKE 'identifier_with_wildcards'];

-- 示例:
SHOW DATABASES like 'hive*';

LIKE 子句允许使用正则表达式进行过滤,但是 SHOW 语句当中的 LIKE 子句只支持 *(通配符)和 |(条件或)两个符号。例如 employeesemp *emp * | * ees,所有这些都将匹配名为 employees 的数据库。

2. 查看表的列表

1
2
3
4
5
-- 语法
SHOW TABLES [IN database_name] ['identifier_with_wildcards'];

-- 示例
SHOW TABLES IN default; -- in 指定在哪个库查,default是默认库

3. 查看视图列表

1
SHOW VIEWS [IN/FROM database_name] [LIKE 'pattern_with_wildcards'];   --仅支持 Hive 2.2.0 +

4. 查看表的分区列表

1
SHOW PARTITIONS table_name; 

5. 查看表/视图的创建语句

1
SHOW CREATE TABLE ([db_name.]table_name|view_name);

Hive 分区表和分桶表

MR的分区 就是hive的分桶

hive的分桶就是MR的分区

hive的分区和MR分区没有关系

hive中分桶就是分文件,分区就是分目录

分区表

概念

Hive 中的表对应为 HDFS 上的指定目录,在查询数据时候,默认会对全表进行扫描,这样时间和性能的消耗都非常大。

分区为 HDFS 上表目录的子目录,数据按照分区存储在子目录中。如果查询的 where 字句的中包含分区条件,则直接从该分区去查找,而不是扫描整个表目录,合理的分区设计可以极大提高查询速度和性能。

这里说明一下分区表并非 Hive 独有的概念,实际上这个概念非常常见。比如在我们常用的 Oracle 数据库中,当表中的数据量不断增大,查询数据的速度就会下降,这时也可以对表进行分区。表进行分区后,逻辑上表仍然是一张完整的表,只是将表中的数据存放到多个表空间(物理文件上),这样查询数据时,就不必要每次都扫描整张表,从而提升查询性能。

使用场景

通常,在管理大规模数据集的时候都需要进行分区,比如将日志文件按天进行分区,从而保证数据细粒度的划分,使得查询性能得到提升。

创建分区表

在 Hive 中可以使用 PARTITIONED BY 子句创建分区表。表可以包含一个或多个分区列,程序会为分区列中的每个不同值组合创建单独的数据目录。下面的我们创建一张雇员表作为测试:

1
2
3
4
5
6
7
8
9
10
11
12
CREATE EXTERNAL TABLE emp_partition(
empno INT,
ename STRING,
job STRING,
mgr INT,
hiredate TIMESTAMP,
sal DECIMAL(7,2),
comm DECIMAL(7,2)
)
PARTITIONED BY (deptno INT) -- 按照部门编号进行分区
ROW FORMAT DELIMITED FIELDS TERMINATED BY "\t"
LOCATION '/hive/emp_partition';

加载数据到分区表

加载数据到分区表时候必须要指定数据所处的分区:

1
2
3
4
# 加载部门编号为20的数据到表中
LOAD DATA LOCAL INPATH "/usr/file/emp20.txt" OVERWRITE INTO TABLE emp_partition PARTITION (deptno=20)
# 加载部门编号为30的数据到表中
LOAD DATA LOCAL INPATH "/usr/file/emp30.txt" OVERWRITE INTO TABLE emp_partition PARTITION (deptno=30)

查看分区目录

这时候我们直接查看表目录,可以看到表目录下存在两个子目录,分别是 deptno=20deptno=30,这就是分区目录,分区目录下才是我们加载的数据文件。

1
# hadoop fs -ls  hdfs://hadoop001:8020/hive/emp_partition/

这时候当你的查询语句的 where 包含 deptno=20,则就去对应的分区目录下进行查找,而不用扫描全表。

image-20220809124042571

静态分区

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
#静态分区就是手动来操作分区的文件夹

#----------------------单级分区-一级文件夹------------------------------

-- 1、数据样例
/*
2021-01-28,Autauga,Alabama,01001,5554,69
2021-01-28,Baldwin,Alabama,01003,17779,225
2021-01-28,Barbour,Alabama,01005,1920,40
2021-01-28,Bibb,Alabama,01007,2271,51
2021-01-28,Blount,Alabama,01009,5612,98
2021-01-29,Bullock,Alabama,01011,1079,29
2021-01-29,Butler,Alabama,01013,1788,60
2021-01-29,Calhoun,Alabama,01015,11833,231
2021-01-29,Chambers,Alabama,01017,3159,76
2021-01-29,Cherokee,Alabama,01019,1682,35
2021-01-30,Chilton,Alabama,01021,3523,79
2021-01-30,Choctaw,Alabama,01023,525,24
2021-01-30,Clarke,Alabama,01025,3150,38
2021-01-30,Clay,Alabama,01027,1319,50
*/

-- 2、创建分区表 (这里做测试 外部表内部表都行)
drop table t_covid;
create external table t_covid(
dt_value string ,
country string ,
state string ,
country_code string ,
cases int ,
deaths int
)
partitioned by (dt string) -- 这里用来指定分区的字段,字段名字可以随便写
row format delimited fields terminated by ',' -- 自定字段之间的分隔符
;

-- 3、给分区表加载数据
load data local inpath '/root/hive_data/covid-28.dat' into table t_covid partition (dt='2021-01-28');
load data local inpath '/root/hive_data/covid-29.dat' into table t_covid partition (dt='2021-01-29');
load data local inpath '/root/hive_data/covid-30.dat' into table t_covid partition (dt='2021-01-30');


-- 4、查看分区表数据
select * from t_covid; --查看所有分区数据

select * from t_covid where dt='2021-01-28'; -- 查询指定单个分区的数据
select * from t_covid where dt='2021-01-28' or dt='2021-01-29' ; -- 查询指定多个分区的数据


#----------------------单级分区-多级文件夹------------------------------
-- 1、数据样例
/*
2021-02-28,Cleburne,Alabama,01029,1258,28
2021-02-28,Coffee,Alabama,01031,4795,72
2021-02-28,Colbert,Alabama,01033,5686,104
2021-02-28,Conecuh,Alabama,01035,999,23
2021-02-28,Coosa,Alabama,01037,670,19
2021-02-28,Covington,Alabama,01039,3504,87
2021-02-28,Crenshaw,Alabama,01041,1279,47
2021-02-28,Cullman,Alabama,01043,8466,145、

2021-02-29,Dale,Alabama,01045,4235,92
2021-02-29,Dallas,Alabama,01047,3181,108
2021-02-29,DeKalb,Alabama,01049,8052,130
2021-02-29,Elmore,Alabama,01051,8449,131

2021-02-30,Escambia,Alabama,01053,3478,47
2021-02-30,Etowah,Alabama,01055,12359,228
2021-02-30,Fayette,Alabama,01057,1841,37
2021-02-30,Franklin,Alabama,01059,3829,55
2021-02-30,Geneva,Alabama,01061,2205,51
*/

-- 2、创建分区表
drop table t_covid2;
create table t_covid2(
dt_value string ,
country string ,
state string ,
country_code string ,
cases int ,
deaths int
)
partitioned by (month string,dt string) -- 这里用来指定分区的字段,字段名字可以随便写
row format delimited fields terminated by ',' -- 自定字段之间的分隔符
;

-- 3、给分区表加载数据
-- 1月份数据

load data local inpath '/root/hive_data/covid-28.dat' into table t_covid2
partition (month='2021-01',dt='2021-01-28');

load data local inpath '/root/hive_data/covid-29.dat' into table t_covid2
partition (month='2021-01',dt='2021-01-29');

load data local inpath '/root/hive_data/covid-30.dat' into table t_covid2
partition (month='2021-01',dt='2021-01-30');



-- 2月份数据
load data local inpath '/root/hive_data/2_month/covid-28.dat' into table t_covid2
partition (month='2021-02', dt='2021-02-28');


load data local inpath '/root/hive_data/2_month/covid-29.dat' into table t_covid2
partition (month='2021-02', dt='2021-02-29');

load data local inpath '/root/hive_data/2_month/covid-30.dat' into table t_covid2
partition (month='2021-02', dt='2021-02-30');



-- 4、查询数据
select * from t_covid2; -- 查询所有分区

select * from t_covid2 where month = '2021-02'; -- 查询2月份数据

select * from t_covid2 where month = '2021-02' and dt = '2021-02-28'; -- 查询2月28号份数据

-- 手动添加分区
alter table t_covid2 add partition(month='2021-03',dt='2021-03-28');

注意:添加分区之后就可以在hdfs文件系统当中看到表下面多了一个文件夹

动态分区

1
#动态分区就是Hive可以根据数据本身的特点来自动创建分区文件夹

image-20230413045353100

  • 实例1-单级分区
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
-- 0、样例数据
/*
1 2022-01-01 zhangsan 80
2 2022-01-01 lisi 70
3 2022-01-01 wangwu 90
1 2022-01-02 zhangsan 90
2 2022-01-02 lisi 65
3 2022-01-02 wangwu 96
1 2022-01-03 zhangsan 91
2 2022-01-03 lisi 66
3 2022-01-03 wangwu 96
*/
-- 1、设置参数
set hive.exec.dynamic.partition=true; -- 开启动态分区的功能
set hive.exec.dynamic.partition.mode=nonstrict; -- 设置非严格模式


-- 2、创建普通非分区表
use myhive;
create table myhive.test1
(
id int,
date_val string,
name string,
score int
)
row format delimited fields terminated by '\t';
;

-- 3、给普通非分区表加载数据
load data local inpath '/root/hive_data/dynamic_partition/a.txt' into table test1;
select * from test1;

-- 4、创建最终的分区表
drop table test2;
create table myhive.test2
(
id int,
name string,
score int
)
partitioned by (dt string)
row format delimited fields terminated by '\t';
;

-- date_val作为分区pt字段插入
-- overwrite 覆盖插入

-- 5、将普通表的数据查询插入到最终分区表
insert overwrite table test2
select id,name,score,date_val from test1; -- 分区字段一定要放在select的最后


-- 6、数据查询
select * from test2;
select * from test2 where dt='2022-01-01';
select * from test2 where dt='2022-01-02';
select * from test2 where dt='2022-01-03';
  • 实例2-多级分区
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
-- 0、样例数据
/*
1 2022-01-01 zhangsan m 80
2 2022-01-01 lisi m 70
3 2022-01-01 wangwu f 90
1 2022-01-02 zhangsan f 90
2 2022-01-02 lisi f 65
3 2022-01-02 wangwu m 96
1 2022-01-03 zhangsan f 91
2 2022-01-03 lisi m 66
3 2022-01-03 wangwu m 96
*/

-- 1、创建普通表
drop table if exists test3;
create table test3
(
id int,
date_val string,
name string,
sex string,
score int
)
row format delimited fields terminated by '\t';
;
-- 2、给普通表加载数据
load data local inpath '/root/hive_data/dynamic_partition/b.txt' overwrite into table test3;
select * from test3;

-- 3、创建最终的分区表
drop table test4;
create table test4
(
id int,
name string,
score int
)
partitioned by (dt string,gender string)
row format delimited fields terminated by '\t';
;
-- 5、将普通表的数据查询插入到最终分区表
insert overwrite table test4
select id,name,score,date_val,sex from test3; -- 分区的两个字段一定要放在最后

-- 6、查询
select * from test4;
select * from test4 where dt='2022-01-01' and gender='f';
select * from test4 where gender='f';
  • 实例3-自定义分区
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
-- 0、样例数据
/*
2021-01-28,Autauga,Alabama,01001,5554,69
2021-01-28,Baldwin,Alabama,01003,17779,225
2021-01-28,Barbour,Alabama,01005,1920,40
2021-01-28,Bibb,Alabama,01007,2271,51
2021-01-28,Blount,Alabama,01009,5612,98
2021-01-29,Bullock,Alabama,01011,1079,29
2021-01-29,Butler,Alabama,01013,1788,60
2021-01-29,Calhoun,Alabama,01015,11833,231
2021-01-29,Chambers,Alabama,01017,3159,76
2021-01-29,Cherokee,Alabama,01019,1682,35
2021-01-30,Chilton,Alabama,01021,3523,79
2021-01-30,Choctaw,Alabama,01023,525,24
2021-01-30,Clarke,Alabama,01025,3150,38
2021-01-30,Clay,Alabama,01027,1319,50
2021-02-28,Cleburne,Alabama,01029,1258,28
2021-02-28,Coffee,Alabama,01031,4795,72
2021-02-28,Colbert,Alabama,01033,5686,104
2021-02-28,Conecuh,Alabama,01035,999,23
2021-02-28,Coosa,Alabama,01037,670,19
2021-02-28,Covington,Alabama,01039,3504,87
2021-02-28,Crenshaw,Alabama,01041,1279,47
2021-02-28,Cullman,Alabama,01043,8466,145、
2021-02-29,Dale,Alabama,01045,4235,92
2021-02-29,Dallas,Alabama,01047,3181,108
2021-02-29,DeKalb,Alabama,01049,8052,130
2021-02-29,Elmore,Alabama,01051,8449,131
2021-02-30,Escambia,Alabama,01053,3478,47
2021-02-30,Etowah,Alabama,01055,12359,228
2021-02-30,Fayette,Alabama,01057,1841,37
2021-02-30,Franklin,Alabama,01059,3829,55
2021-02-30,Geneva,Alabama,01061,2205,51
2022-02-28,Geneva,Alabama,01061,2205,51
2022-02-29,Geneva,Alabama,01061,2205,51
2022-02-30,Geneva,Alabama,01061,2205,51
*/
-- 1、创建普通表
drop table if exists test5;
create table test5
(
dt_value string ,
country string ,
state string ,
country_code string ,
cases int ,
deaths int
)
row format delimited fields terminated by ',';
;
-- 2、给普通表加载数据
load data local inpath '/root/hive_data/dynamic_partition/c.txt' overwrite into table test5;
select * from test5;

-- 3、创建最终的分区表
drop table test6;
create table test6
(
country string ,
state string ,
country_code string ,
cases int ,
deaths int
)
partitioned by (year string,month string, dt string)
row format delimited fields terminated by ',';
;
-- 5、将普通表的数据查询插入到最终分区表
/*
year=2022
month=01
dt=28
*/
insert overwrite table test6
select country,state,country_code,cases,deaths,
substring(dt_value,1,4), -- 2022
substring(dt_value,6,2), -- 0
substring(dt_value,-2,2)
from test5; -- 分区的两个字段一定要放在最后



-- 6、查询
select * from test6;
select * from test6 where year='2021' and month ='01';

分桶表

分桶就是将数据划分到不同的文件,其实就是MapReduce的分区

将数据按照指定的字段进行分成多个桶中去,说白了就是将数据按照字段进行划分,可以将数据按照字段划分到多个文件当中去

作用:

1、提高Join的效率
2、用于数据的抽样(了解)

如果2张表,没有处理的话,关联的时候A表的字段会去全表扫描B表,效率很低。而分桶后,扫描桶相同的,扫描数据量少了,效率高了。

image-20230226040749882

桶相同的进行join,就避免了全部扫描是否匹配,提高了效率。

ps: 分桶要可以排序,更加高效

image-20230226040754544

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
-- 0、样例数据
新冠数据:covid19.dat
-- 1、开启分桶功能
set hive.enforce.bucketing=true;

-- 2、创建普通表
drop table t_covid_common;
create table t_covid_common
(
dt_value string ,
country string ,
state string ,
country_code string ,
cases int ,
deaths int
) row format delimited fields terminated by ',';

-- 3、给普通表加载数据
load data local inpath '/root/hive_data/covid19.dat' overwrite into table t_covid_common;
select * from t_covid_common;

-- 4、创建分桶表
drop table t_covid_bucket;
create table t_covid_bucket
(
dt_value string ,
country string ,
state string ,
country_code string ,
cases int ,
deaths int
)
clustered by(country_code) into 5 buckets -- country_code就是MapReduce分区中K2
row format delimited fields terminated by ',';


-- 5、查询普通表给分桶表加载数据
insert overwrite table t_covid_bucket
select * from t_covid_common cluster by(country_code);

-- 可以在SQL的前边加上explain,查看SQL的执行计划

简介

分区提供了一个隔离数据和优化查询的可行方案,但是并非所有的数据集都可以形成合理的分区,分区的数量也不是越多越好,过多的分区条件可能会导致很多分区上没有数据。同时 Hive 会限制动态分区可以创建的最大分区数,用来避免过多分区文件对文件系统产生负担。鉴于以上原因,Hive 还提供了一种更加细粒度的数据拆分方案:分桶表 (bucket Table)。

分桶表会将指定列的值进行哈希散列,并对 bucket(桶数量)取余,然后存储到对应的 bucket(桶)中。

理解分桶表

单从概念上理解分桶表可能会比较晦涩,其实和分区一样,分桶这个概念同样不是 Hive 独有的,对于 Java 开发人员而言,这可能是一个每天都会用到的概念,因为 Hive 中的分桶概念和 Java 数据结构中的 HashMap 的分桶概念是一致的。

当调用 HashMap 的 put() 方法存储数据时,程序会先对 key 值调用 hashCode() 方法计算出 hashcode,然后对数组长度取模计算出 index,最后将数据存储在数组 index 位置的链表上,链表达到一定阈值后会转换为红黑树 (JDK1.8+)。下图为 HashMap 的数据结构图:

image-20220809124057501

图片引用自:HashMap vs. Hashtable

创建分桶表

在 Hive 中,我们可以通过 CLUSTERED BY 指定分桶列,并通过 SORTED BY 指定桶中数据的排序参考列。下面为分桶表建表语句示例:

1
2
3
4
5
6
7
8
9
10
11
12
CREATE EXTERNAL TABLE emp_bucket(
empno INT,
ename STRING,
job STRING,
mgr INT,
hiredate TIMESTAMP,
sal DECIMAL(7,2),
comm DECIMAL(7,2),
deptno INT)
CLUSTERED BY(empno) SORTED BY(empno ASC) INTO 4 BUCKETS --按照员工编号散列到四个 bucket 中
ROW FORMAT DELIMITED FIELDS TERMINATED BY "\t"
LOCATION '/hive/emp_bucket';

加载数据到分桶表

这里直接使用 Load 语句向分桶表加载数据,数据时可以加载成功的,但是数据并不会分桶。

这是由于分桶的实质是对指定字段做了 hash 散列然后存放到对应文件中,这意味着向分桶表中插入数据是必然要通过 MapReduce,且 Reducer 的数量必须等于分桶的数量。由于以上原因,分桶表的数据通常只能使用 CTAS(CREATE TABLE AS SELECT) 方式插入,因为 CTAS 操作会触发 MapReduce。加载数据步骤如下:

1、设置强制分桶

1
set hive.enforce.bucketing = true; --Hive 2.x 不需要这一步

在 Hive 0.x and 1.x 版本,必须使用设置 hive.enforce.bucketing = true,表示强制分桶,允许程序根据表结构自动选择正确数量的 Reducer 和 cluster by column 来进行分桶。

2、CTAS导入数据

1
INSERT INTO TABLE emp_bucket SELECT *  FROM emp;  --这里的 emp 表就是一张普通的雇员表

可以从执行日志看到 CTAS 触发 MapReduce 操作,且 Reducer 数量和建表时候指定 bucket 数量一致:

image-20220809124240660

查看分桶文件

bucket(桶) 本质上就是表目录下的具体文件:

image-20220809124400881

分区表和分桶表结合使用

分区表和分桶表的本质都是将数据按照不同粒度进行拆分,从而使得在查询时候不必扫描全表,只需要扫描对应的分区或分桶,从而提升查询效率。两者可以结合起来使用,从而保证表数据在不同粒度上都能得到合理的拆分。下面是 Hive 官方给出的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
CREATE TABLE page_view_bucketed(
viewTime INT,
userid BIGINT,
page_url STRING,
referrer_url STRING,
ip STRING )
PARTITIONED BY(dt STRING)
CLUSTERED BY(userid) SORTED BY(viewTime) INTO 32 BUCKETS
ROW FORMAT DELIMITED
FIELDS TERMINATED BY '\001'
COLLECTION ITEMS TERMINATED BY '\002'
MAP KEYS TERMINATED BY '\003'
STORED AS SEQUENCEFILE;

此时导入数据时需要指定分区:

1
2
3
INSERT OVERWRITE page_view_bucketed
PARTITION (dt='2009-02-25')
SELECT * FROM page_view WHERE dt='2009-02-25';

一般前期采集分区,后续数据分析查询慢时分桶

Hive 视图和索引

视图

简介

Hive 中的视图和 RDBMS 中视图的概念一致,都是一组数据的逻辑表示,本质上就是一条 SELECT 语句的结果集。视图是纯粹的逻辑对象,没有关联的存储 (Hive 3.0.0 引入的物化视图除外),当查询引用视图时,Hive 可以将视图的定义与查询结合起来,例如将查询中的过滤器推送到视图中。

创建视图

1
2
3
4
5
CREATE VIEW [IF NOT EXISTS] [db_name.]view_name   -- 视图名称
[(column_name [COMMENT column_comment], ...) ] --列名
[COMMENT view_comment] --视图注释
[TBLPROPERTIES (property_name = property_value, ...)] --额外信息
AS SELECT ...;

在 Hive 中可以使用 CREATE VIEW 创建视图,如果已存在具有相同名称的表或视图,则会抛出异常,建议使用 IF NOT EXISTS 预做判断。在使用视图时候需要注意以下事项:

  • 视图是只读的,不能用作 LOAD / INSERT / ALTER 的目标;

  • 在创建视图时候视图就已经固定,对基表的后续更改(如添加列)将不会反映在视图;

  • 删除基表并不会删除视图,需要手动删除视图;

  • 视图可能包含 ORDER BY 和 LIMIT 子句。如果引用视图的查询语句也包含这类子句,其执行优先级低于视图对应字句。例如,视图 custom_view 指定 LIMIT 5,查询语句为 select * from custom_view LIMIT 10,此时结果最多返回 5 行。

  • 创建视图时,如果未提供列名,则将从 SELECT 语句中自动派生列名;

  • 创建视图时,如果 SELECT 语句中包含其他表达式,例如 x + y,则列名称将以_C0,_C1 等形式生成;

    1
    CREATE VIEW  IF NOT EXISTS custom_view AS SELECT empno, empno+deptno , 1+2 FROM emp;

    image-20220809124706522

查看视图

1
2
3
4
5
6
-- 查看所有视图: 没有单独查看视图列表的语句,只能使用 show tables
show tables;
-- 查看某个视图
desc view_name;
-- 查看某个视图详细信息
desc formatted view_name;

删除视图

1
DROP VIEW [IF EXISTS] [db_name.]view_name;

删除视图时,如果被删除的视图被其他视图所引用,这时候程序不会发出警告,但是引用该视图其他视图已经失效,需要进行重建或者删除。

修改视图

1
ALTER VIEW [db_name.]view_name AS select_statement;

被更改的视图必须存在,且视图不能具有分区,如果视图具有分区,则修改失败。

修改视图属性

语法:

1
2
3
4
ALTER VIEW [db_name.]view_name SET TBLPROPERTIES table_properties;

table_properties:
: (property_name = property_value, property_name = property_value, ...)

示例:

1
ALTER VIEW custom_view SET TBLPROPERTIES ('create'='heibaiying','date'='2019-05-05');

image-20220809124814508

索引

简介

Hive 在 0.7.0 引入了索引的功能,索引的设计目标是提高表某些列的查询速度。如果没有索引,带有谓词的查询(如’WHERE table1.column = 10’)会加载整个表或分区并处理所有行。但是如果 column 存在索引,则只需要加载和处理文件的一部分。

索引原理

在指定列上建立索引,会产生一张索引表(表结构如下),里面的字段包括:索引列的值、该值对应的 HDFS 文件路径、该值在文件中的偏移量。在查询涉及到索引字段时,首先到索引表查找索引列值对应的 HDFS 文件路径及偏移量,这样就避免了全表扫描。

1
2
3
4
5
6
7
+--------------+----------------+----------+--+
| col_name | data_type | comment |
+--------------+----------------+----------+--+
| empno | int | 建立索引的列 |
| _bucketname | string | HDFS 文件路径 |
| _offsets | array<bigint> | 偏移量 |
+--------------+----------------+----------+--+

创建索引

1
2
3
4
5
6
7
8
9
10
11
12
13
CREATE INDEX index_name     --索引名称
ON TABLE base_table_name (col_name, ...) --建立索引的列
AS index_type --索引类型
[WITH DEFERRED REBUILD] --重建索引
[IDXPROPERTIES (property_name=property_value, ...)] --索引额外属性
[IN TABLE index_table_name] --索引表的名字
[
[ ROW FORMAT ...] STORED AS ...
| STORED BY ...
] --索引表行分隔符 、 存储格式
[LOCATION hdfs_path] --索引表存储位置
[TBLPROPERTIES (...)] --索引表表属性
[COMMENT "index comment"]; --索引注释

查看索引

1
2
--显示表上所有列的索引
SHOW FORMATTED INDEX ON table_name;

删除索引

删除索引会删除对应的索引表。

1
DROP INDEX [IF EXISTS] index_name ON table_name;

如果存在索引的表被删除了,其对应的索引和索引表都会被删除。如果被索引表的某个分区被删除了,那么分区对应的分区索引也会被删除。

重建索引

1
ALTER INDEX index_name ON table_name [PARTITION partition_spec] REBUILD;

重建索引。如果指定了 PARTITION,则仅重建该分区的索引。

索引案例

创建索引

在 emp 表上针对 empno 字段创建名为 emp_index,索引数据存储在 emp_index_table 索引表中

1
2
3
4
create index emp_index on table emp(empno) as  
'org.apache.hadoop.hive.ql.index.compact.CompactIndexHandler'
with deferred rebuild
in table emp_index_table ;

此时索引表中是没有数据的,需要重建索引才会有索引的数据。

重建索引

1
alter index emp_index on emp rebuild; 

Hive 会启动 MapReduce 作业去建立索引,建立好后查看索引表数据如下。三个表字段分别代表:索引列的值、该值对应的 HDFS 文件路径、该值在文件中的偏移量。

image-20220809124952588

自动使用索引

默认情况下,虽然建立了索引,但是 Hive 在查询时候是不会自动去使用索引的,需要开启相关配置。开启配置后,涉及到索引列的查询就会使用索引功能去优化查询。

1
2
3
SET hive.input.format=org.apache.hadoop.hive.ql.io.HiveInputFormat;
SET hive.optimize.index.filter=true;
SET hive.optimize.index.filter.compact.minsize=0;

查看索引

1
SHOW INDEX ON emp;

image-20220809125026389

索引的缺陷

索引表最主要的一个缺陷在于:索引表无法自动 rebuild,这也就意味着如果表中有数据新增或删除,则必须手动 rebuild,重新执行 MapReduce 作业,生成索引表数据。

同时按照官方文档 的说明,Hive 会从 3.0 开始移除索引功能,主要基于以下两个原因:

  • 具有自动重写的物化视图 (Materialized View) 可以产生与索引相似的效果(Hive 2.3.0 增加了对物化视图的支持,在 3.0 之后正式引入)。
  • 使用列式存储文件格式(Parquet,ORC)进行存储时,这些格式支持选择性扫描,可以跳过不需要的文件或块。

ORC 内置的索引功能可以参阅这篇文章:Hive 性能优化之 ORC 索引–Row Group Index vs Bloom Filter Index

Hive 常用 DML 操作

在Hive中建表成功之后,就会在HDFS上创建一个与之对应的文件夹,且文件夹名字就是表名;

文件夹父路径是由参数hive.metastore.warehouse.dir控制,默认值是/user/hive/warehouse

image-20230119045601199

我们可以在web端直接暴力上传但是命令行操作是要会的。(不推荐)

我们可以用hadoop的上传命令,将数据化数据文件上传到hive的对应库对应表目录下。但是这样相当于跳过了hive,直接使用hadoop命令了。(不推荐)

1
hadoop fs -put 1.txt /user/hive/warehouse/hive_test.db/t_1

官方推荐使用hive的LOAD加载数据。(推荐)

加载文件数据到表

语法:

1
2
3
4
5
6
7
8
LOAD DATA [LOCAL] INPATH 'filepath' [OVERWRITE] 
INTO TABLE tablename [PARTITION (partcol1=val1, partcol2=val2 ...)]

-- 从本地加载数据 数据位于HS2(node1)本地文件系统 本质是hadoop fs -put上传操作
LOAD DATA LOCAL INPATH '/opt/data/hive3_data/students.txt' INTO TABLE student_local;
--从HDFS加载数据 数据位于HDFS文件系统根目录下 本质是hadoop fs -mv 移动操作
--先把数据上传到HDFS上 hadoop fs -put /root/hivedata/students.txt /
LOAD DATA INPATH '/students.txt' INTO TABLE student_HDFS;
  • LOCAL 关键字代表从本地(hive服务所在机器的本地)文件系统加载文件,省略则代表从 HDFS 上加载文件:
  • 从本地文件系统加载文件时, filepath 可以是绝对路径也可以是相对路径 (建议使用绝对路径);

  • 从 HDFS 加载文件时候,filepath 为文件完整的 URL 地址:如 hdfs://namenode:port/user/hive/project/ data1

  • filepath 可以是文件路径 (在这种情况下 Hive 会将文件移动到表中),也可以目录路径 (在这种情况下,Hive 会将该目录中的所有文件移动到表中);

  • 如果使用 OVERWRITE 关键字,则将删除目标表(或分区)的内容,使用新的数据填充;不使用此关键字,则数据以追加的方式加入;

  • 加载的目标可以是表或分区。如果是分区表,则必须指定加载数据的分区;

  • 加载文件的格式必须与建表时使用 STORED AS 指定的存储格式相同。

使用建议:

不论是本地路径还是 URL 都建议使用完整的。虽然可以使用不完整的 URL 地址,此时 Hive 将使用 hadoop 中的 fs.default.name 配置来推断地址,但是为避免不必要的错误,建议使用完整的本地路径或 URL 地址;

加载对象是分区表时建议显示指定分区。在 Hive 3.0 之后,内部将加载 (LOAD) 重写为 INSERT AS SELECT,此时如果不指定分区,INSERT AS SELECT 将假设最后一组列是分区列,如果该列不是表定义的分区,它将抛出错误。为避免错误,还是建议显示指定分区。

示例:

新建分区表:

1
2
3
4
5
6
7
8
9
10
11
CREATE TABLE emp_ptn(
empno INT,
ename STRING,
job STRING,
mgr INT,
hiredate TIMESTAMP,
sal DECIMAL(7,2),
comm DECIMAL(7,2)
)
PARTITIONED BY (deptno INT) -- 按照部门编号进行分区
ROW FORMAT DELIMITED FIELDS TERMINATED BY "\t";

从 HDFS 上加载数据到分区表:

1
LOAD DATA  INPATH "hdfs://hadoop001:8020/mydir/emp.txt" OVERWRITE INTO TABLE emp_ptn PARTITION (deptno=20);

emp.txt 文件可在本仓库的 resources 目录中下载

加载后表中数据如下,分区列 deptno 全部赋值成 20:

image-20230531095211671

beenline执行加载可以看到加载过程的日志,学习时可以使用beenline客户端看一看

查询结果插入到表

在hive中,用标准insert语法插入,可以,但是非常不推荐。hive中insert into走的是MR,插入1条测试下来都需要45s。直接把结构化数据准备好,LOAD插入它不香吗。

语法:

1
2
3
4
5
INSERT OVERWRITE TABLE tablename1 [PARTITION (partcol1=val1, partcol2=val2 ...) [IF NOT EXISTS]]   
select_statement1 FROM from_statement;

INSERT INTO TABLE tablename1 [PARTITION (partcol1=val1, partcol2=val2 ...)]
select_statement1 FROM from_statement;
  • Hive 0.13.0 开始,建表时可以通过使用 TBLPROPERTIES(“immutable”=“true”)来创建不可变表 (immutable table) ,如果不可以变表中存在数据,则 INSERT INTO 失败。(注:INSERT OVERWRITE 的语句不受 immutable 属性的影响);

  • 可以对表或分区执行插入操作。如果表已分区,则必须通过指定所有分区列的值来指定表的特定分区;

  • 从 Hive 1.1.0 开始,TABLE 关键字是可选的;

  • 从 Hive 1.2.0 开始 ,可以采用 INSERT INTO tablename(z,x,c1) 指明插入列;

  • 可以将 SELECT 语句的查询结果插入多个表(或分区),称为多表插入。语法如下:

    1
    2
    3
    4
    5
    FROM from_statement
    INSERT OVERWRITE TABLE tablename1
    [PARTITION (partcol1=val1, partcol2=val2 ...) [IF NOT EXISTS]] select_statement1
    [INSERT OVERWRITE TABLE tablename2 [PARTITION ... [IF NOT EXISTS]] select_statement2]
    [INSERT INTO TABLE tablename2 [PARTITION ...] select_statement2] ...;

动态插入分区:

1
2
3
4
5
INSERT OVERWRITE TABLE tablename PARTITION (partcol1[=val1], partcol2[=val2] ...) 
select_statement FROM from_statement;

INSERT INTO TABLE tablename PARTITION (partcol1[=val1], partcol2[=val2] ...)
select_statement FROM from_statement;

在向分区表插入数据时候,分区列名是必须的,但是列值是可选的。如果给出了分区列值,我们将其称为静态分区,否则它是动态分区。动态分区列必须在 SELECT 语句的列中最后指定,并且与它们在 PARTITION() 子句中出现的顺序相同。

注意:Hive 0.9.0 之前的版本动态分区插入是默认禁用的,而 0.9.0 之后的版本则默认启用。以下是动态分区的相关配置:

配置 默认值 说明
hive.exec.dynamic.partition true 需要设置为 true 才能启用动态分区插入
hive.exec.dynamic.partition.mode strict 在严格模式 (strict) 下,用户必须至少指定一个静态分区,以防用户意外覆盖所有分区,在非严格模式下,允许所有分区都是动态的
hive.exec.max.dynamic.partitions.pernode 100 允许在每个 mapper/reducer 节点中创建的最大动态分区数
hive.exec.max.dynamic.partitions 1000 允许总共创建的最大动态分区数
hive.exec.max.created.files 100000 作业中所有 mapper/reducer 创建的 HDFS 文件的最大数量
hive.error.on.empty.partition false 如果动态分区插入生成空结果,是否抛出异常

示例:

  1. 新建 emp 表,作为查询对象表
1
2
3
4
5
6
7
8
9
10
11
12
13
CREATE TABLE emp(
empno INT,
ename STRING,
job STRING,
mgr INT,
hiredate TIMESTAMP,
sal DECIMAL(7,2),
comm DECIMAL(7,2),
deptno INT)
ROW FORMAT DELIMITED FIELDS TERMINATED BY "\t";

-- 加载数据到 emp 表中 这里直接从本地加载
load data local inpath "/usr/file/emp.txt" into table emp;

​ 完成后 emp 表中数据如下:

image-20230531095227841

  1. 为清晰演示,先清空 emp_ptn 表中加载的数据:
1
TRUNCATE TABLE emp_ptn;
  1. 静态分区演示:从 emp 表中查询部门编号为 20 的员工数据,并插入 emp_ptn 表中,语句如下:
1
2
INSERT OVERWRITE TABLE emp_ptn PARTITION (deptno=20) 
SELECT empno,ename,job,mgr,hiredate,sal,comm FROM emp WHERE deptno=20;

​ 完成后 emp_ptn 表中数据如下:

image-20230117223328521

  1. 接着演示动态分区:
1
2
3
4
5
6
-- 由于我们只有一个分区,且还是动态分区,所以需要关闭严格默认。因为在严格模式下,用户必须至少指定一个静态分区
set hive.exec.dynamic.partition.mode=nonstrict;

-- 动态分区 此时查询语句的最后一列为动态分区列,即 deptno
INSERT OVERWRITE TABLE emp_ptn PARTITION (deptno)
SELECT empno,ename,job,mgr,hiredate,sal,comm,deptno FROM emp WHERE deptno=30;

​ 完成后 emp_ptn 表中数据如下:

image-20230117223345493

insert+select需要保证查询结果列的数目和需要插入数据表格的列数目一致,如果查询出来的数据类型和插入表格对应的列数据类型不一致,将会进行转换,但是不能保证转换一定成功,转换失败的数据将会为NULL。

使用SQL语句插入值

1
2
INSERT INTO TABLE tablename [PARTITION (partcol1[=val1], partcol2[=val2] ...)] 
VALUES ( value [, value ...] )
  • 使用时必须为表中的每个列都提供值。不支持只向部分列插入值(可以为缺省值的列提供空值来消除这个弊端);
  • 如果目标表表支持 ACID 及其事务管理器,则插入后自动提交;
  • 不支持支持复杂类型 (array, map, struct, union) 的插入。

更新和删除数据

语法:

更新和删除的语法比较简单,和关系型数据库一致。需要注意的是这两个操作都只能在支持 ACID 的表,也就是事务表上才能执行。

1
2
3
4
5
-- 更新
UPDATE tablename SET column = value [, column = value ...] [WHERE expression]

--删除
DELETE FROM tablename [WHERE expression]

示例:

1. 修改配置

首先需要更改 hive-site.xml,添加如下配置,开启事务支持,配置完成后需要重启 Hive 服务。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<property>
<name>hive.support.concurrency</name>
<value>true</value>
</property>
<property>
<name>hive.enforce.bucketing</name>
<value>true</value>
</property>
<property>
<name>hive.exec.dynamic.partition.mode</name>
<value>nonstrict</value>
</property>
<property>
<name>hive.txn.manager</name>
<value>org.apache.hadoop.hive.ql.lockmgr.DbTxnManager</value>
</property>
<property>
<name>hive.compactor.initiator.on</name>
<value>true</value>
</property>
<property>
<name>hive.in.test</name>
<value>true</value>
</property>

2. 创建测试表

创建用于测试的事务表,建表时候指定属性 transactional = true 则代表该表是事务表。需要注意的是,按照官方文档 的说明,目前 Hive 中的事务表有以下限制:

  • 必须是 buckets Table;
  • 仅支持 ORC 文件格式;
  • 不支持 LOAD DATA …语句。
1
2
3
4
5
6
CREATE TABLE emp_ts(  
empno int,
ename String
)
CLUSTERED BY (empno) INTO 2 BUCKETS STORED AS ORC
TBLPROPERTIES ("transactional"="true");

3. 插入测试数据

1
INSERT INTO TABLE emp_ts  VALUES (1,"ming"),(2,"hong"); -- 不推荐,主要是对已有数据分析,而不是造数据

插入数据依靠的是 MapReduce 作业,执行成功后数据如下:

image-20230117223445319

4. 测试更新和删除

1
2
3
4
5
--更新数据
UPDATE emp_ts SET ename = "lan" WHERE empno=1;

--删除数据
DELETE FROM emp_ts WHERE empno=2;

更新和删除数据依靠的也是 MapReduce 作业,执行成功后数据如下:

image-20230531095244904

查询结果写出到文件系统

语法:

1
2
3
INSERT OVERWRITE [LOCAL] DIRECTORY directory1
[ROW FORMAT row_format] [STORED AS file_format]
SELECT ... FROM ...
  • OVERWRITE 关键字表示输出文件存在时,先删除后再重新写入;

  • 和 Load 语句一样,建议无论是本地路径还是 URL 地址都使用完整的;

  • 写入文件系统的数据被序列化为文本,其中列默认由^A 分隔,行由换行符分隔。如果列不是基本类型,则将其序列化为 JSON 格式。其中行分隔符不允许自定义,但列分隔符可以自定义,如下:

    1
    2
    3
    4
    5
    6
    7
    -- 定义列分隔符为'\t' 
    insert overwrite local directory './test-04'
    row format delimited
    FIELDS TERMINATED BY '\t'
    COLLECTION ITEMS TERMINATED BY ','
    MAP KEYS TERMINATED BY ':'
    select * from src;

示例:

这里我们将上面创建的 emp_ptn 表导出到本地文件系统,语句如下:

1
2
3
4
INSERT OVERWRITE LOCAL DIRECTORY '/usr/file/ouput'
ROW FORMAT DELIMITED
FIELDS TERMINATED BY '\t'
SELECT * FROM emp_ptn;

导出结果如下:

image-20230117223501404

Hive 常用 DQL 操作

使用hive SQL本质就是分析数据,数据分析最重要的就是查询

数据准备

为了演示查询操作,这里需要预先创建三张表,并加载测试数据。

数据文件 emp.txt 和 dept.txt 可以从本仓库的resources 目录下载。

员工表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 -- 建表语句
CREATE TABLE emp(
empno INT, -- 员工表编号
ename STRING, -- 员工姓名
job STRING, -- 职位类型
mgr INT,
hiredate TIMESTAMP, --雇佣日期
sal DECIMAL(7,2), --工资
comm DECIMAL(7,2),
deptno INT) --部门编号
ROW FORMAT DELIMITED FIELDS TERMINATED BY "\t";

--加载数据
LOAD DATA LOCAL INPATH "/usr/file/emp.txt" OVERWRITE INTO TABLE emp;

部门表

1
2
3
4
5
6
7
8
9
10
-- 建表语句
CREATE TABLE dept(
deptno INT, --部门编号
dname STRING, --部门名称
loc STRING --部门所在的城市
)
ROW FORMAT DELIMITED FIELDS TERMINATED BY "\t";

--加载数据
LOAD DATA LOCAL INPATH "/usr/file/dept.txt" OVERWRITE INTO TABLE dept;

分区表

这里需要额外创建一张分区表,主要是为了演示分区查询:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
CREATE EXTERNAL TABLE emp_ptn(
empno INT,
ename STRING,
job STRING,
mgr INT,
hiredate TIMESTAMP,
sal DECIMAL(7,2),
comm DECIMAL(7,2)
)
PARTITIONED BY (deptno INT) -- 按照部门编号进行分区
ROW FORMAT DELIMITED FIELDS TERMINATED BY "\t";


--加载数据
LOAD DATA LOCAL INPATH "/usr/file/emp.txt" OVERWRITE INTO TABLE emp_ptn PARTITION (deptno=20)
LOAD DATA LOCAL INPATH "/usr/file/emp.txt" OVERWRITE INTO TABLE emp_ptn PARTITION (deptno=30)
LOAD DATA LOCAL INPATH "/usr/file/emp.txt" OVERWRITE INTO TABLE emp_ptn PARTITION (deptno=40)
LOAD DATA LOCAL INPATH "/usr/file/emp.txt" OVERWRITE INTO TABLE emp_ptn PARTITION (deptno=50)

单表查询

在查询过程中的执行顺序

from > where > group(含聚合)> having >order > select

SELECT

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
-- 查询表中全部数据
SELECT * FROM emp;

-- 指定字段查询
select county, cases, deaths from t_usa_covid19;

select state, cases + 10 as new_cases
from t_covid; -- 指定列别名
select state, cases + 10 new_cases
from t_covid; -- 指定列别名 省略 as

/*
1、在Hive的一些版本中,当使用聚合函数来统计时,发现SQL语句没有返回返回任何结果
2、因为Hive默认是去MySQL的元数据中获取文件的行数、但是元数据中默认行数都是0
3、我们需要设置一个参数,不让SQL去元数据中获取行数,而是执行这条SQL对文件进行出来,自己来统计行数
*/
set hive.compute.query.using.stats=false; -- 默认聚合从元数据查询记录数,设置false,不让SQL去元数据中获取行数,而是统计实际的记录数

-- 开启Hive的本地模式,加快查询速度
set hive.stats.column.autogather=false;
set hive.exec.mode.local.auto=true; --开启本地mr


-- 3、聚合函数
-- 1)求总行数(count)
select count(*) from t_covid; -- 求表的总行数,只要有一列不为NULL,则统计
select count(1) from t_covid; -- 求表的总行数,只要有一列不为NULL,则统计
select count(state) from t_covid; -- 求state列有多少行,不会统计NULL值

--2)求分数的最大值(max)
select max(cases) max_cases from t_covid;

-- 3)求分数的最小值(min)
select min(cases) min_cases from t_covid;

-- 4)求分数的总和(sum)
select sum(cases) total_cases from t_covid;

-- 5)求分数的平均值(avg)
select round(avg(cases), 2) from t_covid;


-- 4、Limit 分区查询
select * from t_covid;
select * from t_covid limit 3; -- 查询前3条
select * from t_covid limit 3,2; -- 从索引3开始先显示,显示2条 ,索引从0开始

嵌套查询可以用with写法,看着更加清晰

WHERE

1
2
3
4
5
-- 查询 10 号部门中员工编号大于 7782 的员工信息 
SELECT * FROM emp WHERE empno > 7782 AND deptno = 10;

-- where条件中使用函数 找出州名字母长度超过10位的有哪些
select * from t_usa_covid19 where length(state) >10 ;

注意:where条件中不能使用聚合函数

1
2
3
4
5
6
-- 报错: SemanticException:Not yet supported place for UDAF ‘sum'
--聚合函数要使用它的前提是结果集已经确定。
--而where子句还处于“确定”结果集的过程中,因而不能使用聚合函数。
select state,sum(deaths) from t_usa_covid19 where sum(deaths) >100 group by state;
--可以使用Having实现
select state,sum(deaths) from t_usa_covid19 group by state having sum(deaths) > 100;

where 中还可以使用运算符,控制判断(is null)、between and、in等

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
select *
from t_covid
where deaths between 1000 and 2000; -- [1000,2000] 包含左边和右边

select *
from t_covid
where deaths between 1000 and 2000; -- [1000,2000] 包含左边和右边


select *
from t_covid
where state = 'Alaska'
or state = 'New Mexico';
select *
from t_covid
where state in ('Alaska', 'New Mexico');
-- 和上边等价


-- 查询州名以S开头的疫情数据
select * from t_covid where state like 'S%';
-- 查询州名包含s的疫情数据
select * from t_covid where state like '%s%';

select * from t_covid where state rlike '[s]'; -- 正则
-- 做用同上


-- 查询确诊病例数大于50000,同时死亡病例数大于1000的信息
select *
from t_covid
where cases >= 50000
and deaths >= 1000;
-- 查询阿拉斯加州和新墨西哥州的疫情数据
select *
from t_covid
where state = 'Alaska'
or state = 'New Mexico';

-- 查询除了阿拉斯加州和新墨西哥州以外的疫情数据
select *
from t_covid
where state not in ('Alaska', 'New Mexico');

select *
from t_covid
where deaths not between 1000 and 2000;

DISTINCT

Hive 支持使用 DISTINCT 关键字去重。

1
2
3
4
5
-- 查询所有工作类型
SELECT DISTINCT job FROM emp;

-- 多字段去重
SELECT DISTINCT job1,job2 FROM emp; -- 可能存在job1可能有多个,对应不同的job2的这种情况。

分区查询

分区查询 (Partition Based Queries),可以指定某个分区或者分区范围。

1
2
3
-- 查询分区表中部门编号在[20,40]之间的员工
SELECT emp_ptn.* FROM emp_ptn
WHERE emp_ptn.deptno >= 20 AND emp_ptn.deptno <= 40;

LIMIT

实际工作中,对于陌生的表,可能有几亿行数据,你直接select *,那就有的乐了。看你终端能不能接收,服务器缓存会不会崩等等。

dataGrip,默认会对select *对分页处理。YYDS

1
2
3
4
5
6
-- 查询薪资最高的 5 名员工
SELECT * FROM emp ORDER BY sal DESC LIMIT 5;

-- 返回结果集从第1行开始 共3行
select * from t_usa_covid19 where count_date = "2021-01-28" and state ="California" limit 2,3;
-- 注意 第一个参数偏移量是从0开始的

GROUP BY

Hive 支持使用 GROUP BY 进行分组聚合操作。

1
2
3
4
set hive.map.aggr=true;

-- 查询各个部门薪酬综合
SELECT deptno,SUM(sal) FROM emp GROUP BY deptno; -- 分组之后,select的后边,只能跟分组字段和聚合函数

hive.map.aggr 控制程序如何进行聚合。默认值为 false。如果设置为 true,Hive 会在 map 阶段就执行一次聚合。这可以提高聚合效率,但需要消耗更多内存。

ORDER AND SORT

可以使用 ORDER BY 或者 Sort BY 对查询结果进行排序,排序字段可以是整型也可以是字符串:如果是整型,则按照大小排序;如果是字符串,则按照字典序排序。ORDER BY 和 SORT BY 的区别如下:

  • 使用 ORDER BY 时会有一个 Reducer 对全部查询结果进行排序,可以保证数据的全局有序性;
  • 使用 SORT BY 时只会在每个 Reducer 中进行排序,这可以保证每个 Reducer 的输出数据是有序的,但不能保证全局有序。

由于 ORDER BY 的时间可能很长,如果你设置了严格模式 (hive.mapred.mode = strict),则其后面必须再跟一个 limit 子句。

注 :hive.mapred.mode 默认值是 nonstrict ,也就是非严格模式。

1
2
-- 查询员工工资,结果按照部门升序,按照工资降序排列
SELECT empno, deptno, sal FROM emp ORDER BY deptno ASC, sal DESC;

HAVING

可以使用 HAVING 对分组数据进行过滤。

1
2
3
4
5
-- 查询工资总和大于 9000 的所有部门
SELECT deptno,SUM(sal) FROM emp GROUP BY deptno HAVING SUM(sal)>9000;

-- 可以使用别名:在group by的时候聚合函数已经作用得出结果 having直接引用结果过滤 不需要再单独计算一次了
select state,sum(deaths) as cnts from t_usa_covid19 where count_date = "2021-01-28" group by state having cnts> 10000;

DISTRIBUTE BY

如果想要把具有相同 Key 值的数据分发到同一个 Reducer 进行处理,这可以使用 DISTRIBUTE BY 字句。需要注意的是,DISTRIBUTE BY 虽然能把具有相同 Key 值的数据分发到同一个 Reducer,但是不能保证数据在 Reducer 上是有序的。情况如下:

把以下 5 个数据发送到两个 Reducer 上进行处理:

1
2
3
4
5
k1
k2
k4
k3
k1

Reducer1 得到如下乱序数据:

1
2
3
k1
k2
k1

Reducer2 得到数据如下:

1
2
k4
k3

如果想让 Reducer 上的数据是有序的,可以结合 SORT BY 使用 (示例如下),或者使用下面我们将要介绍的 CLUSTER BY。

1
2
-- 将数据按照部门分发到对应的 Reducer 上处理
SELECT empno, deptno, sal FROM emp DISTRIBUTE BY deptno SORT BY deptno ASC;

distribute by 对数据进行分区, sort by 对每个分区进行排序

排序好像不是很严谨(hash冲突可能分到同一个区),后续有更好用的开窗函数

CLUSTER BY

如果 SORT BYDISTRIBUTE BY 指定的是相同字段,且 SORT BY 排序规则是 ASC,此时可以使用 CLUSTER BY 进行替换,同时 CLUSTER BY 可以保证数据在全局是有序的。

1
SELECT empno, deptno, sal FROM emp CLUSTER  BY deptno ;

多表联结查询

Hive 支持内连接,外连接,左外连接,右外连接,笛卡尔连接,这和传统数据库中的概念是一致的,可以参见下图。

需要特别强调:JOIN 语句的关联条件必须用 ON 指定,不能用 WHERE 指定,否则就会先做笛卡尔积,再过滤,这会导致你得不到预期的结果 (下面的演示会有说明)。

image-20220809125929908

INNER JOIN

INNER JOIN 和 JOIN 和 '表a,表b where 关联条件'(隐式连接)是等价的

1
2
3
4
5
6
7
8
9
10
11
12
-- 查询员工编号为 7369 的员工的详细信息
SELECT
e.*,d.*
FROM
emp e JOIN dept d ON e.deptno = d.deptno -- 左右两边表的deptno都相等才行
WHERE empno=7369;

--如果是三表或者更多表连接,语法如下
SELECT a.val, b.val, c.val
FROM a
JOIN b ON (a.key = b.key1)
JOIN c ON (c.key = b.key1)

LEFT OUTER JOIN

LEFT OUTER JOIN(早期写法) 和 LEFT JOIN 是等价的。

1
2
3
4
-- 左连接 : 左表为主表,关联不上的显示null返回
SELECT e.*,d.*
FROM emp e LEFT OUTER JOIN dept d
ON e.deptno = d.deptno;

RIGHT OUTER JOIN

1
2
3
4
--右连接
SELECT e.*,d.*
FROM emp e RIGHT OUTER JOIN dept d
ON e.deptno = d.deptno;

执行右连接后,由于 40 号部门下没有任何员工,所以此时员工信息为 NULL。这个查询可以很好的复述上面提到的——JOIN 语句的关联条件必须用 ON 指定,不能用 WHERE 指定。你可以把 ON 改成 WHERE,你会发现无论如何都查不出 40 号部门这条数据,因为笛卡尔运算不会有 (NULL, 40) 这种情况。

image-20220809125943650

FULL OUTER JOIN

1
2
3
SELECT e.*,d.*
FROM emp e
FULL OUTER JOIN dept d ON e.deptno = d.deptno;

LEFT SEMI JOIN

LEFT SEMI JOIN (左半连接)是 IN/EXISTS 子查询的一种更高效的实现。

  • JOIN 子句中右边的表只能在 ON 子句中设置过滤条件;
  • 查询结果只包含左边表的数据,所以只能 SELECT 左表中的列。
1
2
3
4
5
6
7
8
-- 查询在纽约办公的所有员工信息
SELECT emp.*
FROM emp LEFT SEMI JOIN dept
ON emp.deptno = dept.deptno AND dept.loc="NEW YORK";

--上面的语句就等价于
SELECT emp.* FROM emp
WHERE emp.deptno IN (SELECT deptno FROM dept WHERE loc="NEW YORK");

JOIN

笛卡尔积连接,这个连接日常的开发中可能很少遇到,且性能消耗比较大,基于这个原因,如果在严格模式下 (hive.mapred.mode = strict),Hive 会阻止用户执行此操作。

1
SELECT * FROM emp JOIN dept;

JOIN优化

STREAMTABLE

在多表进行联结的时候,如果每个 ON 字句都使用到共同的列(如下面的 b.key),此时 Hive 会进行优化,将多表 JOIN 在同一个 map / reduce 作业上进行。同时假定查询的最后一个表(如下面的 c 表)是最大的一个表,在对每行记录进行 JOIN 操作时,它将尝试将其他的表缓存起来,然后扫描最后那个表进行计算。因此用户需要保证查询的表的大小从左到右是依次增加的。

1
`SELECT a.val, b.val, c.val FROM a JOIN b ON (a.key = b.key) JOIN c ON (c.key = b.key)`

然后,用户并非需要总是把最大的表放在查询语句的最后面,Hive 提供了 /*+ STREAMTABLE() */ 标志,用于标识最大的表,示例如下:

1
2
3
4
SELECT /*+ STREAMTABLE(d) */  e.*,d.* 
FROM emp e JOIN dept d
ON e.deptno = d.deptno
WHERE job='CLERK';

MAPJOIN

如果所有表中只有一张表是小表,那么 Hive 把这张小表加载到内存中。这时候程序会在 map 阶段直接拿另外一个表的数据和内存中表数据做匹配,由于在 map 就进行了 JOIN 操作,从而可以省略 reduce 过程,这样效率可以提升很多。Hive 中提供了 /*+ MAPJOIN() */ 来标记小表,示例如下:

1
2
3
4
SELECT /*+ MAPJOIN(d) */ e.*,d.* 
FROM emp e JOIN dept d
ON e.deptno = d.deptno
WHERE job='CLERK';

SELECT的其他用途

查看当前数据库:

1
SELECT current_database()

本地模式

在上面演示的语句中,大多数都会触发 MapReduce, 少部分不会触发,比如 select * from emp limit 5 就不会触发 MR,此时 Hive 只是简单的读取数据文件中的内容,然后格式化后进行输出。在需要执行 MapReduce 的查询中,你会发现执行时间可能会很长,这时候你可以选择开启本地模式。

1
2
--本地模式默认关闭,需要手动开启此功能
SET hive.exec.mode.local.auto=true;

启用后,Hive 将分析查询中每个 map-reduce 作业的大小,如果满足以下条件,则可以在本地运行它:

  • 作业的总输入大小低于:hive.exec.mode.local.auto.inputbytes.max(默认为 128MB);
  • map-tasks 的总数小于:hive.exec.mode.local.auto.tasks.max(默认为 4);
  • 所需的 reduce 任务总数为 1 或 0。

因为我们测试的数据集很小,所以你再次去执行上面涉及 MR 操作的查询,你会发现速度会有显著的提升。

Hive 函数

介绍

Hive内建了不少函数,用于满足用户不同使用需求,提高SQL编写效率:

  1. 使用show functions查看当下可用的所有函数;
  2. 通过describe function extended funcname来查看函数的使用方式。

Hive的函数分为两大类:内置函数(Built-in Functions)、用户定义函数UDF(User-Defined Functions):

  • 内置函数可分为:数值类型函数、日期类型函数、字符串类型函数、集合函数、条件函数等;

  • 用户定义函数根据输入输出的行数可分为3类:UDF、UDAF、UDTF

用户定义函数UDF分类标准

  • UDF(User-Defined-Function)普通函数,一进一出
  • UDAF(User-Defined Aggregation Function)聚合函数,多进一出
  • UDTF(User-Defined Table-Generating Functions)表生成函数,一进多出

image-20230119065841160

UDF分类标准扩大化

UDF分类标准本来针对的是用户自己编写开发实现的函数。UDF分类标准可以扩大到Hive的所有函数中:包括内置函数和用户自定义函数。

因为不管是什么类型的函数,一定满足于输入输出的要求,那么从输入几行和输出几行上来划分没有任何问题。

千万不要被UD(User-Defined)这两个字母所迷惑,照成视野的狭隘。

比如Hive官方文档中,针对聚合函数的标准就是内置的UDAF类型。

自定义UDF

编程步骤:

(1)继承org.apache.hadoop.hive.ql.exec.UDF

(2)需要实现evaluate函数;evaluate函数支持重载

注意:

(1)UDF必须要有返回类型,可以返回null,但是返回类型不能为void;

(2)UDF中常用Text/LongWritable等类型,不推荐使用java类型;

代码:

  • pom
1
2
3
4
5
6
7
8
9
10
11
12
<dependencies>
<dependency>
<groupId>org.apache.hive</groupId>
<artifactId>hive-exec</artifactId>
<version>3.1.2</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-common</artifactId>
<version>3.1.4</version>
</dependency>
</dependencies>
  • 开发java类继承UDF,并重载evaluate 方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class MyUDF  extends UDF {
//17801112345 ---> 178****2345
public String evaluate(String phoneNumStr){
//匹配手机号是否合法
String regex = "1[35789][0-9]{9}";
boolean flag = phoneNumStr.matches(regex);
if (!flag){
return null;
}else{
String str1 = phoneNumStr.substring(0,3);
String str2 = phoneNumStr.substring(7);

return str1 + "****"+str2;
}
}
  • 临时函数

将我们的项目打包,并上传到hive的lib目录下,并重命名我们的jar包名称

1
2
cd /export/server/hive/lib
mv day19_udf-1.0-SNAPSHOT.jar my_udf.jar

hive客户端添加jar

1
hive> add jar /export/server/hive/lib/my_udf.jar

设置函数与我们的自定义函数关联-临时函数

1
hive> create temporary function my_jiami as 'cn.itcast.udf.MyUDF';

使用自定义函数

1
hive>select my_jiami(phone_num) from test_user;
  • 永久函数
1
2
3
4
5
6
7
8
1. 把自定义函数的jar上传到hdfs中.
hadoop fs -mkdir /hive_func
hadoop fs -put my_udf.jar /hive_func
2. 创建永久函数
hive> create function my_jiami2 as 'cn.itcast.udf.MyUDF'
using jar 'hdfs://node1:8020/hive_func/my_udf.jar';
3. 验证
hive>select my_jiami2(phone_num) from test_user;
  • 删除函数
1
2
3
4
-- 删除临时函数 
drop temporary function if exists encryptPhoneNumber;
-- 删除永久函数,不会删除HDFS上的jar包
drop function if exists my_lower2;

自定义UDTF

自定义一个UDTF,实现将一个任意分隔符的字符串切割成独立的单词,例如:

1
2
3
4
5
6
7
8
源数据:
"zookeeper,hadoop,hdfs,hive,MapReduce"
目标数据:
zookeeper
hadoop
hdfs
hive
MapReduce
  • 代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
import org.apache.hadoop.hive.ql.exec.UDFArgumentException;
import org.apache.hadoop.hive.ql.metadata.HiveException;
import org.apache.hadoop.hive.ql.udf.generic.GenericUDTF;
import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspector;
import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspectorFactory;
import org.apache.hadoop.hive.serde2.objectinspector.StructObjectInspector;
import org.apache.hadoop.hive.serde2.objectinspector.primitive.PrimitiveObjectInspectorFactory;

import java.util.ArrayList;
import java.util.List;
import java.util.function.ObjDoubleConsumer;

public class MyUDTF extends GenericUDTF {
private final transient Object[] forwardListObj = new Object[1];

// 执行一次,定义UDTF一进多出,每列的名字和类型
@Override
public StructObjectInspector initialize(StructObjectInspector argOIs) throws UDFArgumentException {
//设置列名的类型
List<String> fieldNames = new ArrayList<>();
//设置输出的列名
fieldNames.add("column_01");
List<ObjectInspector> fieldOIs = new ArrayList<ObjectInspector>() ;//检查器列表

//设置输出的列的值类型
fieldOIs.add(PrimitiveObjectInspectorFactory.javaStringObjectInspector);

return ObjectInspectorFactory.getStandardStructObjectInspector(fieldNames, fieldOIs);

}

@Override
public void process(Object[] objects) throws HiveException {
// 用法:自定义函数(参数1,分隔符)
//1:获取原始数据
String args = objects[0].toString();
//2:获取数据传入的第二个参数,此处为分隔符
String splitKey = objects[1].toString();
//3.将原始数据按照传入的分隔符进行切分
String[] fields = args.split(splitKey);
//4:遍历切分后的结果,并写出
for (String field : fields) {
//将每一个单词添加值对象数组
forwardListObj[0] = field;
//将对象数组内容写出
forward(forwardListObj);
}

}

@Override
public void close() throws HiveException {

}
}

添加我们的jar包

1
2
3
4
5
6
7
8
9
10
11
12
将打包的jar包上传到node3主机/export/data/hive-2.1.0/lib目录,并重命名我们的jar包名称
cd /export/data/hive/lib
mv original-day_10_hive_udtf-1.0-SNAPSHOT.jar my_udtf.jar

hive的客户端添加我们的jar包,将jar包添加到hive的classpath下
hive> add jar /export/server/hive/lib/my_udtf.jar;

创建临时函数与开发后的udtf代码关联
hive>create temporary function my_udtf as 'cn.itcast.udf.MyUDTF';

使用自定义udtf函数
hive>select my_udtf("zookeeper,hadoop,hdfs,hive,MapReduce",",") word;

字符串函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
------------String Functions 字符串函数------------
select length("zuoer"); -- 5
select reverse("zuoer"); -- reouz

select concat("angela","baby");
--带分隔符字符串连接函数:concat_ws(separator, [string | array(string)]+)
select concat_ws('.', 'www', array('nbchen', 'com'));

--字符串截取函数:substr(str, pos[, len]) 或者 substring(str, pos[, len])
select substr("angelababy",-2); --pos是从1开始的索引,如果为负数则倒着数
select substr("angelababy",2,2);

-- 字符串转大写
select upper('abCDE');
select ucase('abCDE');
-- 字符串转小写
select lower('abCDE');
select lcase('abCDE');

-- 去除空格
select trim(' abc ');
select ltrim(' abc ');
select rtrim(' abc ');

-- 正则替换
select regexp_replace('foobar','oo|ar','');

--分割字符串函数: split(str, regex)
--split针对字符串数据进行切割 返回是数组array 可以通过数组的下标取内部的元素 注意下标从0开始的
select split('apache hive', ' ');
select split('apache hive', ' ')[0];
select split('apache hive', ' ')[1];

-- URL路径解析
select parse_url('https://www.baidu.com/info/s?word=bigdata&tn=25017023_2_pg', 'HOST'); -- www.baidu.com
select parse_url('https://www.baidu.com/info/s?word=bigdata&tn=25017023_2_pg', 'PATH'); -- /info/s
select parse_url('https://www.baidu.com/info/s?word=bigdata&tn=25017023_2_pg', 'QUERY'); -- word=bigdata&tn=25017023_2_pg
select parse_url('https://www.baidu.com/info/s?word=bigdata&tn=25017023_2_pg', 'QUERY','word'); -- bigdata
select parse_url('https://www.baidu.com/info/s?word=bigdata&tn=25017023_2_pg', 'QUERY','tn'); -- 25017023_2_pg

-- json数据解析
select get_json_object('{"name": "zhangsan","age": 18, "preference": "music"}', '$.name');
select get_json_object('{"name": "zhangsan","age": 18, "preference": "music"}', '$.age');
select get_json_object('{"name": {"aaa":"bbb"}}', '$.name.aaa');

日期函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
----------- Date Functions 日期函数 -----------------
--获取当前日期: current_date
select current_date();
--获取当前UNIX时间戳函数: unix_timestamp
select unix_timestamp();
--日期转UNIX时间戳函数: unix_timestamp
select unix_timestamp("2011-12-07 13:01:03");
--指定格式日期转UNIX时间戳函数: unix_timestamp
select unix_timestamp('20111207 13:01:03','yyyyMMdd HH:mm:ss');
--UNIX时间戳转日期函数: from_unixtime
select from_unixtime(1618238391);
select from_unixtime(0, 'yyyy-MM-dd HH:mm:ss');
select from_unixtime(unix_timestamp('2023年4月13日 13点01分03秒','yyyy年MM月dd日 HH点mm分ss秒'), 'yyyy-MM-dd HH:mm:ss');

--日期比较函数: datediff 日期格式要求'yyyy-MM-dd HH:mm:ss' or 'yyyy-MM-dd'
select datediff('2012-12-08','2012-05-09');
--日期增加函数: date_add
select date_add('2012-02-28',10);
--日期减少函数: date_sub
select date_sub('2012-01-1',10);


# date_format处理的是时间字符串,from_unixtime处理的是unix时间戳
select date_format('2020-1-1 1:1:1', 'yyyy-MM-dd HH:mm:ss'); -- 2020-01-01 01:01:01

-- to_date 返回日期时间字段中的日期部分
select to_date('2011-12-08 10:03:01'); -- 2011-12-08
-- year 返回日期中的年
select year('2011-12-08 10:03:01'); --2011
-- month 返回日期中的月份
select month('2011-12-08 10:03:01');-- 12
-- quarter 返回日期中的季度
select quarter('2011-12-08 10:03:01');-- 4
-- day 返回日期中的天
select day('2011-12-08 10:03:01');-- 8
-- weekofyear 返回日期在当前的周数
select weekofyear('2011-12-08 10:03:01'); --49

数学函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
----Mathematical Functions 数学函数-------------
--取整函数: round 返回double类型的整数值部分 (遵循四舍五入)
select round(3.1415926);
--指定精度取整函数: round(double a, int d) 返回指定精度d的double类型
select round(3.1415926,4);
--取随机数函数: rand 每次执行都不一样 返回一个0到1范围内的随机数
select rand();
--指定种子取随机数函数: rand(int seed) 得到一个稳定的随机数序列
select rand(3);


-- 向下取整
select floor(3.9);

-- 向上取整
select ceil(3.1);

-- 取随机数
select floor((rand() * 100) + 1); -- 1到100之间的随机数

-- 绝对值
select abs(-12);

-- 几次方运算
select pow(2,4);

条件函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
-----Conditional Functions 条件函数------------------
select * from student limit 3;

--if条件判断: if(boolean testCondition, T valueTrue, T valueFalseOrNull)
select if(1=2,100,200);
select if(sex ='男','M','W') from student limit 3;

--空值转换函数: nvl(T value, T default_value)
select nvl("allen","itcast");
select nvl(null,"itcast");

--条件转换函数: CASE a WHEN b THEN c [WHEN d THEN e]* [ELSE f] END
select case 100 when 50 then 'tom' when 100 then 'mary' else 'tim' end;
select case sex when '男' then 'male' else 'female' end from student limit 3;

转换函数

1
2
3
4
5
6
7
8
9
cast(表达式 as 数据类型) 类似java中的强转
可以将"20190607"这样类型的时间数据转化成int类型数据。

select cast(12.35 as int);
select cast('20190607' as int);
select cast('2020-12-05' as date);

# 转为字符串
select cast(12.35 as string);

行转列

1、行转列是指多行数据转换为一个列的字段。
2、Hive行转列用到的函数:

1
2
3
4
5
6
7
8
9
10
11
concat(str1,str2,...)  --字段或字符串拼接
concat_ws(sep, str1,str2) --以分隔符拼接每个字符串
collect_set(col)/collect_list(col) --将某字段的值进行去重汇总,产生array类型字段

行转多列:
select name,
max(case when subject = '数学' then score else null end) math,
max(case when subject='英语' then score else null end) english,
max(case when subject='语文' then score else null end) chinese
from t1
group by name;

image-20230413064151458

  • 测试数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
字段: deptno  	ename 
20 SMITH
30 ALLEN
30 WARD
20 JONES
30 MARTIN
30 BLAKE
10 CLARK
20 SCOTT
10 KING
30 TURNER
20 ADAMS
30 JAMES
20 FORD
10 MILLER
# 建表
create table emp(
deptno int,
ename string
) row format delimited fields terminated by '\t';
# 插入数据
load data local inpath "/export/data/hivedatas/emp.txt" into table emp;
# 转换
select deptno,concat_ws("|",collect_set(ename)) as ems from emp group by deptno;

行转列,COLLECT_SET**(col):函数只接受基本数据类型,它的主要作用是将某字段的值进行去重汇总,产生array**类型字段。

image-20230226062505754

列转行(表生成函数)

  • explode函数

explode(col):将hive一列中复杂的array或者map结构拆分成多行。

explode(ARRAY) 数组的每个元素生成一行

explode(MAP) map中每个key-value对,生成一行,key为一列,value为一列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 数据
10 CLARK|KING|MILLER
20 SMITH|JONES|SCOTT|ADAMS|FORD
30 ALLEN|WARD|MARTIN|BLAKE|TURNER|JAMES
# 建表
create table emp2(
deptno int,
names array<string>
)
row format delimited fields terminated by '\t'
collection items terminated by '|';
# 插入数据
load data local inpath '/export/data/hivedatas/emp2.txt' into table emp2;
# 查询数据
select * from emp;

image-20230226061751055

1
select explode(names) as name from emp;

image-20230226061745522

  • LATERAL VIEW侧视图

用法:LATERAL VIEW udtf(expression) tableAlias AS columnAlias
解释:用于和split, explode等UDTF一起使用,它能够将一列数据拆成多行数据,在此基础上可以对拆分后的数据进行聚合。

1
select deptno,name from emp2 lateral view explode(names) tmp_tb as name;

image-20230226061831960

  • Reflect

reflect函数可以支持在sql中调用java中的静态方法

使用java.lang.Math当中的Max求两列中最大值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
--创建hive表
create table test_reflect(col1 int,col2 int) row format delimited fields terminated by ',';

--准备数据 test_reflect.txt
1,2
4,3
6,4
7,5
5,6

--加载数据

load data local inpath '/export/data/hivedatas/test_reflect.txt' into table test_udf;

--使用java.lang.Math当中的Max求两列当中的最大值
select reflect("java.lang.Math","max",col1,col2) from test_udf;

开窗函数

ROW_NUMBER,RANK,DENSE_RANK => 既有明细,又可以分组

  • 准备数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
cookie1,2018-04-10,1
cookie1,2018-04-11,5
cookie1,2018-04-12,7
cookie1,2018-04-13,3
cookie1,2018-04-14,2
cookie1,2018-04-15,4
cookie1,2018-04-16,4
cookie2,2018-04-10,2
cookie2,2018-04-11,3
cookie2,2018-04-12,5
cookie2,2018-04-13,6
cookie2,2018-04-14,3
cookie2,2018-04-15,9
cookie2,2018-04-16,7

CREATE TABLE test_window_func1(
cookieid string,
createtime string, --day
pv INT
) ROW FORMAT DELIMITED FIELDS TERMINATED BY ',';

-- 加载数据:
load data local inpath '/export/data/hivedatas/test1.txt' into table test_window_func1;
  • ROW_NUMBER
1
2
3
4
5
6
7
ROW_NUMBER()  从1开始,按照顺序,生成分组内记录的序列
SELECT
cookieid,
createtime,
pv,
ROW_NUMBER() OVER(PARTITION BY cookieid ORDER BY pv desc) AS rn
FROM itcast_t1;
  • RANK 和 DENSE_RANK
1
2
3
4
5
6
7
8
9
10
11
RANK() 生成数据项在分组中的排名,排名相等会在名次中留下空位
DENSE_RANK() 生成数据项在分组中的排名,排名相等会在名次中不会留下空位
SELECT
cookieid,
createtime,
pv,
RANK() OVER(PARTITION BY cookieid ORDER BY pv desc) AS rn1,
DENSE_RANK() OVER(PARTITION BY cookieid ORDER BY pv desc) AS rn2,
ROW_NUMBER() OVER(PARTITION BY cookieid ORDER BY pv DESC) AS rn3
FROM itcast_t1
WHERE cookieid = 'cookie1';

SUM,AVG,MIN,MAX

从那几行加到那几行,挺好用的

可以做到累加,历史平均,历史最差,历史最好等等效果

  • 数据准备
1
2
3
4
5
6
7
8
9
10
11
12
13
--建表语句:
create table test_window_func2(
cookieid string,
createtime string, --day
pv int
) row format delimited
fields terminated by ',';

--加载数据:
load data local inpath '/export/data/hivedatas/test1.txt' into table test_window_func2;

--开启智能本地模式
SET hive.exec.mode.local.auto=true;
  • SUM(结果和ORDER BY相关,默认为升序)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
select cookieid,createtime,pv,
sum(pv) over(partition by cookieid order by createtime) as pv1
from test_window_func2;

select cookieid,createtime,pv,
sum(pv) over(partition by cookieid order by createtime rows between unbounded preceding and current row) as pv2
from test_window_func2;

select cookieid,createtime,pv,
sum(pv) over(partition by cookieid) as pv3
from test_window_func2; --如果没有order by排序语句 默认把分组内的所有数据进行sum操作

select cookieid,createtime,pv,
sum(pv) over(partition by cookieid order by createtime rows between 3 preceding and current row) as pv4
from test_window_func2;

select cookieid,createtime,pv,
sum(pv) over(partition by cookieid order by createtime rows between 3 preceding and 1 following) as pv5
from itcast_t2;

select cookieid,createtime,pv,
sum(pv) over(partition by cookieid order by createtime rows between current row and unbounded following) as pv6
from itcast_t2;

--pv1: 分组内从起点到当前行的pv累积,如,11号的pv1=10号的pv+11号的pv, 12号=10号+11号+12号
--pv2: 同pv1
--pv3: 分组内(cookie1)所有的pv累加
--pv4: 分组内当前行+往前3行,如,11号=10号+11号, 12号=10号+11号+12号,
13=10+11+12+13号, 14=11+12+13+14
--pv5: 分组内当前行+往前3行+往后1行,如,14号=11号+12号+13号+14号+15号=5+7+3+2+4=21
--pv6: 分组内当前行+往后所有行,如,13号=13号+14号+15号+16号=3+2+4+4=13,
14=14+15+16=2+4+4=10

/*
- 如果不指定rows between,默认为从起点到当前行;
- 如果不指定order by,则将分组内所有值累加;
- 关键是理解rows between含义,也叫做window子句:
- preceding:往前
- following:往后
- current row:当前行
- unbounded:起点
- unbounded preceding 表示从前面的起点
- unbounded following:表示到后面的终点
*/
  • AVG,MIN,MAX

AVG,MIN,MAX和SUM用法一样

1
2
3
4
5
6
7
8
9
10
11
select cookieid,createtime,pv,
avg(pv) over(partition by cookieid order by createtime rows between unbounded preceding and current row) as pv2
from itcast_t2;

select cookieid,createtime,pv,
max(pv) over(partition by cookieid order by createtime rows between unbounded preceding and current row) as pv2
from itcast_t2;

select cookieid,createtime,pv,
min(pv) over(partition by cookieid order by createtime rows between unbounded preceding and current row) as pv2
from itcast_t2;
  • lag,lead

比如你想将上一行(lag)/下一行(lead)和当前行放到一行就有用了

1
2
3
4
5
6
7
8
9
10
11
lag()/lead()
  lead(field, num, defaultvalue)
    field: 需要查找的字段
    num: 往后查找的num行的数据
    defaultvalue: 没有符合条件的默认值

over()
  表示lag()与lead()操作的数据都在over()的范围内,里面可以使用以下子句
    partition by 语句(用于分组)
    order by 语句()用于排序)
  如:over(partition by a order by b) 表示以a字段进行分组,再以b字段进行排序,对数据进行查询

有一个用户购物信息表,里面有name(用户姓名),orderdate(购买日期),cost (消费额)三个字段,表数据如下:

image-20230302194356105

需求:查询顾客上次的购买时间?

思路:先根据name字段进行分组,根据orderdate字段进行组内排序,排完序后使用lag函数取出当前orderdate字段前1行同字段的值,作为新字段值,如果没有设置默认值。

sql如下:

image-20230302194410034

image-20230302194425668

Hive 调优

本地模式

一般用关于测试,如果数据量查过了设置阈值,则不会走本地模式

大多数的Hadoop Job是需要Hadoop提供的完整的可扩展性来处理大数据集的。不过,有时Hive的输入数据量是非常小的。在这种情况下,为查询触发执行任务时消耗可能会比实际job的执行时间要多的多。对于大多数这种情况,Hive可以通过本地模式在单台机器上处理所有的任务。对于小数据集,执行时间可以明显被缩短。

用户可以通过设置hive.exec.mode.local.auto的值为true,来让Hive在适当的时候自动启动这个优化。

1
2
3
4
5
6
set hive.stats.column.autogather=false;
set hive.exec.mode.local.auto=true; --开启本地mr
--设置local mr的最大输入数据量,当输入数据量小于这个值时采用local mr的方式,默认为134217728,即128M
set hive.exec.mode.local.auto.inputbytes.max=51234560;
--设置local mr的最大输入文件个数,当输入文件个数小于这个值时采用local mr的方式,默认为4
set hive.exec.mode.local.auto.input.files.max=10;

案例实操:

1
2
3
4
5
6
7
8
9
--1)开启本地模式,并执行查询语句
hive (default)> set hive.exec.mode.local.auto=true;
hive (default)> select * from score cluster by sid;
18 rows selected (1.568 seconds)

--2)关闭本地模式,并执行查询语句
hive (default)> set hive.exec.mode.local.auto=false;
hive (default)> select * from score cluster by sid;
18 rows selected (11.865 seconds)

空key处理

有时join超时是因为某些key对应的数据太多,而相同key对应的数据都会发送到相同的reducer上,从而导致内存不够。此时我们应该仔细分析这些异常的key,很多情况下,这些key对应的数据是异常数据,不需要进行无意义的join,我们需要在SQL语句中进行过滤。例如key对应的字段为空,操作如下:

环境准备:

1
2
3
4
5
6
7
8
create table ori(id bigint, time_val bigint, uid string, keyword string, url_rank int, click_num int, click_url string) row format delimited fields terminated by '\t';

create table nullidtable(id bigint, time_val bigint, uid string, keyword string, url_rank int, click_num int, click_url string) row format delimited fields terminated by '\t';

create table jointable(id bigint, time_valbigint, uid string, keyword string, url_rank int, click_num int, click_url string) row format delimited fields terminated by '\t';

load data local inpath '/export/data/hivedatas/hive_big_table/*' into table ori;
load data local inpath '/export/data/hivedatas/hive_have_null_id/*' into table nullidtable;
  • 过滤空key
1
2
3
4
5
6
7
8
9
10
11
不过滤:
INSERT OVERWRITE TABLE jointable
SELECT a.* FROM nullidtable a JOIN ori b ON a.id = b.id;
结果:
No rows affected (152.135 seconds)

过滤:
INSERT OVERWRITE TABLE jointable
SELECT a.* FROM (SELECT * FROM nullidtable WHERE id IS NOT NULL ) a JOIN ori b ON a.id = b.id;
结果:
No rows affected (141.585 seconds)

有时虽然某个key为空对应的数据很多,但是相应的数据不是异常数据,业务上无法把null掉,必须要包含在join的结果中,此时我们可以表a中key为空的字段赋一个随机的值,使得数据随机均匀地分不到不同的reducer上。防止数据倾斜。例如:

  • 不随机分布:
1
2
3
4
5
6
7
8
set hive.exec.reducers.bytes.per.reducer=32123456;
set mapreduce.job.reduces=7;
INSERT OVERWRITE TABLE jointable
SELECT a.*
FROM nullidtable a
LEFT JOIN ori b ON CASE WHEN a.id IS NULL THEN 'hive' ELSE a.id END = b.id;

No rows affected (41.668 seconds) 52.477

结果:这样的后果就是所有为null值的id全部都变成了相同的字符串,及其容易造成数据的倾斜(所有的key相同,相同key的数据会到同一个reduce当中去)

为了解决这种情况,我们可以通过hive的rand函数,随记的给每一个为空的id赋上一个随机值,这样就不会造成数据倾斜

  • 随机分布
1
2
3
4
5
6
7
8
9
10
11
12
set hive.exec.reducers.bytes.per.reducer=32123456;
set mapreduce.job.reduces=7;
INSERT OVERWRITE TABLE jointable
SELECT a.*
FROM nullidtable a
LEFT JOIN ori b ON CASE WHEN a.id IS NULL THEN concat('hive', rand()) ELSE a.id END = b.id;


No rows affected (42.594 seconds)

或者用if
if(id is null,floor((rand() * 1000000)+1),id)

hive sql优化

Map端join

因为是拉取小表到内存中,所以必须要有小表参与

思考: 在执行JOIN的SQL的时候, SQL会被翻译为MR. 请思考, 翻译后MR默认是如何进行Join操作的呢?

image-20211225161819130

1
2
3
思考: 这种reduce join操作, 存在那些弊端呢?          
1- 可能会存在数据倾斜的问题         
2- 所有的数据的合并都是在reduce端完成的, 而reduce数量相对比较少的, 导致reduce的压力比较大      

思考: 如何提升Join的效率呢? Map Join

image-20211225164449487

默认情况下,Map阶段同一Key数据分发给一个reduce,当一个key数据过大时就倾斜了。

并不是所有的聚合操作都需要在Reduce端完成,很多聚合操作都可以先在Map端进行部分聚合,最后在Reduce端得出最终结果。

1
2
3
4
5
6
7
开启Map端聚合参数设置
--(1)是否在Map端进行聚合,默认为True
set hive.map.aggr = true;
--(2)在Map端进行聚合操作的条目数目(阈值)
set hive.groupby.mapaggr.checkinterval = 100000;
--(3)有数据倾斜的时候进行负载均衡(默认是false)
set hive.groupby.skewindata = true;

当选项设定为 true,生成的查询计划会有两个MR Job。第一个MR Job中,Map的输出结果会随机分布到Reduce中,每个Reduce做部分聚合操作,并输出结果,这样处理的结果是相同的Group By Key有可能被分发到不同的Reduce中,从而达到负载均衡的目的;第二个MR Job再根据预处理的数据结果按照Group By Key分布到Reduce中(这个过程可以保证相同的Group By Key被分布到同一个Reduce中),最后完成最终的聚合操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
	Map Join: 将核心Join操作, 都集中在Map端进行处理, 而Map的数量是由读取文件的切片来决定的, 会保证每一个mapTask的读取数量基本是差不多, 可以大幅降低数据倾斜发生, Map的数量会随着读取文件数量增大而变多,以此来提升Join的执行效率

弊端:
1) 比较耗费内存
2) 要求整个Join中, 必须得有小表

使用条件的:
set hive.auto.convert.join=true; -- 开启Map Join的支持, 默认就是True
-- 小表数据量的最大阈值: 默认 20971520(20M)
set hive.auto.convert.join.noconditionaltask.size=512000000 默认值为 20971520

如果不满足条件, HIVE会自动使用 reduce Join 操作



-- 结论是:
1、Map端的Join当前的Hive已经默认支持
2、你什么都不需要做也可以使用Map端Join,但是这个多大的表算小表可以自己定义,默认是20M
set hive.auto.convert.join.noconditionaltask.size=512000000

小表 JOIN 大表

在hive中先后顺序无所谓, 但是在HIVE建议小表在前, 大表在后):

  • 采用 Map Join 解决
1
2
3
4
5
6
使用条件的: 
set hive.auto.convert.join=true; -- 开启Map Join的支持, 默认就是True
-- 小表数据量的最大阈值: 默认 20971520(20M)
set hive.auto.convert.join.noconditionaltask.size=512000000 默认值为 20971520

支持所有表类型(内部表, 外部表, 分桶表, 分区表)

中型表 JOIN 大表

比如大于50M但是有没有大于1G这样的表

  • 方案一: 如果中型表能对数据提前过滤, 尽量提前过滤掉, 过滤后, 又可能就已经满足了Map Join条件(并不一定可用)
  • 方案二: Bucket Map Join
1
2
3
4
5
6
使用条件:
1- Join两个表必须是分桶表
2- 开启Bucket Map Join 支持: set hive.optimize.bucketmapjoin = true;
3- 一个表的分桶数量是另一个表的分桶数量的整倍数
4- 分桶列 必须 是 Join条件的列
5- 必须建立在Map Join场景中

image-20211225171851829

1
2
3
结论是:
1、Bucket Map Join :的表必须都是分桶表,分桶数量是整数倍
2、开启Bucket Map Join :set hive.optimize.bucketmapjoin = true;

大表 JOIN 大表

  • 解决方案: SMB(Sort Merge Bucket) Map Join
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
使用条件:
1- 两个表必须都是分桶表
2- 开启SMB Map Join 支持
-- 开启Bucket Map Join
set hive.optimize.bucketmapjoin = true;
-- 开启 SMB Join支持
set hive.auto.convert.sortmerge.join=true;
set hive.auto.convert.sortmerge.join.noconditionaltask=true;
set hive.optimize.bucketmapjoin.sortedmerge = true;

-- 开启自动尝试使用SMB Join
set hive.optimize.bucketmapjoin.sortedmerge = true

3- 两个表的分桶的数量是一致的
4- 分桶列 必须是 Join条件列 , 同时必须保证按照分桶字段列进行排序操作
-- 开启强制排序
set hive.enforce.sorting=true;
5- 应用在 Bucket Map Join 场景中

建表的时候:
create table test_smb_2(
mid string,
age_id string
) CLUSTERED BY(mid) SORTED BY(mid) INTO 500 BUCKETS;

Count(distinct)

禁用distinct,有需要的用开窗函数,distinct可能执行10分钟,开窗只要2分钟(底层有分组)

数据量小的时候无所谓,数据量大的情况下,由于COUNT DISTINCT操作需要用一个Reduce Task来完成,这一个Reduce需要处理的数据量太大,就会导致整个Job很难完成,一般COUNT DISTINCT使用先GROUP BY再COUNT的方式替换

1
2
3
4
5
方式1:
SELECT count(DISTINCT id) FROM bigtable;

方式2
SELECT count(id) FROM (SELECT id FROM bigtable GROUP BY id) a;

虽然会多用一个Job来完成,但在数据量大的情况下,这个绝对是值得的。

一般可以理解为小于一个block(128M)为数据量小,小表

避免笛卡尔积

尽量避免笛卡尔积,即避免join的时候不加on条件,或者无效的on条件,Hive只能使用1个reducer来完成笛卡尔积。

如何解决数据倾斜问题

何为数据倾斜:

1
在hive中, 执行一条SQL语句, 最终会翻译为MR, MR中mapTask和reduceTask都可能存在多个, 数据倾斜主要指的是整个MR中reduce阶段有多个reduce , 每个reduce拿到的数据的条数并不均衡, 导致某一个或者某几个拿到了比其他的reduce更多的数据, 到底处理数据压力集中在某几个reduce上, 形成数据倾斜问题

在hive中执行什么SQL操作的时候可能会出现数据倾斜

1
2
1) 执行多表join的操作 - 多表
2) 执行group by的操作 - 单表

怎么发现数据倾斜?

思考: 如何才能知道发生了数据倾斜呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
    倾斜出现后, 出现的问题, 程序迟迟无法结束, 或者说翻译MR中reduceTask有多个, 大部分的reduceTask都执行完成了, 只有其中一个或者某几个没有执行完成, 此时认为发生了数据倾斜
```

* 方式一: 通过 yarn查看(运行过程中)或jobhistory查看(程序结束后)

![image-20220104221536449](./assets/image-20220104221536449.png)

![image-20220104221625190](./assets/image-20220104221625190.png)

![image-20220104221718580](./assets/image-20220104221718580.png)

![image-20220104221803831](./assets/image-20220104221803831.png)

![image-20220104221833893](./assets/image-20220104221833893.png)

```properties
看的就是这个reduce的执行时间, 如果这个时间比其他reduce的时候都大的多, 认为出现了数据倾斜问题
```



* 还可以通过HUE查看

![image-20220104222028804](./assets/image-20220104222028804.png)

![image-20220104222056764](./assets/image-20220104222056764.png)

```shell
-- 演示数据倾斜
set mapreduce.job.reduces=10;


select city_id,count(*) from yp_dws.dws_sale_daycount group by city_id;
```





#### join数据倾斜的处理

![image-20230415174258722](./assets/image-20230415174258722.png)

出现倾斜的根本原因:

```properties
在reduce中,某一个, 或者某几个的分组k2对应value的数据比较多, 从而引起数据倾斜问题
```

* 解决方案一:

```properties
可以通过 map join, bucket map join 以及 SMB Map join 解决

注意:
通过map join来解决数据倾斜, 但是map join使用是存在条件的, 如果无法满足这些条件, 无法使用map join
```

* 解决方案二:

```properties
思路: 将那些产生倾斜的k2和对应value数据, 从当前这个MR移植出去, 单独找一个MR来处理即可, 处理后, 和之前MR的汇总结果即可
关键问题: 如何找出那些存在倾斜的k2数据

运行期处理方案:
思路: 在执行MR的时候, 会动态统计每一个k2的值出现的重复的数量, 当这个重复的数量达到一定阈值后, 认为当前这个k2的数据存在数据倾斜, 自动将其剔除, 交由给一个单独的MR来处理即可, 两个MR处理完成后, 将结果, 基于union all 合并在一起即可
实操 :
set hive.optimize.skewjoin=true;
set hive.skewjoin.key=100000; -- 此参考在实际生产环境中, 需要进行调整在合理的值
适用于: 并不清楚那个key容易产生倾斜, 此时可以交由系统来动态检测

编译期处理方案:
思路: 在创建这个表的时候,我们就可以预知到后续插入到这个表中, 那些key的值会产生倾斜, 在建表的时候, 将其提前配置设置好即可, 在后续运行的时候, 程序会自动将设置的k2的值数据单独找一个MR来进行单独的处理操作, 处理后, 再和原有MR进行union all的合并操作

实操:
set hive.optimize.skewjoin.compiletime=true;
建表
CREATE TABLE list_bucket_single (key STRING, value STRING)
-- 倾斜的字段和需要拆分的key值
SKEWED BY (key) ON (1,5,6)
-- 为倾斜值创建子目录单独存放
[STORED AS DIRECTORIES];



在实际生产环境中, 应该使用那种方式呢? 两种方式都会使用的
一般来说, 会将两个都开启, 编译期的明确在编译期将其设置好, 编译期不清楚, 通过 运行期动态捕获即可
```

* union all优化方案

```properties
说明: 不管是运行期, 还是编译期的join倾斜解决, 最终都会运行多个MR , 最终将多个MR的结果, 通过union all进行汇总, union all也是单独需要一个MR来运行的

解决方案:
让每一个MR在运行完成后, 直接将结果输出到目的地即可, 默认是输出到临时文件目录下, 通过union all合并到最终目的地
set hive.optimize.union.remove=true;

```

#### group by 数据倾斜

* 为什么在执行group by的时候 可能会出现数据倾斜

```properties
假设目前有这么一个表:

sid sname cid
s01 张三 c01
s02 李四 c02
s03 王五 c01
s04 赵六 c03
s05 田七 c02
s06 周八 c01
s07 李九 c01
s08 老夯 c03

需求: 请计算每个班级有多少个人
select cid, count(1) from stu group by cid;

翻译后MR是如何处理SQL的呢?

map 阶段:

mapTask 1
k2 v2
c01 {s01 张三 c01}
c02 {s02 李四 c02}
c01 {s03 王五 c01}
c03 {s04 赵六 c03}

mapTask 2
k2 v2
c02 {s05 田七 c02}
c01 {s06 周八 c01}
c01 {s07 李九 c01}
c03 {s08 老夯 c03}

reduce 阶段

reduceTask 1 : 接收 c01 和 c03
接收到的数据:
k2 v2
c01 {s01 张三 c01}
c01 {s03 王五 c01}
c03 {s04 赵六 c03}
c01 {s06 周八 c01}
c01 {s07 李九 c01}
c03 {s08 老夯 c03}
分组后:
c01 [{s01 张三 c01},{s03 王五 c01},{s06 周八 c01},{s07 李九 c01}]
c03 [{s04 赵六 c03},{s08 老夯 c03}]
结果:
c01 4
c03 2
reduceTask 2 : 接收 c02
接收到的数据:
k2 v2
c02 {s02 李四 c02}
c02 {s05 田七 c02}
分组后:
c02 [{s02 李四 c02} , {s05 田七 c02}]
结果:
c02 2
以上整个计算过程中, 发现 其中一个reduce接收到的数据量比另一个reduce接收的数据量要多得多, 认为出现了数据倾斜问题
```

思考: 如何解决group by的数据倾斜呢?

```properties
解决方案一: 基于MR的combiner(规约)减轻数据倾斜问题 (小combiner)

假设目前有这么一个表:

sid sname cid
s01 张三 c01
s02 李四 c02
s03 王五 c01
s04 赵六 c03
s05 田七 c02
s06 周八 c01
s07 李九 c01
s08 老夯 c03

需求: 请计算每个班级有多少个人
select cid, count(1) from stu group by cid;

翻译后MR是如何处理SQL的呢?

map 阶段:

mapTask 1
k2 v2
c01 {s01 张三 c01}
c02 {s02 李四 c02}
c01 {s03 王五 c01}
c03 {s04 赵六 c03}
规约操作: 跟reduce的操作是一致的
输出:
k2 v2
c01 2
c02 1
c03 1

mapTask 2
k2 v2
c02 {s05 田七 c02}
c01 {s06 周八 c01}
c01 {s07 李九 c01}
c03 {s08 老夯 c03}
规约操作: 跟reduce的操作是一致的
输出:
k2 v2
c02 1
c01 2
c03 1

reduce 阶段

reduceTask 1 : 接收 c01 和 c03
接收到数据:
k2 v2
c01 2
c03 1
c01 2
c03 1
分组处理:
c01 [2,2]
c03 [1,1]
结果:
c01 4
c03 2
reduceTask 2 : 接收 c02
接收到数据:
k2 v2
c02 1
c02 1
分组后:
c02 [1,1]
结果:
c02 2


通过规约来解决数据倾斜, 发现处理后, 两个reduce中从原来相差3倍, 变更为相差2倍 , 减轻了数据倾斜问题

如何配置呢?
只需要在hive中开启combiner使用配置即可:
set hive.map.aggr=true;
```

解决方案二: 基于负载均衡的方案解决 (大的combiner)

```properties
解决方案一: 基于MR的combiner(规约)减轻数据倾斜问题 (小combiner)

假设目前有这么一个表:

sid sname cid
s01 张三 c01
s02 李四 c02
s03 王五 c01
s04 赵六 c03
s05 田七 c02
s06 周八 c01
s07 李九 c01
s08 老夯 c03

需求: 请计算每个班级有多少个人
select cid, count(1) from stu group by cid;

翻译后MR是如何处理SQL的呢?
第一个MR的操作:
map 阶段:

mapTask 1
k2 v2
c01 {s01 张三 c01}
c02 {s02 李四 c02}
c01 {s03 王五 c01}
c03 {s04 赵六 c03}


mapTask 2
k2 v2
c02 {s05 田七 c02}
c01 {s06 周八 c01}
c01 {s07 李九 c01}
c03 {s08 老夯 c03}

mapTask执行完成后, 在进行分发数据到达reduce, 默认将相同的k2的数据发往同一个reduce, 此时采用方案是随机分发, 保证每一个reduce拿到相等的数据条数即可


reduce阶段:

reduceTask 1:
接收到数据:
k2 v2
c01 {s01 张三 c01}
c02 {s02 李四 c02}
c01 {s03 王五 c01}
c01 {s06 周八 c01}
分组操作:
c01 [{s01 张三 c01},{s03 王五 c01},{s06 周八 c01}]
c02 [{s02 李四 c02}]
结果:
c01 3
c02 1
reduceTask 2:
接收到数据:
k2 v2
c03 {s04 赵六 c03}
c01 {s07 李九 c01}
c02 {s05 田七 c02}
c03 {s08 老夯 c03}
分组操作:
c03 [{s04 赵六 c03},{s08 老夯 c03}]
c01 [{s07 李九 c01}]
c02 [{s05 田七 c02}]
结果:
c03 2
c01 1
c02 1
第二个MR进行处理: 严格按照相同k2发往同一个reduce

map阶段:

mapTask 1:
k2 v2
c01 3
c02 1
c03 2
c01 1
c02 1
reduce阶段:

reduceTask 1: 接收 c01 和 c03
接收到数据:
k2 v2
c01 3
c01 1
c03 2
结果:
c01 4
c03 2
reduceTask 2: 接收 c02
接收到的数据:
k2 v2
c02 1
c02 1
结果:
c02 2

通过负载均衡来解决数据倾斜, 发现处理后, 两个reduce中从原来相差3倍, 变更为相差1.5倍 , 减轻了数据倾斜问题


如何操作:
只需要在hive中开启负载均衡使用配置即可:
set hive.groupby.skewindata=true;

注意: 使用第二种负载均衡的解决group by的数据倾斜, 一定要注意, sql语句中不能出现多次 distinct操作, 否则hive直接报错
错误信息:
Error in semantic analysis: DISTINCT on different columns notsupported with skew in data
```

![image-20230417083124146](./assets/image-20230417083124146.png)



### HIVE其他优化

hive的并行优化的内容

```properties
1) 并行编译:
hive.driver.parallel.compilation : 是否开启并行编译的操作
hive.driver.parallel.compilation.global.limit: 设置最大同时编译几个会话的SQL
如果设置为 0 或者 负值 , 此时无限制

设置方式: 建议在CM上设置
hive.driver.parallel.compilation true
hive.driver.parallel.compilation.global.limit 15

说明:
默认情况下, 如果hive有多个会话窗口, 而且多个窗口都在提交SQL, 此时hive默认只能对一个会话的SQL进行编译, 其他会话的SQL需要等待, 这样效率相对比较低


2) 并行执行:
一个SQL语句在提交给hive之后, SQL有可能会被翻译为多个阶段, 在这个过程中, 有可能出现多个阶段互不干扰的情况,这个时候, 可以安排多个阶段并行执行的操作, 以此来提升效率
如何设置呢? 推荐在 会话中设置
set hive.exec.parallel=true; 是否开启并行执行
set hive.exec.parallel.thread.number=16; 最大运行并行多少个阶段
注意:
开启了此配置, 并不代表着所有SQL一定会并行的执行, 因为是否并行执行还取决于SQL中多个阶段之间是否有依赖, 只有在没有依赖的时候, 才可能并行执行


例子: union all
select * from A order by c
union all
select * from B where xxx;
hive常规的做法:
先执行第一个阶段 : select * from A order by c 得出临时结果
接着执行第二个阶段: select * from B where xxx; 得出临时结果
最后, 将两个语句的结果 合并在一起
```

* 小文件合并的操作:

```properties
当HDFS中小文件过多后, 会导致整个HDFS的存储容量的下降, 因为每一个小文件都会有一个元数据, 而元数据是存储在namenode的内存中, 当内存一旦满了, 即使datanode上还有空间, 那么也是无法存储了
MR角度: 当小文件过多后, 就会导致读取数据时候, 产生大量文件的切片, 从而导致有多个mapTask的执行, 而每个mapTask处理数据量比较的少, 导致资源的浪费, 导致执行时间较长, 因为一旦没有资源, 所有map只能串行化执行


hive的解决方案: 在执行完成后, 输出的结果的文件尽量的少一些, 避免出现小文件过多问题, 可以通过设置让执行的SQL输出的文件竟可能少一些
hive.merge.mapfiles: 是否开启map端的小文件合并操作, 指的 MR只有map没有reduce的操作时候
hive.merge.mapredfiles: 是否开启reduce端的小文件合并操作, 指的普通MR
hive.merge.size.per.task: 设置文件的大小 合并后的文件最大值 比如 128M
hive.merge.smallfiles.avgsize: 当输出文件的平均大小小于此设置值时,启动一个独立的map-reduce任务进行文件merge,默认值为16M。

说明: 以上配置均可直接在CM上进行配置即可

思考点: 在执行SQL的时候, 什么样SQL 可能会出现多个reduce情况? group by join


例如:
比如一个MR输出 10个文件
1M 10M 50M 2M 3M 30M 10M 5M 6M 20M
此时请问, 是否会进行文件合并工作呢? 发现得出平均值是小于16M 的 所以会执行小文件合并操作
思考: 是将所有的文件合并为一个文件, 还是怎么做呢?
128M 8M
```

* 矢量化查询(批量化):

```properties
配置:
set hive.vectorized.execution.enabled=true; 默认值为true

说明:
一旦开启了矢量化查询的工作,hive执行引擎在读取数据时候, 就会采用批量化读取工作, 一次性读取1024条数据进行统一的处理工作, 从而减少读取的次数(减少磁盘IO次数), 从而提升效率

注意:
要求表的存储格式必须为 ORC
```

* 读取零拷贝:

```properties
一句话: 在读取数据的时候, 能少读一点 尽量 少读一些(没用的数据尽量不读)

配置:
set hive.exec.orc.zerocopy=true; 默认中为false

例子:
看如下这个SQL: 假设A表中 a b c三个字段
select a,b from A where a =xxx;
```

* 关联优化器: 如果多个MR之间的操作的数据都是一样的, 同样shuffle操作也是一样的, 此时可以共享shuffle

> 一个SQL最终翻译成MR , 请问 有没有可能翻译为多个MR的情况呢? 非常有可能
>
> 每一个MR的中间有可能都会执行shuffle的操作, 而shuffle其实比较消耗资源操作(内部存在多次 io操作)

```properties
配置项: set hive.optimize.correlation=true;
```



总结优化点:

```properties
set hive.exec.parallel=true; --是否开启并行执行
set hive.exec.parallel.thread.number=16; --最大运行并行多少个阶段
set hive.vectorized.execution.enabled=true; -- 矢量化查询
set hive.exec.orc.zerocopy=true; -- 读取零拷贝
set hive.optimize.skewjoin.compiletime=true; -- 开启编译期优化
set hive.optimize.skewjoin=true; -- 开启运行期数据倾斜的处理 默认值为false
set hive.skewjoin.key=100000;
set hive.optimize.union.remove=true; -- union优化
set hive.groupby.skewindata=true; -- 开启负载均衡优化
set hive.optimize.correlation=true; --开启关联优化器
```







### **并行执行**

Hive会将一个查询转化成一个或者多个阶段。这样的阶段可以是MapReduce阶段、抽样阶段、合并阶段、limit阶段。或者Hive执行过程中可能需要的其他阶段。默认情况下,Hive一次只会执行一个阶段。不过,某个特定的job可能包含众多的阶段,而这些阶段可能并非完全互相依赖的,也就是说有些阶段是可以并行执行的,这样可能使得整个job的执行时间缩短。不过,如果有更多的阶段可以并行执行,那么job可能就越快完成。

​ 通过设置参数hive.exec.parallel值为true,就可以开启并发执行。不过,在共享集群中,需要注意下,如果job中并行阶段增多,那么集群利用率就会增加。

```markdown
set hive.exec.parallel=true; --打开任务并行执行
set hive.exec.parallel.thread.number=16; --同一个sql允许最大并行度,默认为8。
```

当然,得是在系统资源比较空闲的时候才有优势,否则,没资源,并行也起不来。

### **严格模式**

Hive提供了一个严格模式,可以防止用户执行那些可能意向不到的不好的影响的查询。

​ 通过设置属性hive.mapred.mode值为默认是非严格模式nonstrict 。开启严格模式需要修改hive.mapred.mode值为strict,开启严格模式可以禁止3种类型的查询。

```markdown
set hive.mapred.mode = strict; --开启严格模式
set hive.mapred.mode = nostrict; --开启非严格模式
```

配置文件修改:hive-site.xml

```xml
<property>
<name>hive.mapred.mode</name>
<value>strict</value>
</property>
```

1)**对于分区表,在where语句中必须含有分区字段作为过滤条件来限制范围,否则不允许执行**。换句话说,就是用户不允许扫描所有分区。进行这个限制的原因是,通常分区表都拥有非常大的数据集,而且数据增加迅速。没有进行分区限制的查询可能会消耗令人不可接受的巨大资源来处理这个表。

2)**对于使用了order by语句的查询,要求必须使用limit语句**。因为order by为了执行排序过程会将所有的结果数据分发到同一个Reducer中进行处理,强制要求用户增加这个LIMIT语句可以防止Reducer额外执行很长一段时间。

3)**限制笛卡尔积的查询**。对关系型数据库非常了解的用户可能期望在执行JOIN查询的时候不使用ON语句而是使用where语句,这样关系数据库的执行优化器就可以高效地将WHERE语句转化成那个ON语句。**不幸的是,Hive并不会执行这种优化**,因此,如果表足够大,那么这个查询就会出现不可控的情况。



### 存储方式orc和压缩方式snappy

大数据场景下存储格式压缩格式尤为关键,可以提升计算速度,减少存储空间,降低网络io,磁盘io,所以要选择合适的压缩格式和存储格式,存储方式和压缩方式之前已经讲过,这里不再描述。

> 选择合适的存储格式和压缩方式有利于数据分析 orc存储+snappy压缩



## Hive 练习

### 陌陌聊天数据分析

#### 背景介绍

陌陌作为聊天平台每天都会有大量的用户在线,会出现大量的聊天数据,通过对聊天数据的统计分析,可以更好的对用户构建精准的用户画像,为用户提供更好的服务以及实现高ROI的平台运营推广,给公司的发展决策提供精确的数据支撑。

#### 目标需求

基于Hadoop和Hive实现聊天数据统计分析,构建聊天数据分析报表

- 统计今日总消息量
- 统计今日每小时消息量、发送和接收用户数
- 统计今日各地区发送消息数据量
- 统计今日发送消息和接收消息的用户数
- 统计今日发送消息最多的Top10用户
- 统计今日接收消息最多的Top10用户
- 统计发送人的手机型号分布情况
- 统计发送人的设备操作系统分布情况

#### 数据内容

数据大小:两个文件共14万条数据
列分隔符:制表符 `\t`
数据字典及样例数据

![image-20230119132651639](./assets/image-20230119132651639.png)

#### 基于Hive数仓实现需求开发

##### 建库建表、加载数据

```markdown
--如果数据库已存在就删除
drop database if exists db_msg cascade ;
--创建数据库
create database db_msg ;
--切换数据库
use db_msg ;
--列举数据库
show databases ;

--如果表已存在就删除
drop table if exists db_msg.tb_msg_source ;
--建表
create table db_msg.tb_msg_source(
msg_time string comment "消息发送时间"
, sender_name string comment "发送人昵称"
, sender_account string comment "发送人账号"
, sender_sex string comment "发送人性别"
, sender_ip string comment "发送人ip地址"
, sender_os string comment "发送人操作系统"
, sender_phonetype string comment "发送人手机型号"
, sender_network string comment "发送人网络类型"
, sender_gps string comment "发送人的GPS定位"
, receiver_name string comment "接收人昵称"
, receiver_ip string comment "接收人IP"
, receiver_account string comment "接收人账号"
, receiver_os string comment "接收人操作系统"
, receiver_phonetype string comment "接收人手机型号"
, receiver_network string comment "接收人网络类型"
, receiver_gps string comment "接收人的GPS定位"
, receiver_sex string comment "接收人性别"
, msg_type string comment "消息类型"
, distance string comment "双方距离"
, message string comment "消息内容"
)
--指定分隔符为制表符
row format delimited fields terminated by '\t' ;

# 加载数据
hdfs dfs -mkdir -p /momo/data/
hdfs dfs -put /opt/data/hive3_data/data1.tsv /momo/data/
hdfs dfs -put /opt/data/hive3_data/data2.tsv /momo/data/

# 加载到Hive表中
load data inpath '/momo/data/data1.tsv' into table db_msg.tb_msg_source;
load data inpath '/momo/data/data2.tsv' into table db_msg.tb_msg_source;

# 查询校验数据
select
msg_time,sender_name,sender_ip,sender_phonetype,receiver_name,receiver_network
from tb_msg_source limit 10;
```



##### ETL数据清洗

经过观察分析,数据存在以下问题

```markdown
-- 问题1:当前数据中,有一些数据的字段为空,不是合法数据 => 过滤 where
select msg_time, sender_name, sender_gps from db_msg.tb_msg_source where length(sender_gps) = 0 limit 10;

-- 问题2:需求中,需要统计每天、每个小时的消息量,但是数据中没有天和小时字段,只有整体时间字段,不好处理 => 截取 substr
select msg_time from db_msg.tb_msg_source limit 10

-- 问题3:需求中,需要对经度和维度构建地区的可视化地图,但是数据中GPS经纬度为一个字段,不好处理 => 分隔 split
select sender_gps from db_msg.tb_msg_source limit 10;
```

- ctas方法: 需要将清洗后的数据存到一张新的表,方便后续分析使用

```markdown
-- Create table …… as select ……

# ETL实现
--如果表已存在就删除
drop table if exists db_msg.tb_msg_etl;
--将Select语句的结果保存到新表中
create table db_msg.tb_msg_etl as
select
*,
substr(msg_time,0,10) as dayinfo, substr(msg_time,12,2) as hourinfo, --获取天和小时
split(sender_gps,",")[0] as sender_lng, split(sender_gps,",")[1] as sender_lat --提取经度纬度
from db_msg.tb_msg_source
--过滤字段为空的数据
where length(sender_gps) > 0 ;
```

查看清洗后的结果集:

```markdown
select
msg_time,dayinfo,hourinfo,sender_gps,sender_lng,sender_lat
from db_msg.tb_msg_etl
limit 10;
```



##### 需求指标统计

1. 实际开发中,拿到业务需求指标,如何下手?

1. 正确解读业务需求,避免歧义

2. 确定待查询的数据表-->from 表

3. 找出分析的维度-->group by 分组的字段

4. 找出计算的指标-->聚合的字段

5. 其他细节点(过滤、排序等)

```markdown
# 查询类SQL编写思路举例

表:t_user(id,name,age,sex,city)

需求:统计每个城市男女人数与男女平均年龄

分组字段:每个城市、男女
也就意味着同一个城市,性别相同的人应该分到同一组,因此这里需要根据两个字段进行分组

聚合字段:人数、平均年龄
count(id)就是统计每个分组中的条数-->人数
avg(age)就是统计每个分组中年龄的平均值-->平均年龄
```



2. SQL层面如何编写查询语句?



- 统计今日总消息量

```markdown
--保存结果表
create table if not exists tb_rs_total_msg_cnt comment "今日消息总量"
as
select
dayinfo,
count(*) as total_msg_cnt
from db_msg.tb_msg_etl
group by dayinfo;
```

![image-20230119135132768](./assets/image-20230119135132768.png)

- 统计今日每小时消息量、发送和接收用户数

```markdown
--保存结果表
create table if not exists tb_rs_hour_msg_cnt
comment "每小时消息量趋势"
as
select
dayinfo,
hourinfo,
count(*) as total_msg_cnt,
count(distinct sender_account) as sender_usr_cnt,
count(distinct receiver_account) as receiver_usr_cnt
from db_msg.tb_msg_etl
group by dayinfo,hourinfo;
```

![image-20230119135440233](./assets/image-20230119135440233.png)

- 统计今日各地区发送消息数据量

```markdown
--保存结果表
create table if not exists tb_rs_loc_cnt
comment "今日各地区发送消息总量"
as
select
dayinfo,
sender_gps,
cast(sender_lng as double) as longitude, -- 转换为double,方便后续计算
cast(sender_lat as double) as latitude,
count(*) as total_msg_cnt
from db_msg.tb_msg_etl
group by dayinfo,sender_gps,sender_lng,sender_lat;
```

![image-20230119135759506](./assets/image-20230119135759506.png)

- 统计今日发送消息和接收消息的用户数

```markdown
--保存结果表
create table if not exists tb_rs_usr_cnt
comment "今日发送消息人数、接受消息人数"
as
select
dayinfo,
count(distinct sender_account) as sender_usr_cnt,
count(distinct receiver_account) as receiver_usr_cnt
from db_msg.tb_msg_etl
group by dayinfo;
```

![image-20230119140147409](./assets/image-20230119140147409.png)

- 统计今日发送消息最多的Top10用户

```markdown
--保存结果表
create table if not exists tb_rs_susr_top10
comment "发送消息条数最多的Top10用户"
as
select
dayinfo,
sender_name as username,
count(*) as sender_msg_cnt
from db_msg.tb_msg_etl
group by dayinfo,sender_name
order by sender_msg_cnt desc
limit 10;
```

![image-20230119140439796](./assets/image-20230119140439796.png)

- 统计今日接收消息最多的Top10用户

```markdown
--保存结果表
create table if not exists tb_rs_rusr_top10
comment "接受消息条数最多的Top10用户"
as
select
dayinfo,
receiver_name as username,
count(*) as receiver_msg_cnt
from db_msg.tb_msg_etl
group by dayinfo,receiver_name
order by receiver_msg_cnt desc
limit 10;
```

![image-20230119140857752](./assets/image-20230119140857752.png)

- 统计发送人的手机型号分布情况

```markdown
--保存结果表
create table if not exists tb_rs_sender_phone
comment "发送人的手机型号分布"
as
select
dayinfo,
sender_phonetype,
count(distinct sender_account) as cnt
from tb_msg_etl
group by dayinfo,sender_phonetype;
```

![image-20230119140733833](./assets/image-20230119140733833.png)

- 统计发送人的设备操作系统分布情况

```markdown
--保存结果表
create table if not exists tb_rs_sender_os
comment "发送人的OS分布"
as
select
dayinfo,
sender_os,
count(distinct sender_account) as cnt
from tb_msg_etl
group by dayinfo,sender_os;
```

![image-20230119140628948](./assets/image-20230119140628948.png)



### FineBI实现可视化报表

#### 安装

- 官网:https://www.finebi.com/

只需要设置一下JVM内存大小,其他的下一步即可。

![image-20230119142440198](./assets/image-20230119142440198.png)

关注公众号,获取激活码

设置管理员账号,直接登录

#### 配置数据源

我们的数据源在hive中,需要和fineBI对接。

FineBI与Hive集成的官方文档:https://help.fanruan.com/finebi/doc-view-301.html

##### 驱动配置

- 问题:如果使用FineBI连接Hive,读取Hive的数据表,需要在FineBI中添加Hive的驱动jar包
- 解决:将Hive的驱动jar包放入FineBI的`webapps\webroot\WEB-INF\lib`目录下

![image-20230119144409132](./assets/image-20230119144409132.png)

##### 插件安装

- 问题:我们自己放的Hive驱动包会与FineBI自带的驱动包产生冲突,导致FineBI无法识别我们自己的驱动包
- 解决:安装FineBI官方提供的驱动包隔离插件

![image-20230119144517681](./assets/image-20230119144517681.png)

安装插件后重新fineBI

新建连接找到hive数据源

![image-20230119144640130](./assets/image-20230119144640130.png)

#### 制作可视化报表

1.添加包和业务组

![image-20230119145656137](./assets/image-20230119145656137.png)

2.添加数据库,点击选择表

![image-20230119145721350](./assets/image-20230119145721350.png)

3.数据有了,BI样式其实很丰富,需要纠结的可能是要选择什么样的图表展示数据。

![image-20230119153425257](./assets/image-20230119153425257.png)

> FineBI就是上去玩玩用用的那种,重点还是数据处理,没有数据,这些图表就是无根之水。





## Hive 面试题

- 1.你用的hive版本
- 2.hive和hadoop的关系
- 3.hive中内部表和外部表的区别
- 4.hive汇总分区表和分桶表的区别
- 5.hive表加载数据的方式有哪些
- 6.你用过的哪些hive的开窗函数,什么时候使用开窗函数

```
分组聚合时,还需要保留数据的明细时,就可以用开窗
```

- 7.笔试:行转列,列转行代码,collect_set和collect_list区别
- 8.hive自定义UDF的步骤
- 9.项目中hive数据文件存储格式和压缩算法是什么
- 10.hive的调优方式



hive

```
表A
left join 表B on a.test1 = b.test2
left join 表C on b.test3 = c.test4

1.一个key的记录数如果超过1亿,就会有作业中断(内存溢出),或者长时间运行(数据倾斜)
对关联表的test1、2、3、4做group by,观察key 的数据量。大的就会有问题
2.test1只关联出50%test2
则test2在关联test3,就会数据倾斜有问题。
解决办法是b和c先处理为一个temp临时表。再和a关联。
```

## hive常用工具表

### hive日历维度表

![image-20230927225638587](./assets/image-20230927225638587.png)

```markdown
create table if not exists dim_date (
date_id string comment '日期(yyyymmdd)'
,datestr string comment '日期(yyyy-mm-dd)'
,date_name string comment '日期名称中文'
,weekid int comment '周(0-6,周日~周六)'
,week_cn_name string comment '周_名称_中文'
,week_en_name string comment '周_名称_英文'
,week_en_nm string comment '周_名称_英文缩写'
,yearmonthid string comment '月份id(yyyymm)'
,yearmonthstr string comment '月份(yyyy-mm)'
,monthid int comment '月份id(1-12)'
,monthstr string comment '月份'
,month_cn_name string comment '月份名称_中文'
,month_en_name string comment '月份名称_英文'
,month_en_nm string comment '月份名称_简写_英文'
,quarterid int comment '季度id(1-4)'
,quarterstr string comment '季度名称'
,quarter_cn_name string comment '季度名称_中文'
,quarter_en_name string comment '季度名称_英文'
,quarter_cn_nm string comment '季度名称_简写中文'
,quarter_en_nm string comment '季度名称_简写英文'
,yearid int comment '年份id'
,year_cn_name string comment '年份名称_中文'
,year_en_name string comment '年份名称_英文'
,month_start_date string comment '当月1号(yyyy-mm-dd)'
,month_end_date string comment '当月最后日期(yyyy-mm-dd)'
,month_timespan int comment '月跨天数'
,week_of_year int comment '当年第几周'
,workday_flag string comment '是否工作日(周一至周五Y,否则:N)'
,weekend_flag string comment '是否周末(周六和周日Y,否则:N)'
,week_start_date string comment '当周起始日期'
,week_end_date string comment '当周结束日期'
)comment '日期维度表'
stored as parquet;


insert overwrite table dim_date


select date_id
,datestr
,concat(yearid,'年',monthid,'月',substr(datestr,9,2),'日') as date_name
,weekid
,case weekid when 0 then '星期日'
when 1 then '星期一'
when 2 then '星期二'
when 3 then '星期三'
when 4 then '星期四'
when 5 then '星期五'
when 6 then '星期六'
end as week_cn_name
,case weekid when 0 then 'Sunday'
when 1 then 'Monday'
when 2 then 'Tuesday'
when 3 then 'Wednesday'
when 4 then 'Thurday'
when 5 then 'Friday'
when 6 then 'Saturday'
end as week_en_name
,case weekid when 0 then 'Sun'
when 1 then 'Mon'
when 2 then 'Tues'
when 3 then 'Wed'
when 4 then 'Thur'
when 5 then 'Fri'
when 6 then 'Sat'
end as week_en_nm
,substr(date_id,1,6) as yearmonthid
,substr(datestr,1,7) as yearmonthstr
,monthid
,concat(yearid,'年',monthid,'月') as monthstr
,concat(monthid,'月') as month_cn_name
,case monthid when 1 then 'January'
when 2 then 'February'
when 3 then 'March'
when 4 then 'April'
when 5 then 'May'
when 6 then 'June'
when 7 then 'July'
when 8 then 'August'
when 9 then 'September'
when 10 then 'October'
when 11 then 'November'
when 12 then 'December'
end as month_en_name
,case monthid when 1 then 'Jan'
when 2 then 'Feb'
when 3 then 'Mar'
when 4 then 'Apr'
when 5 then 'May'
when 6 then 'Jun'
when 7 then 'Jul'
when 8 then 'Aug'
when 9 then 'Sept'
when 10 then 'Oct'
when 11 then 'Nov'
when 12 then 'Dec'
end as month_en_nm
,quarterid
,concat(yearid,quarterid) as quarterstr
,concat(yearid,'年第',quarterid,'季度') as quarter_cn_name
,concat(yearid,'Q',quarterid) as quarter_en_name
,case quarterid when 1 then '第一季度'
when 2 then '第二季度'
when 3 then '第三季度'
when 4 then '第四季度'
end as quarter_cn_nm
,concat('Q',quarterid) as quarter_en_nm
,yearid
,concat(yearid,'年') as year_cn_name
,yearid as year_en_name
,month_start_date
,month_end_date
,datediff(month_end_date,month_start_date) + 1 as month_timespan
,week_of_year
,case when weekid in (1,2,3,4,5) then 'Y' else 'N' end as workday_flag
,case when weekid in (0,6) then 'Y' else 'N' end as weekend_flag
--,date_sub(datestr,weekid-1) as week_start_date --周起始日期
,case when weekid = 0 then date_sub(datestr,6)
else date_sub(datestr,weekid-1)
end as week_start_date --周起始日期
--,date_add(datestr,6-weekid+1) as week_end_date --周结束日期
,case when weekid = 0 then datestr
else date_add(datestr,6-weekid+1)
end as week_end_date --周结束日期
from
(
select from_unixtime(unix_timestamp(datestr,'yyyy-MM-dd'),'yyyyMMdd') as date_id
,datestr as datestr
,pmod(datediff(datestr, '2012-01-01'), 7) as weekid
,concat(substr(datestr,1,4),substr(datestr,6,2)) as yearmonthid
,substr(datestr,1,7) as yearmonthstr
,cast(substr(datestr,6,2) as int) as monthid
,case when cast(substr(datestr,6,2) as int) <= 3 then 1
when cast(substr(datestr,6,2) as int) <= 6 then 2
when cast(substr(datestr,6,2) as int) <= 9 then 3
when cast(substr(datestr,6,2) as int) <= 12 then 4
end as quarterid
,substr(datestr,1,4) as yearid
,date_sub(datestr,dayofmonth(datestr)-1) as month_start_date --当月第一天
,last_day(date_sub(datestr,dayofmonth(datestr)-1)) month_end_date --当月最后一天
,weekofyear(datestr) as week_of_year
from
(
select date_add('1900-01-01',t0.pos) as datestr
from
(
select posexplode(
split(
repeat('o', datediff(from_unixtime(unix_timestamp('20501231','yyyymmdd'),'yyyy-mm-dd'), '1900-01-01')), 'o'
)
)
) t0
) t


) A
;
```