1. 创建 Hive 表
基本语法
-- 创建一个名为 pokes 的表,其中包含两列,第一列是整数,另一列是字符串。
CREATE TABLE pokes (foo INT, bar STRING);
-- 创建一个名为 invites 的表,其中包含两列和一个名为 ds 的分区列。
-- 分区列是一个虚拟列,不是数据本身的一部分,而是源自加载特定数据集的分区。
-- 默认情况下,表格假定为文本输入格式,分隔符假定为 ^A(ctrl-a)。
CREATE TABLE invites (foo INT, bar STRING) PARTITIONED BY (ds STRING);
完整建表语法
CREATE [EXTERNAL] TABLE [IF NOT EXISTS] table_name
[(col_name data_type [COMMENT col_comment], ...)]
[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]
[ROW FORMAT row_format]
[STORED AS file_format]
[LOCATION hdfs_path]
参数说明:
| 关键词 | 说明 |
|---|---|
CREATE TABLE | 创建一个指定名字的表,如果相同名字的表已存在则抛出异常;可用 IF NOT EXISTS 忽略该异常 |
EXTERNAL | 创建外部表 |
COMMENT | 为表和列添加注释 |
PARTITIONED BY | 创建分区表 |
CLUSTERED BY | 创建分桶表 |
SORTED BY | 排序(不常用) |
ROW FORMAT DELIMITED | 数据切分格式 |
STORED AS | 指定存储文件类型:SEQUENCEFILE(二进制序列文件)、TEXTFILE(文本)、RCFILE(列式存储格式文件) |
LOCATION | 指定表在 HDFS 上的存储位置 |
LIKE | 复制现有的表结构,不复制数据 |
实例 1:创建内部表
数据示例:
1,Lilei,book-tv-code,beijing:chaoyang-shanghai:pudong
2,Hanmeimei,book-Lilei-code,beijing:haidian-shanghai:huangpu
数据格式说明:
- 字段之间由
,分割 →FIELDS TERMINATED BY ',' - 第二个字段是 Array 形式,元素之间由
-分割 →COLLECTION ITEMS TERMINATED BY '-' - 第三个字段是 K-V 形式,K-V 内部由
:分割,K-V 之间由-分割 →MAP KEYS TERMINATED BY ':' - 每条数据之间由换行符分割(默认
\n),如果是其他分割方式 →LINES TERMINATED BY ';'
CREATE TABLE IF NOT EXISTS psn (
id INT,
name STRING,
hobbies ARRAY<STRING>,
address MAP<STRING, STRING>
)
ROW FORMAT DELIMITED
FIELDS TERMINATED BY ','
COLLECTION ITEMS TERMINATED BY '-'
MAP KEYS TERMINATED BY ':'
LINES TERMINATED BY ';';
实例 2:根据查询结果创建表
查询结果会插入到新建的表中,此方法不支持分区表。
CREATE TABLE IF NOT EXISTS psn1 AS SELECT * FROM psn;
实例 3:根据已有表结构创建表
CREATE TABLE IF NOT EXISTS psn2 LIKE psn;
实例 4:修改内部表为外部表
注意:
'EXTERNAL' = 'TRUE'大小写敏感。
ALTER TABLE psn1 SET TBLPROPERTIES('EXTERNAL' = 'TRUE');
实例 5:修改外部表为内部表
ALTER TABLE psn1 SET TBLPROPERTIES('EXTERNAL' = 'FALSE');
2. 查看 Hive 表
-- 查看表的类型
DESC FORMATTED psn;
-- 查看所有表
SHOW TABLES;
-- 查看部分表
SHOW TABLES '.*s';
-- 查看建表语句(常用于查看表结构、HDFS 路径)
SHOW CREATE TABLE tablename;
-- 查看指定表的列
DESCRIBE invites;
DESC invites;
-- 查看建库语句
SHOW CREATE DATABASE databasename;
-- 查看分区
SHOW PARTITIONS invites;
-- 查看表的详细信息(HUE 上显示的概览信息,主要用于查询文件存储路径 Location)
DESCRIBE FORMATTED tablename;
3. 更改 Hive 表
重命名表
ALTER TABLE events RENAME TO 3koobecaf;
修改列
-- 增加列(ADD 会在所有列后面、partition 列前面添加)
ALTER TABLE pokes ADD COLUMNS (new_col INT);
-- 增加列并添加注释
ALTER TABLE invites ADD COLUMNS (new_col2 INT COMMENT 'a comment');
-- 更新列
ALTER TABLE invites CHANGE COLUMN foo desc STRING;
-- 删除列(REPLACE 方式)
ALTER TABLE invites REPLACE COLUMNS (foo INT COMMENT 'only keep the column foo');
-- 列名重命名
ALTER TABLE invites RENAME COLUMN foo TO bar;
修改分区
-- 删除指定分区
ALTER TABLE invites DROP PARTITION(dt='20221108');
-- 删除顶层分区下所有分区
-- 假设 invites 有两个分区字段 (year='2021', month='1') 和 (year='2021', month='2')
-- 删除 year='2021' 分区,即可同时删除 (year='2021', month='1') 和 (year='2021', month='2')
ALTER TABLE invites DROP PARTITION(year='2021');
-- 删除指定的底层分区
-- 假设 invites 有 (year='2021', month='1') 和 (year='2022', month='1')
-- 删除 month='1' 分区,即可同时删除两个分区
ALTER TABLE invites DROP PARTITION(month='1');
-- 删除多个分区(不连续分区之间用逗号分割)
ALTER TABLE invites DROP PARTITION(dt='20221108'), PARTITION(dt='20220101');
-- 删除分区加判空条件
ALTER TABLE invites DROP IF EXISTS PARTITION(dt='20221108');
-- 增加分区(不同分区之间不需要逗号分割符)
ALTER TABLE invites ADD PARTITION(dt='20221108') PARTITION(dt='20220101');
-- 增加分区加判空条件
ALTER TABLE invites ADD IF NOT EXISTS PARTITION(dt='20221108') PARTITION(dt='20220101');
-- 分区重命名
ALTER TABLE invites PARTITION(dt='20221108') RENAME TO PARTITION(dt='20220101');
注意:
ADD PARTITION必须指定每一层分区对应值,不支持只指定顶层分区来批量添加。
修改文件格式与路径
-- 修改建表使用的文件格式
ALTER TABLE invites SET FILEFORMAT orc;
ALTER TABLE invites PARTITION (month=2, day=2) SET FILEFORMAT parquet;
-- 修改文件路径
ALTER TABLE invites PARTITION(month=2, day=2) SET LOCATION '/user/lijp/temp';
4. 删除 Hive 内部表
注意:
DROP TABLE仅能删除内部表;如果是外部表,只能删除元数据,不能删除 HDFS 数据。
DROP TABLE pokes;
5. 删除内部表元数据
保留底层文件,后续可用于创建外部表。
DROP TABLE pokes PURGE;
6. 删除内部表数据
删除底层文件,保留内部表元数据。
TRUNCATE TABLE pokes;
删除 Hive 外部表
需要先将外部表改为内部表,再删除:
ALTER TABLE pokes SET TBLPROPERTIES('EXTERNAL' = 'FALSE');
DROP TABLE pokes;
7. 修复 Hive 表
当文件与元数据存在差异时使用:
MSCK REPAIR TABLE tableName;
8. 加载本地文件到 Hive 表
表需要提前创建,按照导入文件的格式指定行分隔符、列分隔符。
LOAD DATA LOCAL INPATH './examples/files/kv1.txt' OVERWRITE INTO TABLE pokes;
参数说明:
| 关键词 | 说明 |
|---|---|
LOCAL | 从本地文件系统加载,若缺省则从 HDFS 加载 |
OVERWRITE | 写入前清空表,若缺省则等价于 INTO(追加) |
提示: 可以通过修改
hive-default.xml配置文件中的hive.metastore.warehouse.dir来指定 Hive 表根目录。
9. 加载本地文件到不同分区
需要在建表时创建分区表:
LOAD DATA LOCAL INPATH './examples/files/kv2.txt' OVERWRITE INTO TABLE invites PARTITION (ds='2008-08-15');
LOAD DATA LOCAL INPATH './examples/files/kv3.txt' OVERWRITE INTO TABLE invites PARTITION (ds='2008-08-08');
10. 加载集群文件到不同分区
注意: 导入数据后源文件会消失,导入前需要做好备份。
LOAD DATA INPATH '/user/myname/kv2.txt' OVERWRITE INTO TABLE invites PARTITION (ds='2008-08-15');
-- 动态分区插入(需要在建表时创建分区表)
SET hive.exec.dynamic.partition.mode=nonstrict;
LOAD DATA INPATH '/user/myname/kv2.txt' OVERWRITE INTO TABLE invites PARTITION (ds1, ds2);
注意: Hive 不支持集群分区文件夹插入分区表,仅支持单个文件的插入。
11. SELECT 查询
SELECT a.foo FROM invites a;
12. WHERE 与 ON 筛选
基本用法
- ON:指定连接条件,进行关联操作
- WHERE:对结果进行过滤
-- ON 用于连接
SELECT a.*, b.*
FROM invites a
LEFT JOIN pokes b
ON a.id = b.id;
-- WHERE 用于过滤
SELECT a.*, b.*
FROM invites a
LEFT JOIN pokes b
ON a.id = b.id
WHERE b.id IS NOT NULL;
执行 JOIN 时的区别
i. 正常情形:ON 用于连接,WHERE 用于过滤
SELECT * FROM
(SELECT * FROM (SELECT null AS n1, 1 AS id) t1
UNION ALL
SELECT * FROM (SELECT 'one' AS n1, 2 AS id) t2) t
JOIN
(SELECT * FROM (SELECT 'one' AS n1, 1 AS id) p1
UNION ALL
SELECT * FROM (SELECT null AS n1, 2 AS id) p2) p
ON t.id = p.id
WHERE t.n1 IS NOT NULL;
结果:
| t.n1 | t.id | p.n1 | p.id |
|---|---|---|---|
| NULL | 1 | one | 1 |
| one | 2 | NULL | 2 |
ii. 顺序敏感:ON 在前,WHERE 在后
-- 以下会报错
SELECT * FROM ...
WHERE t.n1 IS NOT NULL
ON t.id = p.id;
-- 报错:cannot recognize input near 'on' 't' '.' in expression specification
如果需要在连接前过滤,需要建立一个额外的子查询:
SELECT * FROM
(SELECT * FROM
(SELECT * FROM (SELECT null AS n1, 1 AS id) t1
UNION ALL
SELECT * FROM (SELECT 'one' AS n1, 2 AS id) t2) t
WHERE t.n1 IS NOT NULL) tt
JOIN
(SELECT * FROM (SELECT 'one' AS n1, 1 AS id) p1
UNION ALL
SELECT * FROM (SELECT null AS n1, 2 AS id) p2) p
ON tt.id = p.id;
iii. 仅使用 ON 筛选而不指定关联关系
相当于筛选后生成笛卡尔积:
SELECT * FROM
(SELECT * FROM (SELECT null AS n1, 1 AS id) t1
UNION ALL
SELECT * FROM (SELECT 'one' AS n1, 2 AS id) t2) t
JOIN
(SELECT * FROM (SELECT 'one' AS n1, 1 AS id) p1
UNION ALL
SELECT * FROM (SELECT null AS n1, 2 AS id) p2) p
ON t.n1 IS NULL;
-- 等价于
SELECT * FROM ...
WHERE t.n1 IS NULL;
结果:
| t.n1 | t.id | p.n1 | p.id |
|---|---|---|---|
| NULL | 1 | one | 1 |
| NULL | 1 | NULL | 2 |
注意事项
- 连接顺序敏感:在执行 JOIN 操作时,ON 用于指定连接条件,WHERE 用于过滤。通常情况下先进行连接操作,再进行过滤操作
- 连接和过滤的区别:ON 中的连接条件用于确定如何将行从一个表与另一个表进行匹配;WHERE 中的过滤条件用于在连接后的结果中筛选满足条件的行
- NULL 值的处理:注意连接条件中的
NULL值、"NULL"和""的区别
NULL 值判断
-- 使用 IS NULL 或 IS NOT NULL 判断 NULL 值
SELECT * FROM t WHERE col IS NULL;
SELECT * FROM t WHERE col IS NOT NULL;
-- 判断 "NULL" 字符串或空字符串
SELECT * FROM t WHERE col = 'NULL';
SELECT * FROM t WHERE col <> 'NULL';
NULL 转换
-- 使用 COALESCE 将 NULL 值替换为默认值
SELECT COALESCE(col, default_value) FROM t;
-- 使用 IF 判断并替换 NULL 字符串
SELECT IF(col = 'NULL', 'IS NULL', 'NOT NULL') FROM t;
-- 使用 CASE WHEN 判断并替换 NULL 字符串
SELECT CASE WHEN col = 'NULL' THEN 'IS NULL' ELSE 'NOT NULL' END FROM t;
13. UNION 合并
| 版本 | 行为 |
|---|---|
| Hive 1.2.0 之前 | 仅支持 UNION ALL,重复的行不会被删除 |
| Hive 1.2.0 及更高版本 | UNION 默认从结果中删除重复的行 |
SELECT * FROM t1
UNION ALL
SELECT * FROM t2;
-- Hive 1.2.0+ 去重
SELECT * FROM t1
UNION
SELECT * FROM t2;
14. 模糊匹配 LIKE 和 RLIKE
-- LIKE:% 表示 0 个或多个字符,_ 表示 1 个字符
SELECT * FROM emp WHERE name LIKE 'J%';
-- RLIKE:支持正则表达式
SELECT * FROM emp WHERE sal RLIKE '^[234]';
15. 查询并写入集群文件
-- DIRECTORY 关键词用于写入集群文件
INSERT OVERWRITE DIRECTORY '/tmp/hdfs_out' SELECT a.* FROM invites a WHERE a.ds='2008-08-15';
-- 写入集群文件时指定格式
INSERT OVERWRITE DIRECTORY '/tmp/hdfs_out'
ROW FORMAT DELIMITED
FIELDS TERMINATED BY '|'
SELECT a.* FROM invites a WHERE a.ds='2008-08-15';
-- INTO 追加模式
INSERT INTO DIRECTORY '/tmp/hdfs_out' SELECT a.* FROM invites a WHERE a.ds='2008-08-15';
16. 查询并写入本地文件
-- LOCAL DIRECTORY 关键词用于写入本地文件
INSERT OVERWRITE LOCAL DIRECTORY '/tmp/local_out' SELECT a.* FROM pokes a;
INSERT INTO LOCAL DIRECTORY '/tmp/local_out' SELECT a.* FROM pokes a;
-- 格式化写入
INSERT OVERWRITE LOCAL DIRECTORY '/tmp/local_out'
ROW FORMAT DELIMITED
FIELDS TERMINATED BY ','
SELECT a.* FROM pokes a;
17. 查询并写入表
支持 INTO(追加)和 OVERWRITE(覆盖)两种模式。
注意: 追加插入表时建议使用
LEFT ANTI JOIN。插入时 Hive 只检查列数量,不检查字段名称是否一致,需要人工确保列对齐。
INSERT OVERWRITE TABLE events SELECT a.* FROM profiles a;
INSERT OVERWRITE TABLE events PARTITION(parColName) SELECT a.* FROM profiles a;
INSERT INTO TABLE events SELECT a.* FROM profiles a;
18. 查询并写入分区表(动态分区)
-- 启用动态分区
SET hive.exec.dynamic.partition = true;
-- 设置为非严格模式(允许全动态分区)
SET hive.exec.dynamic.partition.mode = nonstrict;
-- 动态插入分区(不指定具体值,留空表示动态)
INSERT INTO TABLE target_table
PARTITION (partition_col) -- 不指定具体值,留空表示动态
SELECT col1, col2, ..., partition_col
FROM source_table;
-- 多个分区字段:都不赋值,分区字段放在 SELECT 最后
INSERT INTO TABLE logs PARTITION (dt, region)
SELECT user_id, event, dt, region
FROM raw_logs;
19. 全表导出到文件
EXPORT TABLE default.events TO '/users/lijp/temp';
20. INSERT 插入
-- 标准写法
INSERT OVERWRITE TABLE events
SELECT a.bar, count(*)
FROM invites a
WHERE a.foo > 0
GROUP BY a.bar;
-- FROM 关键词前置写法
FROM invites a
INSERT OVERWRITE TABLE events
SELECT a.bar, count(*)
WHERE a.foo > 0
GROUP BY a.bar;
21. 多表插入
FROM 关键词前置的主要用途:从同一张表中用不同的筛选条件获取记录,对多个不同表进行插入。
FROM src
INSERT OVERWRITE TABLE dest1 SELECT src.* WHERE src.key < 100
INSERT OVERWRITE TABLE dest2 SELECT src.key, src.value WHERE src.key >= 100 AND src.key < 200
INSERT OVERWRITE TABLE dest3 PARTITION(ds='2008-04-08', hr='12') SELECT src.key WHERE src.key >= 200 AND src.key < 300
INSERT OVERWRITE LOCAL DIRECTORY '/tmp/dest4.out' SELECT src.value WHERE src.key >= 300;
22. 创建临时表(CTE)
-- 创建一个临时表
WITH tableName1 AS (SELECT * FROM tableName2)
SELECT * FROM tableName1;
-- 创建多个临时表
WITH tableName1 AS (SELECT * FROM otherTableName1),
tableName2 AS (SELECT * FROM otherTableName2)
SELECT *
FROM tableName1 AS t1
JOIN tableName2 AS t2 ON t1.id = t2.id;
23. 调用脚本进行流式处理
可以通过 Python 或 Shell 脚本对 Hive 数据进行流式处理。
样例文件 url.txt
http://www.baidu.com|测试规则|分类1|分类2|http://www.baidu.com/1231231231231231231231231312312313
步骤 1:建旧表并导入数据
-- 建旧表,存放样例文件
CREATE TABLE rule_old (
host STRING,
class1 STRING,
class2 STRING,
class3 STRING,
url STRING
)
ROW FORMAT DELIMITED
FIELDS TERMINATED BY '|';
-- 导入样例数据(注意:若文件分隔符与表字段分隔符不一致,则每行记录只对应第一列)
LOAD DATA LOCAL INPATH 'url.txt' OVERWRITE INTO TABLE rule_old;
步骤 2:编写 Python 脚本 line_split.py
import sys
for line in sys.stdin:
line = line.strip()
# 注意:这里接受的 line 为标准输入,使用 \t 分割,与建表语句中的字段分隔符无关
[host, class1, class2, class3, url] = line.split("\t")
# 注意:指定分隔符打印标准输出,分隔符必须与待写入表的字段分隔符一致,否则将写入第一列
print("\t".join([host, class1, class2, class3, url]))
步骤 3:建新表并执行转换写入
-- 建新表,存放转换后的样例数据
CREATE TABLE rule_new (
host1 STRING,
class11 STRING,
class22 STRING,
class33 STRING,
url1 STRING
)
ROW FORMAT DELIMITED
FIELDS TERMINATED BY '\t';
-- 处理文件并写入,AS 中的字段名称可随意指定,只根据字段数量进行检查
ADD FILE line_split.py;
INSERT OVERWRITE TABLE rule_new
SELECT TRANSFORM (host, class1, class2, class3, url)
USING 'python line_split.py'
AS (col1, col2, col3, col4, col5)
FROM rule_old;
24. 创建 UDF 函数
a. 创建临时函数
临时函数可以跨库运行。
-- 引入 jar 包
ADD JAR /user/lijp/tool/encrypt.jar;
-- 创建临时函数:CREATE TEMPORARY FUNCTION 函数名 AS 函数在 jar 包中的路径
CREATE TEMPORARY FUNCTION encrypt AS "com.fibodt.encrypt.RuleEncryptUtil.b";
b. 创建永久函数
永久函数需要使用
库名.函数名的方式调用。
CREATE FUNCTION encrypt AS "com.fibodt.encrypt.RuleEncryptUtil.b"
USING AS "/user/lijp/temp/encrypt.jar";
25. Hive 数据类型
a. 数值类型
| 数据类型 | 长度 | 范围 |
|---|---|---|
TINYINT | 1 字节 | -128 ~ 127 |
SMALLINT | 2 字节 | -32,768 ~ 32,767 |
INT | 4 字节 | -2,147,483,648 ~ 2,147,483,647 |
BIGINT | 8 字节 | -9,223,372,036,854,775,808 ~ 9,223,372,036,854,775,807 |
FLOAT | 4 字节 | 浮点型 |
DOUBLE | 8 字节 | 浮点型 |
DECIMAL | 17 字节 | 38 位,存储小数 |
b. 字符类型
| 数据类型 | 描述 |
|---|---|
STRING | 使用时通常用单引号或双引号引用,支持 C 样式转义 |
VARCHAR | 变长字符串,最大长度 65535 |
CHAR | 定长字符串,最大长度 255 |
c. 日期类型
| 数据类型 | 描述 |
|---|---|
TIMESTAMP | 支持 UNIX 时间戳,可选纳秒精度(精度 9) |
DATE | YYYY-MM-DD 格式 |
INTERVAL | 时间间隔 |
-- INTERVAL 使用示例
SELECT current_date() + INTERVAL '1-2' YEAR TO MONTH; -- 增加 1 年 2 个月
SELECT current_date() + INTERVAL '1' DAY; -- 增加 1 天
SELECT current_date() + INTERVAL '1' HOUR; -- 增加 1 小时
SELECT current_date() + INTERVAL '1' SECOND; -- 增加 1 秒
d. 布尔类型
| 数据类型 | 描述 |
|---|---|
BOOLEAN | true / false |
e. 复合类型
| 数据类型 | 描述 | 示例 |
|---|---|---|
ARRAY | 有序且同类型的数据集合,可用下标索引 | ARRAY('foo', 'bar') |
MAP | key-value 对,通过 key 索引取值 | MAP('first', 'John', 'last', 'Doe') |
STRUCT | 支持任意结构的组合,点号取值 | STRUCT('John', 'Doe') |
UNIONTYPE | 一组异构类型集合 | - |
26. Hive 运算符
a. 算术运算符
| 运算符 | 描述 |
|---|---|
A + B | A 和 B 相加 |
A - B | A 减去 B |
A * B | A 和 B 相乘 |
A / B | A 除以 B |
A % B | A 对 B 取余 |
A & B | A 和 B 按位取与 |
A | B | A 和 B 按位取或 |
A ^ B | A 和 B 按位取异或 |
~A | A 按位取反 |
b. 比较运算符
| 操作符 | 支持的数据类型 | 描述 |
|---|---|---|
A = B | 基本数据类型 | A 等于 B 返回 TRUE |
A <=> B | 基本数据类型 | A 和 B 都为 NULL 返回 TRUE,任一为 NULL 返回 NULL |
A <> B, A != B | 基本数据类型 | A 不等于 B 返回 TRUE,任一为 NULL 返回 NULL |
A < B | 基本数据类型 | A 小于 B 返回 TRUE,任一为 NULL 返回 NULL |
A <= B | 基本数据类型 | A 小于等于 B 返回 TRUE,任一为 NULL 返回 NULL |
A > B | 基本数据类型 | A 大于 B 返回 TRUE,任一为 NULL 返回 NULL |
A >= B | 基本数据类型 | A 大于等于 B 返回 TRUE,任一为 NULL 返回 NULL |
A [NOT] BETWEEN B AND C | 基本数据类型 | A 在 B 和 C 之间返回 TRUE |
A IS NULL | 所有数据类型 | A 为 NULL 返回 TRUE |
A IS NOT NULL | 所有数据类型 | A 不为 NULL 返回 TRUE |
IN(数值1, 数值2) | 所有数据类型 | A 在列表中返回 TRUE |
A [NOT] LIKE B | STRING | B 为简单正则:x% 以 x 开头,%x 以 x 结尾,%x% 包含 x |
A RLIKE B / A REGEXP B | STRING | B 为正则表达式,JDK 正则接口实现 |
c. 逻辑运算符
| 操作符 | 含义 |
|---|---|
AND | 逻辑与 |
OR | 逻辑或 |
NOT | 逻辑非 |
27. Hive 内置函数
a. 字符函数
| 返回值 | 函数 | 描述 |
|---|---|---|
STRING | concat(string|binary A, string|binary B…) | 按次序拼接字符串 |
INT | instr(string str, string substr) | 查找子字符串出现的位置 |
INT | length(string A) | 返回字符串长度 |
INT | locate(string substr, string str[, int pos]) | 从 pos 位置后查找 substr 首次出现位置 |
STRING | lower(string A) / upper(string A) | 转小写 / 大写 |
STRING | regexp_replace(string INITIAL_STRING, string PATTERN, string REPLACEMENT) | 正则替换 |
ARRAY | split(string str, string pat) | 按正则分割字符串 |
STRING | substr(string|binary A, int start, int len) | 截取子串 |
STRING | trim(string A) | 去除前后空格 |
MAP | str_to_map(text[, delimiter1, delimiter2]) | 字符串转 Map |
BINARY | encode(string src, string charset) | 按字符集编码为二进制 |
b. 类型转换函数
| 返回值 | 函数 | 描述 |
|---|---|---|
<type> | cast(expr AS <type>) | 类型转换,如 cast("1" AS BIGINT) |
BINARY | binary(string|binary) | 转换为二进制 |
c. 数学函数
| 返回值 | 函数 | 描述 |
|---|---|---|
DOUBLE | round(DOUBLE a) | 四舍五入取整 |
DOUBLE | round(DOUBLE a, INT d) | 四舍五入保留 d 位小数 |
BIGINT | floor(DOUBLE a) | 向下取整 |
DOUBLE | rand(INT seed) | 返回随机数,seed 为随机因子 |
DOUBLE | power(DOUBLE a, DOUBLE p) | a 的 p 次幂 |
DOUBLE | abs(DOUBLE a) | 绝对值 |
d. 日期函数
| 返回值 | 函数 | 描述 |
|---|---|---|
STRING | from_unixtime(bigint unixtime[, string format]) | 时间戳转 format 格式 |
INT | unix_timestamp() | 获取本地时区时间戳 |
BIGINT | unix_timestamp(string date) | yyyy-MM-dd HH:mm:ss 格式字符串转时间戳 |
STRING | to_date(string timestamp) | 返回日期部分 |
INT | year(string date) / month / day / hour / minute / second / weekofyear | 返回对应部分 |
INT | datediff(string enddate, string startdate) | 计算相差天数 |
STRING | date_add(string startdate, int days) | 加 days 天 |
STRING | date_sub(string startdate, int days) | 减 days 天 |
DATE | current_date | 当前日期 |
TIMESTAMP | current_timestamp | 当前时间戳 |
STRING | date_format(date/timestamp/string ts, string fmt) | 格式化日期 |
e. 集合函数
| 返回值 | 函数 | 描述 |
|---|---|---|
INT | size(Map<K,V>) | 返回 Map 键值对个数 |
INT | size(ARRAY) | 返回数组长度 |
ARRAY | map_keys(Map<K,V>) | 返回 Map 所有 key |
ARRAY | map_values(Map<K,V>) | 返回 Map 所有 value |
BOOLEAN | array_contains(ARRAY, value) | 数组是否包含 value |
ARRAY | sort_array(ARRAY) | 数组排序 |
f. 条件函数
| 返回值 | 函数 | 描述 |
|---|---|---|
T | if(boolean testCondition, T valueTrue, T valueFalseOrNull) | 条件判断,true 返回 valueTrue |
T | nvl(T value, T default_value) | value 为 NULL 返回 default_value |
T | COALESCE(T v1, T v2, …) | 返回第一个非 NULL 值 |
T | CASE a WHEN b THEN c [WHEN d THEN e]* [ELSE f] END | 等值判断 |
T | CASE WHEN a THEN b [WHEN c THEN d]* [ELSE e] END | 条件判断 |
BOOLEAN | isnull(a) | a 为 NULL 返回 true |
BOOLEAN | isnotnull(a) | a 非 NULL 返回 true |
g. 表生成函数
| 返回值 | 函数 | 描述 |
|---|---|---|
| N rows | explode(ARRAY) | 数组每个元素生成一行 |
| N rows | explode(MAP) | 每个键值对生成一行,包含 key 和 value 两列 |
| N rows | posexplode(ARRAY) | 与 explode 类似,额外返回元素位置 |
| N rows | stack(INT n, v_1, v_2, …, v_k) | k 列转 n 行,每行 k/n 个字段 |
| tuple | json_tuple(jsonStr, k1, k2, …) | 从 JSON 字符串中获取多个键,以元组返回 |
28. 行列转换
a. 行转列
| 函数 | 描述 |
|---|---|
concat | 拼接多列 |
concat_ws | 拼接多列,以指定字符串分割 |
collect_set | 聚合多行,返回去重集合 |
collect_list | 聚合多行,返回列表 |
b. 列转行
EXPLODE(col):将 Array 或 Map 结构拆分成多行LATERAL VIEW udtf(col) table_alias AS column_alias:与split、explode等 UDTF 一起使用,将一列数据拆成多行,可对拆分后数据聚合EXPLODE(col):将 hive 一列中复杂的 Array 或者 Map 结构拆分成多行。LATERAL VIEW udtf(col) table_alias AS column_alias:用于和split、explode、udtf一起使用,它能够将一列数据拆成多行数据,在此基础上可以对拆分后的数据进行聚合。table_alias是表的别名【可省】,column_alias是新生成的列名。该语句应当在from语句的后面。udtf(col):生成虚拟表,table_alias和column_alias对应了两种字段选择方式table_alias:虚拟表别名,需要一次性获得虚拟表中所有字段时使用,例如:table_alias.*(注意:table_alias本质上是只包含了一列,且这一列就是一个 struct 类型的列表) 示例:SELECT tableAlias.* FROM etl_fetch.5_utag_result_30day LATERAL VIEW explode(hosts) table_alias AS column_aliascolumn_alias:虚拟列别名,需要精确获得虚拟表中某一个字段时使用,例如:column_alias.字段名(注意:column_alias本质上是对table_alias的解包,获取了其内部的唯一的列,该列为 struct 类型) 示例:SELECT column_alias.score FROM etl_fetch.5_utag_result_30day LATERAL VIEW explode(hosts) table_alias AS column_alias
29. 窗口函数
a. 分析函数
| 关键字 | 描述 |
|---|---|
OVER() | 指定分析函数工作的数据窗口大小,随行变化 |
CURRENT ROW | 当前行 |
n PRECEDING | 往前 n 行数据 |
n FOLLOWING | 往后 n 行数据 |
UNBOUNDED PRECEDING | 从前面的起点 |
UNBOUNDED FOLLOWING | 到后面的终点 |
LAG(col, n) | 往前第 n 行数据 |
LEAD(col, n) | 往后第 n 行数据 |
NTILE(n) | 将有序分区分发到 n 个组中,返回组编号(从 1 开始) |
b. 聚合函数
| 函数 | 描述 |
|---|---|
RANK() | 排序相同时会重复,总数不变 |
DENSE_RANK() | 排序相同时会重复,总数减少 |
ROW_NUMBER() | 按顺序递增编号 |
c. 示例
SELECT name, subject, score,
RANK() OVER(PARTITION BY subject ORDER BY score DESC) AS rp,
DENSE_RANK() OVER(PARTITION BY subject ORDER BY score DESC) AS drp,
ROW_NUMBER() OVER(PARTITION BY subject ORDER BY score DESC) AS rmp
FROM score;
30. 排序
a. 全局排序(ORDER BY)
SELECT * FROM emp ORDER BY deptno [ASC | DESC];
b. 每个 Reduce 内部排序(SORT BY)
-- 设置 reduce 个数
SET mapreduce.job.reduces = 3;
-- 查看设置的 reduce 个数
SET mapreduce.job.reduces;
-- 根据部门编号降序查看员工信息
SELECT * FROM emp SORT BY deptno [ASC | DESC];
c. 每个分区内部排序(DISTRIBUTE BY + SORT BY)
注意: Hive 要求
DISTRIBUTE BY必须写在SORT BY之前。
SELECT * FROM emp DISTRIBUTE BY deptno SORT BY month;
d. CLUSTER BY
当 Reduce 排序和分区排序的字段相同时,可用 CLUSTER BY 简写:
-- 以下二者等价
SELECT * FROM dept_partition DISTRIBUTE BY deptno SORT BY deptno;
SELECT * FROM dept_partition CLUSTER BY deptno;
31. 空值处理
NVL:给值为 NULL 的数据赋默认值。
-- 格式:NVL(string1, replace_with)
-- 如果 string1 为 NULL,返回 replace_with;否则返回 string1
-- 两个参数都为 NULL 则返回 NULL
SELECT NVL(col, 'default_value') FROM t;
32. Hive 文件格式
a. TEXTFILE
Hive 默认格式,以 \n 为换行符,\001 为字段间隔,\002 为 ARRAY/STRUCT 等容器元素间隔,\003 为 MAP 键间隔。
CREATE TABLE test (
col1 INT,
col2 STRING
)
ROW FORMAT DELIMITED
FIELDS TERMINATED BY '\001'
COLLECTION ITEMS TERMINATED BY '\002'
MAP KEYS TERMINATED BY '\003'
LINES TERMINATED BY '\n'
STORED AS TEXTFILE;
b. SEQUENCEFILE
Hadoop API 提供的二进制文件格式,以 <key, value> 形式序列化。Key 为空,value 存放实际值,避免 MR map 阶段排序。比 Text 更紧凑,支持 Split,但无 Metadata,只能新增字段。
生产中基本不会用,k-v 格式比源文本格式占用磁盘更多。
CREATE TABLE test (
col1 INT,
col2 STRING
)
ROW FORMAT DELIMITED
FIELDS TERMINATED BY '\001'
COLLECTION ITEMS TERMINATED BY '\002'
MAP KEYS TERMINATED BY '\003'
LINES TERMINATED BY '\n'
STORED AS SEQUENCEFILE;
c. RCFILE
Hive 推出的面向列的存储格式,遵循”先按列划分,再垂直划分”的设计理念。
生产中用的少,行列混合存储,ORC 是它的升级版。
CREATE TABLE test (
col1 INT,
col2 STRING
)
ROW FORMAT DELIMITED
FIELDS TERMINATED BY '\001'
COLLECTION ITEMS TERMINATED BY '\002'
MAP KEYS TERMINATED BY '\003'
LINES TERMINATED BY '\n'
STORED AS RCFILE;
d. ORC
ORC(Optimized RCFile)在压缩编码和查询性能方面相比 RCFile 做了很多优化。Metadata 用 Protobuf 存储,支持 Schema 变更(新增/删除字段)。
生产中最常用,列式存储。
CREATE TABLE test (
col1 INT,
col2 STRING
)
ROW FORMAT DELIMITED
FIELDS TERMINATED BY '\001'
COLLECTION ITEMS TERMINATED BY '\002'
MAP KEYS TERMINATED BY '\003'
LINES TERMINATED BY '\n'
STORED AS ORC;
e. PARQUET
源自 Google Dremel 系统,Apache Parquet 设计用于存储嵌套式数据(ProtocolBuffer、Thrift、JSON 等),能以列式格式高效压缩编码,使用更少 IO 操作。
相比 ORC 的优势:能够透明地将 Protobuf 和 Thrift 类型数据进行列式存储,支持 Schema 变更。
生产中最常用,列式存储。
CREATE TABLE test (
col1 INT,
col2 STRING
)
ROW FORMAT DELIMITED
FIELDS TERMINATED BY '\001'
COLLECTION ITEMS TERMINATED BY '\002'
MAP KEYS TERMINATED BY '\003'
LINES TERMINATED BY '\n'
STORED AS PARQUET;
f. AVRO
二进制文件格式,更为紧凑,读取大量数据时提供更好的序列化/反序列化性能。天生带有 Schema 定义,多个 Hadoop 子项目(Pig、Hive、Flume、Sqoop、Hcatalog)均支持。
生产中几乎不用的二进制文件格式。
CREATE TABLE test (
col1 INT,
col2 STRING
)
ROW FORMAT DELIMITED
FIELDS TERMINATED BY '\001'
COLLECTION ITEMS TERMINATED BY '\002'
MAP KEYS TERMINATED BY '\003'
LINES TERMINATED BY '\n'
STORED AS AVRO;
g. 自定义文件格式
通过继承 InputFormat 和 OutputFormat 来自定义文件格式。
以读取 Base64Text 文件为例:
CREATE TABLE base64example (line STRING)
STORED AS
INPUTFORMAT 'org.apache.hadoop.hive.contrib.fileformat.base64.Base64TextInputFormat'
OUTPUTFORMAT 'org.apache.hadoop.hive.contrib.fileformat.base64.Base64TextOutputFormat';
33. 常用设置
注意: 凡是涉及文件大小的参数,均以字节为单位。
1. 引擎设置为 MR 时的优化
动态分区
-- 打开动态分区后,允许所有分区都是动态分区模式
SET hive.exec.dynamic.partition.mode = nonstrict;
-- 是否启动动态分区
SET hive.exec.dynamic.partition = true;
-- 设置每个任务允许创建动态分区的最大数量
SET hive.exec.max.dynamic.partitions = 1000000;
-- 设置每个节点上动态分区的最大数量
SET hive.exec.max.dynamic.partitions.pernode = 10000000;
小文件合并
文件数目小,容易在文件存储端造成瓶颈,给 HDFS 带来压力,影响处理效率。可以通过合并 Map 和 Reduce 的结果文件来消除影响。
-- 设置 map 端输出进行合并,默认为 true
SET hive.merge.mapfiles = true;
-- 设置 reduce 端输出进行合并,默认为 false
SET hive.merge.mapredfiles = true;
并行执行与聚合
-- 开启并行执行:MapReduce 阶段、抽样阶段、合并阶段、limit 阶段等并行执行
SET hive.exec.parallel = true;
-- 关闭并发,防止锁表
SET hive.support.concurrency = false;
-- 是否在 map 端进行聚合,默认为 true,可防止数据倾斜
SET hive.map.aggr = true;
调整 Mapper 和 Reducer 个数
减少 Map 数:
下面三个参数确定合并文件块的大小:大于 128MB 的按 128MB 分隔,小于 128MB 且大于 100MB 的按 100MB 分隔,小于 100MB 的进行合并。
SET mapred.max.split.size = 100000000;
SET mapred.min.split.size.per.node = 100000000;
SET mapred.min.split.size.per.rack = 100000000;
-- 执行前进行小文件合并
SET hive.input.format = org.apache.hadoop.hive.ql.io.CombineHiveInputFormat;
增加 Map 数:
当 input 文件都很大、任务逻辑复杂、map 执行非常慢时,可以增加 Map 数来使每个 map 处理的数据量减少,提高任务执行效率。
SET mapred.reduce.tasks = 10;
调整 Reducer 个数:
-- 增加 reducer 数
SET mapreduce.job.reduces = ?;
-- 每个 reduce 任务处理的数据量,默认 1GB
SET hive.exec.reducers.bytes.per.reducer = 1000000000;
-- 每个任务最大的 reduce 数,默认为 999
SET hive.exec.reducers.max = 1009;
-- 方法一:调整每个 reduce 处理的数据量(如 500MB)
SET hive.exec.reducers.bytes.per.reducer = 500000000;
-- 方法二:直接设置每个 job 的 reduce 个数
SET mapreduce.job.reduces = 15;
其他 MR 优化
-- 严格模式:默认关闭,禁止三种类型查询:分区表不指定分区查询、笛卡尔积、使用 ORDER BY
SET mapred.mode = strict;
-- JVM 重用:使用派生 JVM 来执行 map 和 reduce 任务
SET mapred.job.jvm.numtasks = 1;
-- Map Join 优化:不太大的表直接通过 map 过程做 join
SET hive.auto.convert.join = true;
SET hive.auto.convert.join.noconditionaltask = true;
2. 引擎设置为 Spark 时的优化
若内存允许,优先考虑使用 Spark。
SET hive.execution.engine = spark;
-- 合并小文件(Hive on Spark 下的选项)
SET hive.merge.sparkfiles = true;
-- Hive on Spark 下改用在内存中存储的近似大小,迁移时要适当调高(如 100~200MB)
SET hive.auto.convert.join.noconditionaltask.size = 100000000;
3. 通用优化参数汇总
-- 简单查询仅包含一个 GROUP BY 和 ORDER BY 时,可设为 1 或 2
SET hive.optimize.reducededuplication.min.reducer = 4;
-- 数据已按相同 key 聚合时,去除多余 map/reduce 作业
SET hive.optimize.reducededuplication = true;
-- 合并小文件相关
SET hive.merge.smallfiles.avgsize = 16000000;
SET hive.merge.size.per.task = 256000000;
SET hive.merge.sparkfiles = true;
SET hive.merge.orcfile.stripe.level = true;
-- Map Join 优化
SET hive.auto.convert.join = true;
SET hive.auto.convert.join.noconditionaltask = true;
-- 可转化为 HashMap 放入内存的表大小(Spark 建议 200MB)
SET hive.auto.convert.join.noconditionaltask.size = 20971520;
-- 如果数据按 join 的 key 分桶,Hive 将简单优化 inner join(官方推荐关闭)
SET hive.optimize.bucketmapjoin = false;
SET hive.optimize.bucketmapjoin.sortedmerge = false;
-- 所有 map 任务可用作 Hashtable 的内存百分比(OOM 时调小,默认 0.5)
SET hive.map.aggr.hash.percentmemory = 0.5;
-- map 端聚合(与 GROUP BY 相关),会用更多内存
SET hive.map.aggr = true;
-- 关闭对所有字段排序(Hive 0.13 存在 bug)
SET hive.optimize.sort.dynamic.partition = false;
-- 新创建的表/分区是否自动计算统计数据
SET hive.stats.autogather = true;
SET hive.stats.fetch.column.stats = true;
SET hive.compute.query.using.stats = true;
-- ORDER BY LIMIT 查询中分配存储 Top K 的内存比例
SET hive.limit.pushdown.memory.usage = 0.4;
-- 是否开启自动使用索引
SET hive.optimize.index.filter = true;
-- 单个 reduce 处理的数据量(影响 reduce 数量)
SET hive.exec.reducers.bytes.per.reducer = 67108864;
-- Map Join 任务 HashMap 中 key 对应 value 数量
SET hive.smbjoin.cache.rows = 10000;
-- 将只有 SELECT、FILTER、LIMIT 转化为 FETCH,减少等待时间
SET hive.fetch.task.conversion = more;
SET hive.fetch.task.conversion.threshold = 1073741824;
-- 显示列名
SET hive.cli.print.header = true;
-- 不显示表名
SET hive.resultset.use.unique.column.names = false;
34. SparkSQL 的 Tips
PySpark 环境下换行
df = spark.sql("""
SELECT *
FROM tableName1
JOIN tableName2
ON tableName1.id = tableName2.id
""")
Spark 环境下换行
val df = spark.sql("""
SELECT *
FROM tableName1
JOIN tableName2
ON tableName1.id = tableName2.id
""")
35. Hive 安装与配置
a. 通过 Homebrew 安装
brew install hive
b. 系统环境配置
编辑 ~/.bash_profile:
vim ~/.bash_profile
Hive 系统变量
# hive
HIVE_HOME=/usr/local/Cellar/hive/3.1.3
PATH=$HIVE_HOME:$PATH
PATH=$HIVE_HOME/bin:$PATH
export PATH
Hadoop 系统变量
# hadoop
HADOOP_HOME=/usr/local/Cellar/hadoop/3.3.2
PATH=$HADOOP_HOME/bin:$PATH
PATH=$HADOOP_HOME/sbin:$PATH
export PATH
Java 系统变量
# java
JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk1.8.0_281.jdk/Contents/Home
PATH=$JAVA_HOME:$PATH
PATH=$JAVA_HOME/bin:$PATH
export PATH
使配置生效
source ~/.bash_profile
c. Hive 环境配置
编辑 $HIVE_HOME/libexec/conf/hive-site.xml:
vim $HIVE_HOME/libexec/conf/hive-site.xml
i. 配置元数据库 MySQL
注意: 端口不能有错,
createDatabaseIfNotExist=true表示初始化时自动创建。
<property>
<name>hive.metastore.local</name>
<value>true</value>
</property>
<property>
<name>javax.jdo.option.ConnectionURL</name>
<value>jdbc:mysql://localhost:3306/hive?createDatabaseIfNotExist=true</value>
</property>
ii. 配置数据库连接驱动
<property>
<name>javax.jdo.option.ConnectionDriverName</name>
<value>com.mysql.jdbc.Driver</value>
</property>
iii. 配置数据库账号密码
<property>
<name>javax.jdo.option.ConnectionUserName</name>
<value>root</value>
</property>
<property>
<name>javax.jdo.option.ConnectionPassword</name>
<value>5718515abc</value>
</property>
iv. 配置数据库 Schema 相关参数
<property>
<name>hive.metastore.schema.verification</name>
<value>false</value>
</property>
<property>
<name>datanucleus.schema.autoCreateAll</name>
<value>true</value>
</property>
v. 配置执行计划与临时文件目录
Hive 用来存储不同阶段的 Map/Reduce 执行计划目录,同时也存储中间输出结果。
<property>
<name>hive.exec.local.scratchdir</name>
<value>/tmp/hive</value>
</property>
<property>
<name>hive.downloaded.resources.dir</name>
<value>/tmp/hive</value>
</property>
<property>
<name>hive.server2.logging.operation.log.location</name>
<value>/tmp/hive</value>
</property>
vi. 配置数据存储目录
后续创建的数据库、表都会在该目录下,该目录若没有写权限会导致建库建表失败。
<property>
<name>hive.metastore.warehouse.dir</name>
<value>/Users/lijp/hive/warehouse</value>
</property>
vii. 【可选】配置 Beeline 免密认证
设置为 NONE 可免密登录。注意: 不能与账户密码同时配置。
<property>
<name>hive.server2.authentication</name>
<value>NONE</value>
<description>
Expects one of [nosasl, none, ldap, kerberos, pam, custom].
Client authentication types.
NONE: no authentication check
LDAP: LDAP/AD based authentication
KERBEROS: Kerberos/GSSAPI authentication
CUSTOM: Custom authentication provider
PAM: Pluggable authentication module
NOSASL: Raw transport
</description>
</property>
viii. 【可选】配置 Beeline 账户密码
注意: 不能与
hive.server2.authentication同时配置。
<property>
<name>hive.server2.thrift.client.user</name>
<value>root</value>
<description>Username to use against thrift client</description>
</property>
<property>
<name>hive.server2.thrift.client.password</name>
<value>123456</value>
<description>Password to use against thrift client</description>
</property>
配置后的登录方式:
# 方式一:命令行直接登录
sudo hadoop fs -chmod -R 777 /tmp/hive && beeline -u jdbc:hive2://localhost:10000 -n root -p 123456
# 方式二:进入 beeline 命令行后连接
beeline
!connect jdbc:hive2://localhost:10000 root 123456
d. 常见报错与解决方案
i. 元数据库表缺失
报错:
java.sql.SQLSyntaxErrorException: Table 'hive.version' doesn't exist
原因: MySQL 中对应的元数据库未正确初始化。
方案 1 - 自动初始化:
schematool -dbType mysql -initSchema
方案 2 - 手动初始化:
# 切换至对应目录
cd $HIVE_HOME/libexec/scripts/metastore/upgrade/mysql
# 打开 MySQL 命令行
mysql -u root -p
# 切换至元数据库并执行初始化脚本
use hive;
source /usr/local/Cellar/hive/3.1.3/libexec/scripts/metastore/upgrade/mysql/hive-schema-3.1.0.mysql.sql;
ii. 无写入权限
报错:
Exception in thread "main" java.lang.RuntimeException: The dir: /tmp/hive on HDFS should be writable. Current permissions are: rwxr-xr-x
原因: /tmp/hive 无写入权限。
解决:
sudo hadoop fs -chmod -R 777 /tmp
iii. 无建库建表权限
报错:
FAILED: Execution Error, return code 1 from org.apache.hadoop.hive.ql.exec.DDLTask. MetaException(message:Unable to create database path file:/user/hive/warehouse/test.db, failed to create database test)
原因: 对应目录 /user/hive/warehouse 无写入权限。
解决: 修改 hive-site.xml,改为有写入权限的目录:
<property>
<name>hive.metastore.warehouse.dir</name>
<value>/Users/lijp/hive/warehouse</value>
</property>