HiveQL数据库中的表DDL操作
博客Hive从入门到放弃——HiveQL数据库级别DDL设计的艺术性(四)聊完了数据库的基本操作,我们再来聊聊Hive内表的操作。
创建表
官方推荐建表的结构,莫慌,我们一点点来看看这些关键字的用法,然后自己建几个样例,就很好理解了。
以下内容的[]
符号为可选关键字,|
符号二选一关键字;
CREATE [TEMPORARY] [EXTERNAL] TABLE [IF NOT EXISTS] [db_name.]table_name -- (Note: TEMPORARY available in Hive 0.14.0 and later)
[(col_name data_type [column_constraint_specification] [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, ...) -- (Note: Available in Hive 0.10.0 and later)]
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 (...)] -- (Note: Available in Hive 0.6.0 and later)
]
[LOCATION hdfs_path]
[TBLPROPERTIES (property_name=property_value, ...)] -- (Note: Available in Hive 0.6.0 and later)
[AS select_statement]; -- (Note: Available in Hive 0.5.0 and later; not supported for external tables)
CREATE [TEMPORARY] [EXTERNAL] TABLE [IF NOT EXISTS] [db_name.]table_name
LIKE existing_table_or_view_name
[LOCATION hdfs_path];
data_type
: primitive_type
| array_type
| map_type
| struct_type
| union_type -- (Note: Available in Hive 0.7.0 and later)
primitive_type
: TINYINT
| SMALLINT
| INT
| BIGINT
| BOOLEAN
| FLOAT
| DOUBLE
| DOUBLE PRECISION -- (Note: Available in Hive 2.2.0 and later)
| STRING
| BINARY -- (Note: Available in Hive 0.8.0 and later)
| TIMESTAMP -- (Note: Available in Hive 0.8.0 and later)
| DECIMAL -- (Note: Available in Hive 0.11.0 and later)
| DECIMAL(precision, scale) -- (Note: Available in Hive 0.13.0 and later)
| DATE -- (Note: Available in Hive 0.12.0 and later)
| VARCHAR -- (Note: Available in Hive 0.12.0 and later)
| CHAR -- (Note: Available in Hive 0.13.0 and later)
array_type
: ARRAY < data_type >
map_type
: MAP < primitive_type, data_type >
struct_type
: STRUCT < col_name : data_type [COMMENT col_comment], ...>
union_type
: UNIONTYPE < data_type, data_type, ... > -- (Note: Available in Hive 0.7.0 and later)
row_format
: DELIMITED [FIELDS TERMINATED BY char [ESCAPED BY char]] [COLLECTION ITEMS TERMINATED BY char]
[MAP KEYS TERMINATED BY char] [LINES TERMINATED BY char]
[NULL DEFINED AS char] -- (Note: Available in Hive 0.13 and later)
| SERDE serde_name [WITH SERDEPROPERTIES (property_name=property_value, property_name=property_value, ...)]
file_format:
: SEQUENCEFILE
| TEXTFILE -- (Default, depending on hive.default.fileformat configuration)
| RCFILE -- (Note: Available in Hive 0.6.0 and later)
| ORC -- (Note: Available in Hive 0.11.0 and later)
| PARQUET -- (Note: Available in Hive 0.13.0 and later)
| AVRO -- (Note: Available in Hive 0.14.0 and later)
| JSONFILE -- (Note: Available in Hive 4.0.0 and later)
| INPUTFORMAT input_format_classname OUTPUTFORMAT output_format_classname
column_constraint_specification:
: [ PRIMARY KEY|UNIQUE|NOT NULL|DEFAULT [default_value]|CHECK [check_expression] ENABLE|DISABLE NOVALIDATE RELY/NORELY ]
default_value:
: [ LITERAL|CURRENT_USER()|CURRENT_DATE()|CURRENT_TIMESTAMP()|NULL ]
constraint_specification:
: [, PRIMARY KEY (col_name, ...) DISABLE NOVALIDATE RELY/NORELY ]
[, PRIMARY KEY (col_name, ...) DISABLE NOVALIDATE RELY/NORELY ]
[, CONSTRAINT constraint_name FOREIGN KEY (col_name, ...) REFERENCES table_name(col_name, ...) DISABLE NOVALIDATE
[, CONSTRAINT constraint_name UNIQUE (col_name, ...) DISABLE NOVALIDATE RELY/NORELY ]
[, CONSTRAINT constraint_name CHECK [check_expression] ENABLE|DISABLE NOVALIDATE RELY/NORELY ]
临时表
关键字CREATE TEMPORARY TABLE ……
,创建的临时表仅仅在当前会话是可见的,数据将会被存储在用户的暂存目录中,并在会话结束时被删除。如果创建临时表的名字与当前数据库下的一个非临时表相同,则在这个会话中使用这个表名字时将会使用的临时表,而不是非临时表,用户在这个会话内将不能使用原表,除非删除或者重命名临时表。
临时表有如下限制:
- 不支持分区字段
- 不支持创建索引
- 不具备SQL Server,MySQL一样临时表存在内存中的,使用起来数据飞快的优势;
内部表和外部表
内部表(managed tables)
,也有翻译成管理表
,托管表
的,看文献有幸碰到,知道是一回事就行,即表的逻辑结构,元数据以及表物理数据文件,都由Hive控制,当执行Hive的删除操作时,对应表的数据文件也会一并删除,即Hive对表结构和数据文件具有绝对的拥有权,CREATE TABLE ……
不加任何关键字,默认建出来的就是内部表;
内部表建表样例如下;
CREATE TABLE `ods_rs_basic_tbb_amap_area`(
`ad_code` string COMMENT '行政区码',
`area_name` string COMMENT '标准城市',
`city_code` string COMMENT '便准区号',
`center` string COMMENT '经纬度',
`level` string COMMENT '城市等级',
`load_time` timestamp COMMENT '抽数时间')
COMMENT 'ods标准城市字典表'
PARTITIONED BY ( `event_week` int, `event_day` string, `event_hour` string)
ROW FORMAT SERDE 'org.apache.hadoop.hive.ql.io.parquet.serde.ParquetHiveSerDe'
STORED AS INPUTFORMAT 'org.apache.hadoop.hive.ql.io.parquet.MapredParquetInputFormat'
OUTPUTFORMAT 'org.apache.hadoop.hive.ql.io.parquet.MapredParquetOutputFormat'
LOCATION '/hive/warehouse/ods/rs/basic/ods_rs_basic_tbb_amap_area'
TBLPROPERTIES ('parquet.compression'='snappy')
外部表(external tables)
是指的Hive对映射到该表的元数据管理权限,当删除表时,元数据会被删除,但是该表对应的数据文件并不会被删除,还是会保存在hdfs内,CREATE EXTERNAL TABLE ……
加关键字EXTERNAL
建出来的就是外部表;
外部表建表样例如下;
CREATE EXTERNAL TABLE `ods_rs_basic_tbb_amap_area`(
`ad_code` string COMMENT '行政区码',
`area_name` string COMMENT '标准城市',
`city_code` string COMMENT '便准区号',
`center` string COMMENT '经纬度',
`level` string COMMENT '城市等级',
`load_time` timestamp COMMENT '抽数时间')
COMMENT 'ods标准城市字典表'
PARTITIONED BY ( `event_week` int, `event_day` string, `event_hour` string)
ROW FORMAT SERDE 'org.apache.hadoop.hive.ql.io.parquet.serde.ParquetHiveSerDe'
STORED AS INPUTFORMAT 'org.apache.hadoop.hive.ql.io.parquet.MapredParquetInputFormat'
OUTPUTFORMAT 'org.apache.hadoop.hive.ql.io.parquet.MapredParquetOutputFormat'
LOCATION '/hive/warehouse/ods/rs/basic/ods_rs_basic_tbb_amap_area'
TBLPROPERTIES ('parquet.compression'='snappy')
两者比较
:内部表容易通过Hive来管理表结构和数据文件的生命周期,如表归档,彻底弃用时,直接就可以sql删除操作元数据和数据文件;外部表将表元数据和表实际数据文件解耦一分为二,在彻底弃用表时,除了sql语句删除该表在Hive的元数据,还需要去清除hdfs上的真实文件,但是外部表的优势就是修改表结构的时候可以为所欲为,新增列,删除列不用思考太多,你甚至可以为所欲为的先Copy出原来的建表语句,然后删掉表,在原来Copy的建表语句的基础上修改后再重新建出来,然后用指令修复数据;
--hive外部表删除重建后的数据修复指令
MSCK REPAIR table tablename;
个人建议
:如果业务和使用场景允许,优先考虑使用外部表;将数据文件和Hive元数据解耦设计,顺便奉上我修改Hive的常用模板,如下;
-- 利用show CREATE table语句查询出目前的表结构,我屏蔽了,需要用时打开
-- show CREATE table ods_rs_basic_tbb_amap_area;
-- drop table 是危险语句,我屏蔽了,先不使用,
-- 确认刚刚show CREATE table查出来的基础上的修改完美了再用
-- 如这里我我去掉了load_time字段,我确认在原来基础上
-- 修改的建表没问题了,那可以执行drop table 语句了,然后执行以下所有的语句
--drop table ods_rs_basic_tbb_amap_area;
CREATE EXTERNAL TABLE `ods_rs_basic_tbb_amap_area`(
`ad_code` string COMMENT '行政区码',
`area_name` string COMMENT '标准城市',
`city_code` string COMMENT '便准区号',
`center` string COMMENT '经纬度',
`level` string COMMENT '城市等级')
COMMENT 'ods标准城市字典表'
PARTITIONED BY (
`event_week` int,
`event_day` string,
`event_hour` string)
ROW FORMAT SERDE
'org.apache.hadoop.hive.ql.io.parquet.serde.ParquetHiveSerDe'
STORED AS INPUTFORMAT
'org.apache.hadoop.hive.ql.io.parquet.MapredParquetInputFormat'
OUTPUTFORMAT
'org.apache.hadoop.hive.ql.io.parquet.MapredParquetOutputFormat'
LOCATION
'hdfs://dw-cluster/hive/warehouse/ods/rs/basic/ods_rs_basic_tbb_amap_area'
TBLPROPERTIES (
'parquet.compression'='snappy')
;
--修复数据
MSCK REPAIR table ods_rs_basic_tbb_amap_area;
--确认下数据是否修复了
select * from ods_rs_basic_tbb_amap_area where event_day='20200522';
数据类型
和其他的SQL语言一样,建表语句的字段都需要指定数据类型。需要注意的是所有的这些数据类型都是对Java中接口的实现,因此这些类型的具体行为细节和Java中对应的类型是完全一致的。例如,string类型实现的是Java中的String,float实现的是Java中的float等;
注意:如果Hive使用存储文件为带schema的ORC
和Parquet
文件时,请保证Hive建表的数据类型跟文件的类型保持一致,不然序列化和反序列化的时候很麻烦,可能出现类型不一致的字段无法读写的异常,处理起来非常麻烦,切记这个大坑,最好在建表选择数据类型就避免;
- 基本数据
基本数据类型就是常用的整型,浮点型,字符,字符串,布尔型,时间类型等常见类型,如下表1;
基本数据类型 | 描述 | 值样例 |
---|---|---|
boolean | true/false | TRUE |
tinyint | 1字节的有符号整数 | -128~127 |
smallint | 2个字节的有符号整数,-32768~32767 | 1S |
int | 4个字节的带符号整数 | 1 |
bigint | 8字节带符号整数 | 1L |
float | 4字节单精度浮点数 | 1.0 |
double | 8字节双精度浮点数 | 1.0 |
deicimal | 任意精度的带符号小数 | 1.0 |
String | 字符串,变长 | “a”,’b’ |
varchar | 变长字符串 | “a”,’b’ |
char | 固定长度字符串 | “a”,’b’ |
binary | 字节数组 | 无法表示 |
timestamp | 时间戳,纳秒精度 | 122327493795或者2020-05-02 13:03:08.920000000 |
date | 日期 | ‘2018-04-07’ |
基本数据类型建表样例如下;
CREATE EXTERNAL TABLE `dwd_rs_assemble_tbb_report_scan_analysis`(
`scan_id` bigint COMMENT '扫码ID',
`report_id` bigint COMMENT '上报ID',
`building_id` bigint COMMENT '项目ID',
`install_name` string COMMENT '安装名称',
`install_area_kind` string COMMENT '安装区域类型',
`install_area` string COMMENT '安装区域',
`qr_code` string COMMENT '二维码',
`qr_code_json` string COMMENT '二维码转成json',
`icc_id` string COMMENT 'iccid码',
`isp_name` string COMMENT '运营商',
`syn_mode` string COMMENT '主从机',
`manual_schno` string COMMENT '人工上刊的scho插播号',
`internet_schno` string COMMENT '网络推送的scho插播号',
`signal_strength` string COMMENT '信号强度',
`sc_location_id` bigint COMMENT '二维码点位ID',
`sc_location_longitude` decimal(18,9) COMMENT '二维码点位经度',
`sc_location_latitude` decimal(18,9) COMMENT '二维码点位纬度',
`is_success` Boolean COMMENT '扫码是否成功 1 成功 0 失败',
`valid_status` Boolean COMMENT '可用性状态',
`update_user_id` bigint COMMENT '更新人id',
`update_user_name` string COMMENT '更新人名称',
`update_date` timestamp COMMENT '更新日期',
`real_shno` string COMMENT '实际插播号',
`scan_date_time` timestamp COMMENT '手机端扫码日期',
`net_type` string COMMENT '网络类型',
`signal_trength` string COMMENT '信号强度',
`push_speed` float COMMENT '推送速度'
)
COMMENT 'ods扫码表最近一次扫码解析表'
PARTITIONED BY (
`event_week` int,
`event_day` string,
`event_hour` string)
ROW FORMAT SERDE
'org.apache.hadoop.hive.ql.io.parquet.serde.ParquetHiveSerDe'
STORED AS INPUTFORMAT
'org.apache.hadoop.hive.ql.io.parquet.MapredParquetInputFormat'
OUTPUTFORMAT
'org.apache.hadoop.hive.ql.io.parquet.MapredParquetOutputFormat'
LOCATION
'/hive/warehouse/dwd/rs/dwd_rs_assemble_tbb_report_scan_analysis'
TBLPROPERTIES ('parquet.compression'='snappy')
- 复杂类型
复杂数据类型就是处理一些相对复杂的数据模型结构而设计的,如列表,key-value字典型,结构和联合类型等,具体如表2;
复杂类型 | 描述 | 值样例 |
---|---|---|
array | 有序的的同类型的集合 | array(1,2) |
map | key-value,key必须为原始类型,value可以任意类型 | map(‘a’,1,’b’,2) |
struct | 字段集合,类型可以不同 | struct(‘1’,1,1.0), |
union | 联合类型,官方也说不完善,很少用到 | UNIONTYPE< data_type, data_type, …> |
复杂结构的建表操作如下,首先先编写一个复杂结构的文件出来,该文件不包含union类型,union类型下文有描述,为了方便说明直观的问题,我们直接采用肉眼可见的分隔符(但是实际操作 中不太会这么干,因为这些分隔符本身也可能是数据的一部分,所以实际开发中要么用自带schema的parquet或orc文件,要么慎用分隔符),具体数据如下,|
是字段间的分隔符,,
是同一个字段内不同元素的分隔符,;
是key-value的分隔符,\n
作为换行符,数据分为(name)姓名
,(salary )薪资
,(subordinates )下属
,(deductions)杂项扣费
,(address )地址
共5列,而一个人的下属
下属可能有多个,所以是个列表;杂项扣费
涉及的项目可能是多项,每一项扣费也不一定一样,所以是个key-value的字典类型;地址
包含街道,城市,州,编号等,这些部分结构相对固定,但是每一个部分数据类型还不一致,所以可以考虑为一个结构体,该文件命名为/source/employees.txt
,具体数据如下;
John Doe|100000.0|Mary,SmithTodd,JonesFederal|Taxes;2,State;15.2|Insurance.11,Michigan,Ave.ChicagoIL,60600|
Mary Smith|80000.0|Bill,KingFedera|Taxes;0.5,State;10.2|Insurance.1100,Ontario,St.ChicagoIL,60601|
Todd Jones|70000.0|Federal|Taxes;15|Insurance.1200,Chicago,Ave.OakParkIL,60700|
Bill King|60000.0|Federal|Taxes;15|Insurance.1300,Obscure,Dr.ObscuriaIL,60100|
Boss Man|200000.0|John,DoeFred,FinanceFederal|Taxes;13.0,late;200|Insurance.051,Pretentious,Drive.ChicagoIL,60500|
Fred Finance|150000.0|Stacy,AccountantFederal|Taxes;30.23,others;102.9|Insurance.052,Pretentious,Drive.ChicagoIL,60500|
Stacy Accountant|60000.0|Federal|Taxes;15|Insurance.1300,Main,St.NapervilleIL,60563|
然后利用hadoop指令把这个编写好的文件上传到hdfs上/source
目录下,指令如下;
hadoop fs -put ./employees.txt /source
打开我们的hive,进入rowyet库,根据刚刚文件的说明建员工表employees;,然后导入数据,然后预览下我们的数据结构,具体操作和执行效果如下;
-- 切换到rowyet
hive> use rowyet;
OK
Time taken: 5.047 seconds
-- 根据需求分析建表
hive> create external table if not exists employees(
> name string,
> salary float,
> subordinates array<string>,
> deductions map<string,float>,
> address struct<street:string, city:string, state:string, zip:int>
> ) row format delimited
> fields terminated by '|'
> collection items terminated by ','
> map keys terminated by ';'
> lines terminated by '\n'
> stored as textfile;
OK
Time taken: 0.09 seconds
-- 将hdfs上/source/employees.txt覆盖式导入该表
hive> load data inpath '/source/employees.txt' overwrite into table employees;
Loading data to table rowyet.employees
OK
Time taken: 0.548 seconds
--预览数据在hive表内的结果
hive> select * from rowyet.employees;
OK
employees.name employees.salary employees.subordinates employees.deductions employees.address
John Doe 100000.0 ["Mary","SmithTodd","JonesFederal"] {"Taxes":2.0,"State":15.2} {"street":"Insurance.11","city":"Michigan","state":"Ave.ChicagoIL","zip":60600}
Mary Smith 80000.0 ["Bill","KingFedera"] {"Taxes":0.5,"State":10.2} {"street":"Insurance.1100","city":"Ontario","state":"St.ChicagoIL","zip":60601}
Todd Jones 70000.0 ["Federal"] {"Taxes":15.0} {"street":"Insurance.1200","city":"Chicago","state":"Ave.OakParkIL","zip":60700}
Bill King 60000.0 ["Federal"] {"Taxes":15.0} {"street":"Insurance.1300","city":"Obscure","state":"Dr.ObscuriaIL","zip":60100}
Boss Man 200000.0 ["John","DoeFred","FinanceFederal"] {"Taxes":13.0,"late":200.0} {"street":"Insurance.051","city":"Pretentious","state":"Drive.ChicagoIL","zip":60500}
Fred Finance 150000.0 ["Stacy","AccountantFederal"] {"Taxes":30.23,"others":102.9} {"street":"Insurance.052","city":"Pretentious","state":"Drive.ChicagoIL","zip":60500}
Stacy Accountant 60000.0 ["Federal"] {"Taxes":15.0} {"street":"Insurance.1300","city":"Main","state":"St.NapervilleIL","zip":60563}
Time taken: 0.131 seconds, Fetched: 7 row(s)
眼尖的朋友可能发现了,这不就是个json串吗?没错,其实这些复杂结构也是可以转换成json串处理的,hive对json串也是有很好的支持,拿第一行数据为例,我们转成json串,具体如下;
{
"name":"John Doe",
"salary":"100000.0",
"subordinates":[
"Mary",
"SmithTodd",
"JonesFederal"
],
"deductions":{
"Taxes":2,
"State":15.2
},
"address":{
"street":"Insurance.11",
"city":"Michigan",
"state":"Ave.ChicagoIL",
"zip":60600
}
}
union类型
:
uniontype可以理解为泛型,同一时刻同一地点只有联合体中的一个元素生效uniontype中的元素共享内存;可以通过create_union内置函数创建uniontype:create_union(tag, val1, val2) tag是数字,0开始,必须小于后面参数的个数插入uniontype数据,通过这种方式只能插入只有一个元素的uniontype,包含多个会提示跟表中的字段类型不一致,着实坑吧?操作如下;
-- 插入一个元素的
CREATE TABLE union_test3(foo UNIONTYPE<map<string, string>>)
insert into table union_test3 select * from (select create_union(0,map('name', 'tom', 'name2', 'wadeyu')))t;
同时写入多个元素到uniontype的类型时,需要先准备一个数据文件,假设为文件union_test.log
,内容如下;
0,1
1,3.0
2,world
3,wade:tom:polly
4,k1^Dv1:k2^Dv2
写入多个元素HiveQL操作如下;
-- 同时写入多个元素
# 创建表
create table union_testnew(
foo uniontype<int, double, string, array<string>, map<string, string>>
)
row format delimited
collection items terminated by ','
map keys terminated by ':'
lines terminated by '\n'
stored as textfile;
# 导入数据
hive (badou)> load data local inpath './union_test.log' overwrite into table union_testnew;
# 查询数据
hive (rowyet)> select * from union_testnew;
OK
union_testnew.foo
{0:1}
{1:3.0}
{2:"world"}
{3:["wade","tom","polly"]}
{4:{"k1":"v1","k2":"v2"}}
Time taken: 0.225 seconds, Fetched: 5 row(s)
官网提示:UNIONTYPE support is incomplete The UNIONTYPE datatype was introduced in Hive 0.7.0 (HIVE-537), but full support for this type in Hive remains incomplete. Queries that reference UNIONTYPE fields in JOIN (HIVE-2508), WHERE, and GROUP BY clauses will fail, and Hive does not define syntax to extract the tag or value fields of a UNIONTYPE. This means that UNIONTYPEs are effectively pass-through-only.
翻译翻译就是目前Hive对这个数据类型支持也不完善,没有提取具体值得方法,只做值得传递,直白一点就是太不常用了,也慎用,坑多,反正我实际开发没用过,所以这里专门用蓝色标注
表约束
Hive建表大多数情况下是为大数据准备的,既然是大数据 ,数据本身内部可能就存在一定误差,所以Hive的表约束并没有像关系型数据库一样,建表时候起到很重要的作用,这里我们甚至可以忽略不计;
表描述
Hive表描述关键字对应建表模板里面的是COMMENT table_comment
,常用来写它的中文名,如下面的ods标准城市字典表
;
CREATE EXTERNAL TABLE `ods_rs_basic_tbb_amap_area`(
`ad_code` string COMMENT '行政区码',
`area_name` string COMMENT '标准城市',
`city_code` string COMMENT '便准区号',
`center` string COMMENT '经纬度',
`level` string COMMENT '城市等级')
COMMENT 'ods标准城市字典表'
PARTITIONED BY (
`event_week` int,
`event_day` string,
`event_hour` string)
ROW FORMAT SERDE
'org.apache.hadoop.hive.ql.io.parquet.serde.ParquetHiveSerDe'
STORED AS INPUTFORMAT
'org.apache.hadoop.hive.ql.io.parquet.MapredParquetInputFormat'
OUTPUTFORMAT
'org.apache.hadoop.hive.ql.io.parquet.MapredParquetOutputFormat'
LOCATION
'hdfs://dw-cluster/hive/warehouse/ods/rs/basic/ods_rs_basic_tbb_amap_area'
TBLPROPERTIES (
'parquet.compression'='snappy')
;
表分区和分桶表
表分区对应建表模板里面的[PARTITIONED BY (col_name data_type [COMMENT col_comment], ...)]
,所谓分区对应Hive表里面,按照分区的字段,要求,存到hdfs不同的文件目录下,分区字段本身也是存在Hive表内的,查询时候可以用来按照分区在where条件过滤筛选,能大大的加快查询速度;
分桶对应的建表模板是 [CLUSTERED BY (col_name, col_name, ...) [SORTED BY (col_name [ASC|DESC], ...)] INTO num_buckets BUCKETS]
,其实 就是让数据在数据文件内按照你指定的字段来分布,指定桶的个数也就代表你最终的这个表被切割的文件个数,其实就是对应MapReduce里面Reduce的个数,决定最终会有几个文件;
分区提供了一个隔离数据和优化查询的便利方式,不过并非所有的数据都可形成合理的分区,尤其是需要确定合适大小的分区划分方式,(不合理的数据分区划分方式可能导致有的分区数据过多,而某些分区没有什么数据的尴尬情况)分桶是将数据集分解为更容易管理的若干部分的另一种技术,分区实现文件的隔离,好的分桶决策文件的个数和分布,尽量让每个文件拥有的数据差不多,既不会数据倾斜,在大数据集群处理的时候可以并发处理使得每个任务处理的文件数据量差不多,而不会出现极端一大一小,最终的文件结果要等巨大文件算完,同时分桶文件也是满足抽样调查的一种很好实现方式,如数据按照某id哈希分桶后,每个文件内本身的数据就是随机分布的。
分区分桶表的建表样例如下,PARTITIONED BY ( event_week int, event_day string,event_hour string)
按照周,日期 ,小时分区;CLUSTERED BY (
ad_code) SORTED BY (
ad_codeASC)] INTO 2 BUCKETS
按照ad_code
分桶,共2个桶文件,数据按照每行数据的ad_code
哈希再对2取模后决定每条数据该去哪个桶文件,数据在每个桶文件内按照ad_code
的顺序排列;
CREATE EXTERNAL TABLE `ods_rs_basic_tbb_amap_area`(
`ad_code` string COMMENT '行政区码',
`area_name` string COMMENT '标准城市',
`city_code` string COMMENT '便准区号',
`center` string COMMENT '经纬度',
`level` string COMMENT '城市等级')
COMMENT 'ods标准城市字典表'
PARTITIONED BY (
`event_week` int,
`event_day` string,
`event_hour` string)
CLUSTERED BY (`ad_code`) SORTED BY (`ad_code` ASC)] INTO 2 BUCKETS
ROW FORMAT SERDE
'org.apache.hadoop.hive.ql.io.parquet.serde.ParquetHiveSerDe'
STORED AS INPUTFORMAT
'org.apache.hadoop.hive.ql.io.parquet.MapredParquetInputFormat'
OUTPUTFORMAT
'org.apache.hadoop.hive.ql.io.parquet.MapredParquetOutputFormat'
LOCATION
'hdfs://dw-cluster/hive/warehouse/ods/rs/basic/ods_rs_basic_tbb_amap_area'
TBLPROPERTIES (
'parquet.compression'='snappy')
;
分区的目的就是会在该表目录下形成一个按周,日期,小时的分目录的,而分桶就是产生的文件按照分桶字段来分布,具体结构可以参考下;
hdfs://dw-cluster/hive/warehouse/ods/rs/basic/ods_rs_basic_tbb_amap_area/ # hdfs上ods_rs_basic_tbb_amap_area表目录
├event_week=19 # 19周
├event_day=20200510 # 日期
├event_hour=00 # 小时
├ part-00000-3e6c685c-b62b-46cf-b782-0f4cd628e8ad-c000.snappy.parquet #桶文件1
├ part-00001-3e6c685c-b62b-46cf-b882-0c4cd628e8ad-c000.snappy.parquet #桶文件2
├ _SUCCESS #文件上传成功的标识
├event_week=20 # 19周
├event_day=20200517 # 日期
├event_hour=00 # 小时
├ part-00000-2cebddea-6178-426f-866c-c5cc863537c4-c000.snappy.parquet #桶文件1
├ part-00001-3ec5cc86c-b62b-46cf-b882-0c4cd628e8ad-c000.snappy.parquet #桶文件2
├ _SUCCESS #文件上传成功的标识
注意:spark sql对分桶表支持不是很好,而且spark支持 repartition的功能,其实桶表用处不是特别大,这里顺便说一下,由于Hive引擎本身都是要转化成MapReduce的,数据大量和磁盘交互,速度比较慢,所以实际生产中并不会用Hive作为计算引擎,而是单纯的元数据管理而已,所以你的建表还需要配合你选的计算查询引擎,常见的有Hive on Spark
,Spark on Hive
,Taz(据说拼多多之前大量使用)
,impala(ORC文件暂不支持)等
,这里做个 初步了解,不要影响你学习Hive本身,这些我们后续有机会再聊聊吧,尤其是Spark on Hive
利用Spark来计算 Hive表,这是我们公司现在用的一种架构,后续会详细交流。
Hive倾斜值性能
建表模板中的[SKEWED BY (col_name, col_name, ...) -- (Note: Available in Hive 0.10.0 and later)] ON ((col_value, col_value, ...), (col_value, col_value, ...), ...) [STORED AS DIRECTORIES]
是改善Hive表的一个或多个列有倾斜值的表的性能,通过指定经常出现的值(严重倾斜),hive将会在元数据中记录这些倾斜的列名和值,在join时能够进行优化。若是指定了STORED AS DIRECTORIES,也就是使用列表桶(ListBucketing),hive会对倾斜的值建立子目录,查询会更加得到优化;
使用举列如下;
CREATE TABLE list_bucket_multiple (col1 STRING, col2 int, col3 STRING)
SKEWED BY (col1, col2) ON (('s1',1), ('s3',3), ('s13',13), ('s78',78)) [STORED AS DIRECTORIES];
这也是个相对比较鸡肋的功能,因为如果你已经知道了这些值会数据倾斜,其实你只要根据这些值派生出一列 ,然后对新的列哈希后重分区就行,SKEWED BY
也是用了类似的原理实现,不常用;
Hive建表的序列化和反序列化,文件存储类型,文件存储位置和属性
序列化和反序列化
:建表模板的[ROW FORMAT row_format]
就是指的表内的序列化和反序列化,什么意思呢?就是指定Hive文件内的列分隔符呀,让存在Hive文件内的数据有序的展现成二维表一样;
存储为文件时,需要指定分割符号,如下ROW FORMAT DELIMITED
;
CREATE TABLE page_view(viewTime INT, userid BIGINT,
page_url STRING, referrer_url STRING,
ip STRING COMMENT 'IP Address of the User')
COMMENT 'This is the page view table'
PARTITIONED BY(dt STRING, country 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;
上面的\001
,\002
,\003
,是对应的ASCII码的值,是键盘内不可输入的值作为文件的分隔符,当然,我在上面讲的数据类型(复杂类型)
举的例子的分隔符 更直观,但是问题就来了,如果某一列本身包含这个分割符的内容,你怎么办,那在序列化的时候不就出现了bug?这个时候虽然可以通过边界符号,来避免,但是始终不是良策,这个时候专门就有人研究自带schema的文件类型,如parquet
,ORC
等,这些文件本身每一列自带数据类型,而且也自带了序列化和反序列化的方法,我们直接调用就行,不要我们去考虑刚刚说到的那些异常,所以这就是为啥生产中基本都使用parquet
,ORC
的原因,而txt等文件只是临时文件的时候会用一下,如下存成了Parquet文件,直接用org.apache.hadoop.hive.ql.io.parquet.serde.ParquetHiveSerDe
作为序列化和反序列化的方法;
CREATE EXTERNAL TABLE `ods_rs_basic_tbb_amap_area`(
`ad_code` string COMMENT '行政区码',
`area_name` string COMMENT '标准城市',
`city_code` string COMMENT '便准区号',
`center` string COMMENT '经纬度',
`level` string COMMENT '城市等级')
COMMENT 'ods标准城市字典表'
PARTITIONED BY (
`event_week` int,
`event_day` string,
`event_hour` string)
ROW FORMAT SERDE
'org.apache.hadoop.hive.ql.io.parquet.serde.ParquetHiveSerDe'
STORED AS INPUTFORMAT
'org.apache.hadoop.hive.ql.io.parquet.MapredParquetInputFormat'
OUTPUTFORMAT
'org.apache.hadoop.hive.ql.io.parquet.MapredParquetOutputFormat'
LOCATION
'hdfs://dw-cluster/hive/warehouse/ods/rs/basic/ods_rs_basic_tbb_amap_area'
TBLPROPERTIES (
'parquet.compression'='snappy')
;
文件存储类型
:在上面的建表语句也说明了文件存储类型的选择,对应建表语句模板的[STORED AS file_format]
,总之就是生产表一般选用parquet
或者orc
文件,关于文件的选用,可以参考博客RC ORC Parquet之大数据文件存储格式的一哥之争,里面详细介绍了这些文件的原理和区别,以及如何选用。
注意:文件存储类型如果使用自带schema的parquet和orc的话,请切记要保证文件自身的schema和hive的数据类型要保持一致,否则,可能不兼容,无法正常序列化,会很麻烦。
#以parquet为例,假设我有个parquet文件在hdfs上,目录如下;
#/hive/warehouse/ods/rs/basic/ods_rs_basic_tbb_amap_area/event_week=19/event_day=20200510/event_hour=00
#那么先用spark语句预览下该parquet文件的schema,如下;
spark.read.parquet("/hive/warehouse/ods/rs/basic/ods_rs_basic_tbb_amap_area/event_week=19/event_day=20200510/event_hour=00").printSchema
#得到结果
root
|-- ad_code: string (nullable = true)
|-- area_name: string (nullable = true)
|-- city_code: string (nullable = true)
|-- center: string (nullable = true)
|-- level: string (nullable = true)
|-- event_week: integer (nullable = true)
|-- event_day: string (nullable = true)
|-- event_hour: string (nullable = true)
|-- event_minute: string (nullable = true)
然后再根据该parquet文件 建表语句如下;
CREATE EXTERNAL TABLE `ods_rs_basic_tbb_amap_area`(
`ad_code` string COMMENT '行政区码',
`area_name` string COMMENT '标准城市',
`city_code` string COMMENT '便准区号',
`center` string COMMENT '经纬度',
`level` string COMMENT '城市等级')
COMMENT 'ods标准城市字典表'
PARTITIONED BY (
`event_week` int,
`event_day` string,
`event_hour` string)
ROW FORMAT SERDE
'org.apache.hadoop.hive.ql.io.parquet.serde.ParquetHiveSerDe'
STORED AS INPUTFORMAT
'org.apache.hadoop.hive.ql.io.parquet.MapredParquetInputFormat'
OUTPUTFORMAT
'org.apache.hadoop.hive.ql.io.parquet.MapredParquetOutputFormat'
LOCATION
'hdfs://dw-cluster/hive/warehouse/ods/rs/basic/ods_rs_basic_tbb_amap_area'
TBLPROPERTIES (
'parquet.compression'='snappy')
;
如果你没参考,而且是擅作主张把比如event_week int
建成了event_week bigint
,那么恭喜你中奖了,很有可能就会出现该字段在Hive序列化解析的时候没问题,但是用sparksql解析序列化的时候,报错,event_week
字段 bigint类型和 文件的int类型不兼容,无法解析,具体如QueryExecutionException: Encounter error while reading parquet files. One possible cause: Parquet column cannot be converted in the corresponding files. Details
,这个坑查了官网,也没有很好的办法,还是一个bug,所以最好就是参考你的文件的schema来选择表的数据类型;
文件存储位置
:对应建表模板的关键字[LOCATION hdfs_path]
,就是指定你的Hive表所在的hdfs位置,如果不指定,默认会在 你是用的Hive数据库指定的目录下,如果没有库,就会选择默认的 数据库default
下,因为default
是没有实际目录的,那么不指定库又没指定Hive表的存储位置,就会默认表存储在配置文件hive-site.xml
下的hive.metastore.warehouse.dir
属性所对应的目录,默认是\user\hive\warehouse
;
Hive表属性
:对应建表模板的关键字[TBLPROPERTIES (property_name=property_value, ...)]
,就是指定你的Hive表的属性,如压缩格式等;
……
TBLPROPERTIES (
'parquet.compression'='snappy')
;
Hive复制表结构
建表语句模板内还讲到了如何根据已存在的表来复制新建一个新的表,但是数据不会生成过来,跟MySQL语法很像;但是有一点可以注意下,就是如果create后面没有指定表的类型是内部表还是外部表,那么表的类型会跟已存在的表保持一致,如果指定了则会改写。
CREATE [TEMPORARY] [EXTERNAL] TABLE [IF NOT EXISTS] [db_name.]table_name
LIKE existing_table_or_view_name
[LOCATION hdfs_path];
Hive删除表
删除表其实很简单,当然也是高危动作啦,稍微要注意下,慎重删除,不然造成不良后果那岂不是只能跑路了?删除的时候唯一的区别就是表是内部表还是外部表,内部表的话直接删除就连数据也没了,外部表删除表只是把表的元数据给删除了,如果你想要清理该表对应的数据文件,还是需要去hdfs上利用hdfs指令删除的,删除表的语句如下;
-- 常用版删除语句
drop table ods_rs_basic_tbb_amap_area;
-- rerun容错版
drop table if exists ods_rs_basic_tbb_amap_area;
Hive修改表
HiveQL修改表结构常用关键字ALTER
,修改的模板如下;
ALTER TABLE name RENAME TO new_name --重命名表明
ALTER TABLE name ADD COLUMNS (col_spec[, col_spec ...]) --新增列
ALTER TABLE name DROP [COLUMN] column_name --删除列
ALTER TABLE name CHANGE column_name new_name new_type --修改列
ALTER TABLE name REPLACE COLUMNS (col_spec[, col_spec ...]) --替换列
假设存在表employee,数据列如下;
column Type
eid int
name String
salary Float
designation String
则实际操作,举例如下;
--重命名,将employee 重命名为emp
ALTER TABLE employee RENAME TO emp;
--新增列 dept
ALTER TABLE employee ADD COLUMNS ( dept STRING COMMENT 'Department name');
--替换原来的列,用eid 替换empid ,用ename 替换name
ALTER TABLE employee REPLACE COLUMNS ( eid INT empid Int, ename STRING name String);
--修改列的数据类型
ALTER TABLE employee CHANGE name ename String;
ALTER TABLE employee CHANGE salary salary Double;
--删除列salary
ALTER TABLE employee DROP [COLUMN] salary
也可以修改表的元数据,模板如下;
# 你可以用这个命令向表中增加metadata改变表文件格式与组织
ALTER TABLE table_name SET TBLPROPERTIES table_properties
table_properties:
:[property_name = property_value…..]
#这个命令修改了表的物理存储属性
ALTER TABLE table_name SET FILEFORMAT file_format
ALTER TABLE table_name CLUSTERED BY(userid) SORTED BY(viewTime) INTO num_buckets BUCKETS
指令这么多,修改起来这么复杂的样子,怎么办呢?还记我说的终极方案吗?不要管这些,情景允许的话,先使建成外部表,然后如果要修改,可以在原来基础上copy出来建表语句,修改好后drop表,执行新的建表语句,然后修复表数据即可,如下代码;
-- 利用show CREATE table语句查询出目前的表结构,我屏蔽了,需要用时打开
-- show CREATE table ods_rs_basic_tbb_amap_area;
-- drop table 是危险语句,我屏蔽了,先不使用,
-- 确认刚刚show CREATE table查出来的基础上的修改完美了再用
-- 如这里我我去掉了load_time字段,我确认在原来基础上
-- 修改的建表没问题了,那可以执行drop table 语句了,然后执行以下所有的语句
--drop table ods_rs_basic_tbb_amap_area;
CREATE EXTERNAL TABLE `ods_rs_basic_tbb_amap_area`(
`ad_code` string COMMENT '行政区码',
`area_name` string COMMENT '标准城市',
`city_code` string COMMENT '便准区号',
`center` string COMMENT '经纬度',
`level` string COMMENT '城市等级')
COMMENT 'ods标准城市字典表'
PARTITIONED BY (
`event_week` int,
`event_day` string,
`event_hour` string)
ROW FORMAT SERDE
'org.apache.hadoop.hive.ql.io.parquet.serde.ParquetHiveSerDe'
STORED AS INPUTFORMAT
'org.apache.hadoop.hive.ql.io.parquet.MapredParquetInputFormat'
OUTPUTFORMAT
'org.apache.hadoop.hive.ql.io.parquet.MapredParquetOutputFormat'
LOCATION
'hdfs://dw-cluster/hive/warehouse/ods/rs/basic/ods_rs_basic_tbb_amap_area'
TBLPROPERTIES (
'parquet.compression'='snappy')
;
--修复数据
MSCK REPAIR table ods_rs_basic_tbb_amap_area;
--确认下数据是否修复了
select * from ods_rs_basic_tbb_amap_area where event_day='20200522';