HQL DDL语句介绍:
它指的是 数据定义语言, 主要是用来 操作数据库, 数据表, 字段等的. 这里我们学习DDL语句, 主要就是学习 Hive如何建表. 它(Hive如何建表)非常重要, 是你学好Hive的第1课.
建表语法:
create [external] table [if not exists] 表名(
列名 数据类型 [ comment '描述信息'],
列名 数据类型 [ comment '描述信息'],
列名 数据类型 [ comment '描述信息'],
......
) comment '表的描述信息'
分区
分桶(桶内排序 + 分桶个数)
行格式切割符
存储格式(行存储, 列存储)
存储位置
表的属性信息;
常用的数据类型:
原生类型:
int 整数
double 小数
string 字符串
timestamp 时间戳
date 日期(年月日)
复杂类型:
array 数组类型
map 字段(映射)类型
struct 结构体
union 联合体
Hive的本质:
把HDFS文件映射成一张Hive表, 然后通过写 类SQL的语句来操作它(HDFS文件), 底层会被解析成MR程序, 交由Yarn来调度执行.
案例1: 默认切割符
-- ---------------------------- 案例1: 默认切割符 ----------------------------
-- 结论: Hive表默认切割符是'\001'符号, 在windows中显示为 SOH, 在HDFS中显示为 小口, 在Linux中显示为 ^A
-- 0. 建库.
create database day06;
use day06;
show tables;
-- 1. 建表, 表字段, 数据类型等要和 HDFS源文件保持一致.
drop table team_ace_player;
create table team_ace_player ( -- 表名无需和HDFS源文件保持一致, 见名知意即可.
id int comment '编号',
team_name string comment '队名',
player_name string comment '玩家名字'
) comment '战队ACE玩家信息';
-- 2. 把源文件上传到到Hive表所在的 HDFS路径下.
-- 3. 查询表数据.
select * from team_ace_player;
-- 4. 如何查看表结构信息.
desc team_ace_player; -- 发现注释信息乱码, 原因是: 我们的Hive表是UTF-8码表, Hive的元数据存在MySQL中, 用的GBK码表, 码表不一致导致的.
show create table team_ace_player;
desc formatted team_ace_player; -- 查看表结构详细信息(掌握), 其中: Table Type 表示表类型, MANAGED_TABLE(受管理表, 即: 内部表), EXTERNAL_TABLE(外部表)
案例2: 解决Hive注释中文乱码问题
-- ---------------------------- 案例2: 解决Hive注释中文乱码问题 ----------------------------
-- 产生原因: 我们的Hive表是UTF-8码表, Hive的元数据存在MySQL中, 用的GBK码表, 码表不一致导致的.
-- 解决方案: 保持码表一致即可, 我们去MySQL中修改Hive元数据信息的码表, 改为: UTF-8
-- 具体步骤:
-- Step1: 先通过DataGrip连接node1机器的MySQL, 账号: root, 密码: 123456 当然, 也可以直接在CRT中连接MySQL数据库.
-- Step2: 在MySQL中执行如下的内容即可, 详见MySQL的文件. 细节: 还要去hive-site.xml文件中修改下码表.
-- Step3: 重启metastore服务 和 hiveserver2服务.
-- Step4: 在Hive中把刚才的表删掉, 然后重建, 再次查询, 注释(描述信息)不乱码了.
案例3: 指定切割符
-- ---------------------------- 案例3: 指定切割符 ----------------------------
-- 1. 建表, 指定分隔符为 '\t'
create table t_archer (
id string comment 'ID',
name string comment '英雄',
hp_max string comment '最大生命',
mp_max string comment '最大法力',
attack_max string comment '最高物攻',
defense_max string comment '最大物防',
attack_range string comment '攻击范围',
role_main string comment '主要定位',
role_assist string comment '次要定位'
) comment '射手表'
row format delimited fields terminated by '\t'; -- 行格式切割 字段分隔符为 '\t'
-- 2. 上传源文件到该Hive表的HDFS路径下.
-- 3. 查询表数据.
select * from t_archer;
案例4: 其它的建表方式
-- ---------------------------- 案例4: 其它的建表方式 ----------------------------
-- 1. 查询所有数据表.
show tables;
-- 2. 原表
select * from t_archer; -- 10条数据.
-- 3. 备份 t_archer表, 要求: 字段名, 数据类型, 数据都都保持一致.
create table t1 as select * from t_archer; -- 底层会转成MR程序, 备份表和原表, 字段名, 数据类型, 数据都会保持一致. 用的是默认分隔符: '\001'
create table t2 row format delimited fields terminated by ','
as select * from t_archer; -- 底层会转成MR程序, 备份表和原表, 字段名, 数据类型, 数据都会保持一致. 用的是指定分隔符: ','
-- 4. 备份 t_archer表, 要求: 字段名, 数据类型, 保持一致, 没有数据, 即: 空表.
create table t3 like t_archer; -- 底层不会转MR.
-- 5. 备份完之后, 查询表数据.
select * from t1;
select * from t2;
select * from t3;
案例5: 修改表 相关操作
-- ---------------------------- 案例5: 修改表 相关操作 ----------------------------
show tables;
-- 1. 修改表名, t1 => t_archer_backup1
alter table t1 rename to t_archer_backup1;
-- 2. 修改表 t_archer_backup1 的存储路径.
alter table t_archer_backup1 set location '/wordcount/aa';
select * from t_archer_backup1;
desc formatted t_archer_backup1;
-- 3. 修改表属性, 例如: 内部表 和 外部表的相互转换.
-- 3.1 把内部表修改为外部表.
alter table t_archer_backup1 set tblproperties('EXTERNAL'='TRUE'); -- 固定格式, 必须大写.
-- 3.2 把外部表修改为内部表.
alter table t_archer_backup1 set tblproperties('EXTERNAL'='FALSE'); -- 固定格式, 必须大写.
-- 3.3 查看表属性信息.
desc formatted t_archer_backup1; -- Table Type表示表的类型, managed_table(内部表), external_table(外部表)
-- 3.4 添加列
desc t_archer_backup1;
alter table t_archer_backup1 add columns (address string, hobby string); -- 新增两列.
-- 3.5 修改列名
alter table t_archer_backup1 change address addr string;
-- 3.6 删除表.
show tables; -- 查看所有数据表.
drop table t3; -- 删除表.
-- 3.7 (清空)删除表数据
select * from t_archer_backup1;
-- delete from t_archer_backup1; -- 报错, Hive不支持这种删除方式.
truncate table t_archer_backup1; -- 删除表数据.
drop table t3; -- 删除表
-- 3.8 尝试用 truncate 方式, 清空 外部表.
desc formatted team_ace_player;
alter table team_ace_player set tblproperties('EXTERNAL'='TRUE'); -- 将表改为 外部表.
select * from team_ace_player; -- 有数据.
-- 尝试清空外部表, 报错, truncate 只能情况 内部表.
truncate table team_ace_player;
案例6: 内部表 和 外部表的区别
-- ---------------------------- 案例6: 内部表 和 外部表的区别 ----------------------------
/*
问题: 内部表 和 外部表的区别是什么?
答案:
1. 格式不同.
直接创建Hive表默认就是内部表, 如果要创建外部表, 必须用 external 关键字.
2. 删除时是否会删除源文件.
删除内部表的时候, 除了会删除元数据, 还会删除源文件.
删除外部表的时候, 只会删除元数据, 但是源文件(HDFS上)还在.
3. 应用场景不同.
删除Hive的时候, 如果也要同步删除源文件, 就用内部表.
否则就用外部表, 例如: Hbase 和 Hive的映射.
*/
-- 1. 建表, 内部表.
create table t_archer_inner (
id string comment 'ID',
name string comment '英雄',
hp_max string comment '最大生命',
mp_max string comment '最大法力',
attack_max string comment '最高物攻',
defense_max string comment '最大物防',
attack_range string comment '攻击范围',
role_main string comment '主要定位',
role_assist string comment '次要定位'
) comment '射手表'
row format delimited fields terminated by '\t';
-- 2. 上传文件, 查询表数据.
select * from t_archer_inner; -- 10条.
-- 3. 建表, 外部表.
create external table t_archer_outer (
id string comment 'ID',
name string comment '英雄',
hp_max string comment '最大生命',
mp_max string comment '最大法力',
attack_max string comment '最高物攻',
defense_max string comment '最大物防',
attack_range string comment '攻击范围',
role_main string comment '主要定位',
role_assist string comment '次要定位'
) comment '射手表'
row format delimited fields terminated by '\t';
-- 4. 上传文件, 查询表数据.
select * from t_archer_outer; -- 10条
-- 查看所有数据表, 如果这里边没有, 说明Hive的元数据删除了.
show tables;
-- 5. 删除内部表 和 外部表, 观察: Hive是否还有表的信息(元数据), HDFS上源文件是否还存在.
drop table t_archer_inner;
drop table t_archer_outer;
-- 6. 思考: 如果删除了外部表, 又把外部表创建了出来, 里边有数据吗?
-- 答案: 如果表名, 数据类型, 字段数量等和原表(删除前的外部表)一致, 则重新创建表之后, 直接就有数据.
-- 7. 扩展: 内部表 和 外部表之间如何互相转换.
desc formatted t_archer_outer;
-- 外部表 转 内部表
alter table t_archer_outer set tblproperties('EXTERNAL'='FALSE');
-- 内部表 转 外部表
alter table t_archer_outer set tblproperties('EXTERNAL'='TRUE');
案例7: 分区表之 静态分区
-- ---------------------------- 案例7: 分区表之 静态分区 ----------------------------
/*
分区表详解:
概述/作用:
1. 分区 = 分文件夹, 即: 把1个整体 拆分成 n个文件夹, 避免全表扫描, 提高查询效率.
2. 例如: 公司成立10年所有的数据都在1个文件夹中放着, 查找数据, 每次都要全表扫描, 效率相对较低.
可以按照 年月分区, 把每年每月的数据都放一起, 这样查找时只要找指定的内容即可, 降低扫描次数, 提高效率.
格式:
create table 表名(
) partitioned by(分区字段1 数据类型, 分区字段2 数据类型...)
row format ......;
细节:
分区字段必须是表中没有的字段.
*/
-- 1. 建表 t_all_hero, 用来存储王者荣耀所有的英雄数据(射手, 法师, 刺客, 战士, 坦克, 辅助)
create table t_all_hero (
id string comment 'ID',
name string comment '英雄',
hp_max string comment '最大生命',
mp_max string comment '最大法力',
attack_max string comment '最高物攻',
defense_max string comment '最大物防',
attack_range string comment '攻击范围',
role_main string comment '主要定位',
role_assist string comment '次要定位'
) comment '王者荣耀英雄表'
row format delimited fields terminated by '\t'; -- 行格式切割 字段分隔符为 '\t'
-- 2. 把所有的英雄数据文件(例如: 射手, 法师, 刺客, 战士, 坦克, 辅助)都传入到该表所在的HDFS路径下.
-- 3. 查看表数据.
select * from t_all_hero;
-- 4. 新需求: 查询所有的 射手数据.
select * from t_all_hero where role_main='archer'; -- 10条
-- 5. 思考上述的查询方式, 效率高不高?
-- 答案: 不高, 因为需要全表扫描, 可以用分区表解决, 把每一类英雄数据放到一起, 这样查找某一类的时候, 可以精准查找, 避免全表扫描.
-- 6. 创建分区表, 按照 角色主要定位 进行分区(分文件夹)
create table t_all_hero_part (
id string comment 'ID',
name string comment '英雄',
hp_max string comment '最大生命',
mp_max string comment '最大法力',
attack_max string comment '最高物攻',
defense_max string comment '最大物防',
attack_range string comment '攻击范围',
role_main string comment '主要定位',
role_assist string comment '次要定位'
)
comment '王者荣耀英雄表'
partitioned by (role string) -- 按照角色主要定位分区, 分区字段必须是表中没有的字段
row format delimited fields terminated by '\t';
-- 7. 往分区表中添加数据, 静态分区, 即: 分区字段 = 值, 类似于 静态IP, 都是手动写死的.
load data local inpath '/export/data/hivedata/archer.txt' into table t_all_hero_part partition (role='sheshou');
load data local inpath '/export/data/hivedata/assassin.txt' into table t_all_hero_part partition (role='cike');
load data local inpath '/export/data/hivedata/mage.txt' into table t_all_hero_part partition (role='fashi');
load data local inpath '/export/data/hivedata/support.txt' into table t_all_hero_part partition (role='fuzhu');
load data local inpath '/export/data/hivedata/tank.txt' into table t_all_hero_part partition (role='tanke');
load data local inpath '/export/data/hivedata/warrior.txt' into table t_all_hero_part partition (role='zhanshi');
-- 8. 查询表数据.
select * from t_all_hero; -- 不分区
select * from t_all_hero where role_main='archer'; -- 全表扫描, 效率低.
select * from t_all_hero_part; -- 分区
select * from t_all_hero_part where role='sheshou'; -- 精准扫描, 效率相对较高.
案例8: 分区表之 动态分区
-- ---------------------------- 案例8: 分区表之 动态分区 ----------------------------
-- 细节: 动态分区时, 要关闭严格模式, 因为严格模式要求: 在动态分区的时候, 至少要有1个静态分区.
set hive.exec.dynamic.partition.mode=nonstrict; -- 关闭严格模式
-- 例如: partition(role) 动态分区, partition(role='sheshou') 静态分区, partition(role='sheshou', year,month) 1个静态分区, 2个动态分区
-- 1. 建表, 动态分区表.
create table t_all_hero_part_dynamic (
id string comment 'ID',
name string comment '英雄',
hp_max string comment '最大生命',
mp_max string comment '最大法力',
attack_max string comment '最高物攻',
defense_max string comment '最大物防',
attack_range string comment '攻击范围',
role_main string comment '主要定位',
role_assist string comment '次要定位'
)
comment '王者荣耀英雄表'
partitioned by (role string) -- 按照角色主要定位分区, 分区字段必须是表中没有的字段
row format delimited fields terminated by '\t';
-- 2. 往(动态)分区表中添加数据, insert + select 方式.
insert into t_all_hero_part_dynamic partition (role) -- 动态分区
select *, role_main from t_all_hero; -- role_main是分区列, 内容一样的数据会被放到一起.
-- 3. 查看结果.
select * from t_all_hero_part_dynamic;
案例9: 分区表之 总结
-- ---------------------------- 案例9: 分区表之 总结 ----------------------------
/*
分区总结:
1. 分区 = 分文件夹, 目的是: 避免全表扫描, 降低扫描的次数, 提高查询效率.
2. 分区字段必须是表中 没有的 字段.
3. Hive默认开启了严格模式, 要求: 动态分区时, 至少指定1个静态分区.
partition(role) 动态分区
partition(role='fashi') 静态分区
4. 实际开发中, 可以是单级分区, 也可以是多级分区, 多级分区一般不会超过3层, 且大多数是 年月日分区.
*/
-- 0. 创建学生分区表.
create table stu_part(
id int,
name string,
gender string,
age int,
sno string
)
partitioned by (year string, month string) -- 按照年, 月分区.
row format delimited fields terminated by ',';
-- 1. 往学生分区表中添加数据.
insert into stu_part partition (year='2023', month='05') -- 静态分区
select * from stu;
select * from stu_part;
-- 2. 查看表所有的分区.
show partitions stu_part;
-- 3. 手动添加分区
alter table stu_part add partition(year='2023', month='03'); -- 手动添加1个分区
alter table stu_part add
partition(year='2023', month='01')
partition(year='2022', month='01')
partition(year='2022', month='03')
partition(year='2021', month='05')
partition(year='2023', month='09'); -- 手动添加多个分区
-- 4. 修改分区
alter table stu_part partition (year='2023', month='09') rename to partition (year='2021', month='11');
-- 5. 删除分区.
alter table stu_part drop partition(month='01'); -- 删除(每年)所有的 1月分区
alter table stu_part drop partition(year='2021'); -- 删除2021年所有的分区
alter table stu_part drop partition(year='2023',month='03'); -- 删除2023年03月的分区
案例10: 分桶表
-- ---------------------------- 案例10: 分桶表 ----------------------------
/*
分桶表相关:
概述:
分桶 = 分文件.
目的/好处:
1. 把1个整体(大文件) 拆分成 n个小文件, 减少join的次数, 提高查询效率.
2. 方便采样, 抽样.
细节:
1. 分桶字段必须是表中已有的字段.
2. 分桶表不能通过load方式加载数据, 因为要把文件拆分成n份, 所以必须通过 insert + select方式实现, 它的底层会转MR.
3. 分桶的原理: 哈希取模法
分桶的公式: 分桶字段的哈希值 % 桶的个数 = 结果, 结果是啥, 就进那个桶
例如: 按照性别(男, 女)分桶, 分成2个桶, 执行流程如下:
select abs(hash('男')) % 2 => 0, 1
select abs(hash('男')) % 2 => 0, 1
4. 哈希值 就是 根据字段的内容计算出来的1个整数(可能是正数, 也可能是负数), 类似于: 每个学生都有自己的 学号一样.
5. 同一对象(内容)哈希值一定相同, 不同对象哈希值一般不同.
例如: 重地和通话, 儿女和农丰... 去Java中执行.
*/
-- 0. 分桶原理, 哈希取模法
select hash('男'); -- -27446
select hash('女'); -- -28845
select abs(hash('男')); -- 27446
select abs(hash('男')) % 2; -- 0
select abs(hash('女')) % 2; -- 1
-- 1. 创建普通表.
create database day07;
use day07;
show tables;
create table stu(
id int,
name string,
gender string,
age int,
sno string
) row format delimited fields terminated by ',';
-- 2. 添加数据到普通表, 查询表数据.
select * from stu;
-- 3. 创建分桶表(不排序), 指定: 按照性别分桶, 分成2个桶.
create table stu_bucket(
id int,
name string,
gender string,
age int,
sno string
) clustered by (gender) into 2 buckets -- 按照性别分桶, 分成2个桶, 分桶字段必须是表中已有的字段
row format delimited fields terminated by ',';
-- 4. 直接上传文件到分桶表中, 观察, 分桶表中是否有数据?
-- 结论: 有数据, 但是没有按照我们的要求分成2个桶, 思考: 为啥?
-- 答案: 分桶表必须通过 insert + select 方式添加数据, 底层会转MR, 把数据分成n个文件.
insert into stu_bucket select * from stu;
select * from stu_bucket; -- 22条数据, HDFS路径下 2个文件.
-- 6. 创建分桶(排序)表, 按照: 性别分, 桶内按照年龄降序排列.
create table stu_bucket_sort(
id int,
name string,
gender string,
age int,
sno string
) clustered by (gender) sorted by (age desc) into 2 buckets -- 按照性别分桶, 分成2个桶, 桶内数据按照 年龄降序排列.
row format delimited fields terminated by ',';
-- 7. 往分桶排序表中添加数据, 然后查看结果.
insert into stu_bucket_sort select * from stu; -- 普通表的数据 => 分桶排序表中.
select * from stu_bucket_sort;
案例11: 复杂类型 array
-- ---------------------------- 案例11: 复杂类型 array ----------------------------
-- 源文件中数据格式为: "zhangsan beijing,shanghai,tianjin,hangzhou" 建表存储.
-- 1. 建表, 用于存储上述格式的数据.
drop table test_array;
create table test_array(
name string,
addrs string
) row format delimited
fields terminated by '\t'; -- 切割后, 数据格式为: "zhangsan", "beijing,shanghai,tianjin,hangzhou"
-- 2. 上传源文件, 查看表数据.
select * from test_array;
-- 3. 查找包含 hangzhou 这个城市的 数据信息.
select * from test_array where addrs like '%hangzhou%';
-- 4. 查找每个人, 都去过几个城市, 第2个去的城市是哪里.
select * from test_array;
-- 5. 虽然能实现需求, 但是比较麻烦, 针对于这个特定的数据格式, 我们用复杂类型 array来表示.
create table test_array(
name string,
addrs array<string> -- 表示 所有去过的城市的数组. 数据格式为: [值1, 值2, 值3...]
) row format delimited
fields terminated by '\t' -- 切割后, 数据格式为: "zhangsan", "beijing,shanghai,tianjin,hangzhou"
collection items terminated by ','; -- 切割后, 数据格式为: "zhangsan", ["beijing", "shanghai", "tianjin", "hangzhou"]
-- 6. 上传文件, 查看表数据.
select * from test_array;
-- 7. 数组的相关操作.
-- 查询所有数据
select * from test_array;
-- 查询addrs数组中第一个元素, 索引是从 0 开始数的.
select *, addrs[0] from test_array;
-- 查询addrs数组中元素的个数
select * , size(addrs) from test_array;
-- 查询addrs数组中包含tianjin的信息
select *, array_contains(addrs, 'tianjin') from day07.test_array; -- 这样是看结果, 不是筛选.
select * from day07.test_array where array_contains(addrs, 'tianjin')=true;
-- 最终写法, 即: 上述代码的变形版, 省略true.
select * from day07.test_array where array_contains(addrs, 'tianjin');
案例12: 复杂类型 struct
-- ---------------------------- 案例12: 复杂类型 struct ----------------------------
-- 源文件中数据格式为: "1#周杰轮:11" 建表存储.
-- 流程: "1#周杰轮:11" => "1", "周杰轮:11" => "1", {"name":"周杰轮", "age":11}
-- 1. 建表, 用于存储上述格式的数据.
drop table test_struct;
create table test_struct(
name string,
info struct<name:string, age:int> -- 结构体, 即: 键值对形式, 可以有无数组键值对组合.
) row format delimited
fields terminated by '#' -- 切割后, 数据格式为: "1", "周杰轮:11"
collection items terminated by ':'; -- 切割后, 数据格式为: "1", {"name":"周杰轮","age":11}
-- 2. 上传表数据, 查看表信息.
select * from test_struct;
-- 3. 结构体的相关操作.
select *, info.name from test_struct; -- 通过 info.键名 的方式, 可以实现 根据键获取其对应的值.
-- ---------------------------- 案例13: 复杂类型 map ----------------------------
/*
源文件中数据格式为: "1,zhangsan,father:xiaoming#mother:xiaohuang#brother:xiaoxu,28"
流程为:
1. 原始数据为: "1,zhangsan,father:xiaoming#mother:xiaohuang#brother:xiaoxu,28"
2. 按照,切割: "1", "zhangsan", "father:xiaoming#mother:xiaohuang#brother:xiaoxu", "28"
3. 按照#切割: "1", "zhangsan", ["father:xiaoming", "mother:xiaohuang", "brother:xiaoxu"], "28"
4. 按照:切割: "1", "zhangsan", {"father":"xiaoming", "mother":"xiaohuang", "brother":"xiaoxu"}, "28"
*/
-- 1. 建表, 用于存储上述格式的数据.
create table test_map(
id int,
name string,
members map<string, string>, -- 键值都是string类型
age int
) row format delimited
fields terminated by ',' -- 切割后, 数据格式为: "1", "zhangsan", "father:xiaoming#mother:xiaohuang#brother:xiaoxu", "28"
collection items terminated by '#' -- 切割后, 数据格式为: "1", "zhangsan", ["father:xiaoming", "mother:xiaohuang", "brother:xiaoxu"], "28"
map keys terminated by ':'; -- 切割后, 数据格式为: "1", "zhangsan", {"father":"xiaoming", "mother":"xiaohuang", "brother":"xiaoxu"}, "28"
-- 2. 查询表数据.
select * from test_map;
-- 3. 完成如下查询.
-- 查询全部
select * from test_map;
-- 查询father、mother这两个map的key
select *, members['father'] father, members['mother'] mother from test_map; -- 字典通过 字段名['键名'] 的方式获取其对应的值.
-- 查询全部map的key,使用map_keys函数,结果是array类型
select *, map_keys(members) keys from test_map; -- 结果是array类型 ["father","mother","brother"]
-- 查询全部map的value,使用map_values函数,结果是array类型
select *, map_values(members) keys from test_map; -- 结果是array类型 ["王林","如花","潇潇"]
-- 查询map类型的KV对数量
select *, size(members) kv from test_map; -- 结果是array类型
-- 查询map的key中有brother的数据
-- 数组是否包含某个元素 ["father","mother","brother"] 'brother'
select * from test_map where array_contains( map_keys(members), 'brother');
-- 获取家庭成员中, 有叫如花的信息.
-- 数组是否包含某个元素 ["王林","如花","潇潇"] '如花'
select * from test_map where array_contains( map_values(members), '如花');