大数据技术学习笔记(十)—— Hive(2)

1 查询

1.1 基本查询


1.2 分组


1.3 Join语句


1.4 排序

1.4.1 全局排序


Order By:全局排序,只有一个Reducer。

使用ORDER BY子句排序

  • ASC(ascend):升序(默认)
  • DESC(descend):降序

ORDER BY 子句在SELECT语句的结尾。

1.4.2 内部排序


Sort By:对于大规模的数据集 order by 的效率非常低。在很多情况下,并不需要全局排序,此时可以使用sort by。

Sort by为每个 reducer 产生一个排序文件。每个Reducer内部进行排序,对全局结果集来说不是排序。

1.4.3 分区


Distribute By:在有些情况下,我们需要控制某个特定行应该到哪个reducer,通常是为了进行后续的聚集操作。distribute by子句可以做这件事。distribute by类似MR中partition(自定义分区),进行分区,结合sort by使用。

对于distribute by进行测试,一定要分配多 reduce 进行处理,否则无法看到 distribute by 的效果。

注意:

  • distribute by 的分区规则是根据分区字段的 hash 码与 reduce 的个数进行模除后,余数相同的分到一个区。
  • Hive要求 distribute by 语句要写在 sort by 语句之前。

1.4.4 分区排序


当 distribute by 和 sort by 字段相同时,可以使用 cluster by 方式。

cluster by 除了具有 distribute by 的功能外还兼具 sort by 的功能。但是排序只能是升序排序,不能指定排序规则为ASC或者DESC。

2 分区表和分桶表

2.1 分区表


分区表实际上就是对应一个HDFS文件系统上的独立的文件夹,该文件夹下是该分区所有的数据文件。Hive中的分区就是分目录,把一个大的数据集根据业务需要分割成小的数据集。在查询时通过 where 子句中的表达式选择查询所需要的指定的分区,这样的查询效率会提高很多。

2.1.1 分区表基本操作


(1)引入分区表(需要根据日期对日志进行管理,通过部门信息模拟)

dept_20200401.log
dept_20200402.log
dept_20200403.log

(2)创建分区表

hive (default)> create table dept_partition(
              >     deptno int,
              >     dname string,
              >     loc string
              > )
              > partitioned by (day string)
              > row format delimited fields terminated by '\t';

注意:分区字段不能是表中已经存在的数据,可以将分区字段看作表的伪列。

(3)加载数据到分区表中

数据准备

dept_20200401.log

10	ACCOUNTING	1700
20	RESEARCH	1800

dept_20200402.log

30	SALES	1900
40	OPERATIONS	1700

dept_20200403.log

50	TEST	2000
60	DEV	1900
[huwei@hadoop101 datas]$ vim dept_20200401.log
[huwei@hadoop101 datas]$ vim dept_20200402.log
[huwei@hadoop101 datas]$ vim dept_20200403.log

加载数据

hive (default)> load data local inpath '/opt/module/hive-3.1.2/datas/dept_20200401.log'
              > into table dept_partition
              > partition(day='20200401');
hive (default)> load data local inpath '/opt/module/hive-3.1.2/datas/dept_20200402.log'
              > into table dept_partition
              > partition(day='20200402');
hive (default)> load data local inpath '/opt/module/hive-3.1.2/datas/dept_20200403.log'
              > into table dept_partition
              > partition(day='20200403');

注意:分区表加载数据时,必须指定分区。

此时查看表所有数据

在这里插入图片描述

查看 HDFS

在这里插入图片描述

(4)查询分区表中数据

单分区查询

hive (default)> select * from dept_partition where day='20200401';

多分区联合查询

hive (default)> 
select 
    * 
from dept_partition 
where day='20200401'
union
    select 
        * 
    from dept_partition 
    where day='20200402'
union
    select 
        * 
    from dept_partition 
    where day='20200403';

(5)查看分区表有多少分区

hive (default)> show partitions dept_partition;

(6)增加分区

增加单个分区

hive (default)> alter table dept_partition add partition(day='20200404');

同时创建多个分区(分区之间不能有逗号

hive (default)> alter table dept_partition add partition(day='20200405') partition(day='20200406');

(7)删除分区

删除单个分区

hive (default)> alter table dept_partition drop partition (day='20200406');

同时删除多个分区(分区之间必须有逗号

hive (default)> alter table dept_partition drop partition (day='20200404'), partition(day='20200405');

2.1.2 分区表二级分区


思考:如何一天的日志数据量也很大,如何再将数据拆分?

2.1.3 动态分区调整


关系型数据库中,对分区表 Insert 数据时候,数据库自动会根据分区字段的值,将数据插入到相应的分区中,Hive 中也提供了类似的机制,即动态分区(Dynamic Partition),只不过,使用Hive 的动态分区,需要进行相应的配置。

2.2 分桶表


分区提供一个隔离数据和优化查询的便利方式。不过,并非所有的数据集都可形成合理的分区。对于一张表或者分区,Hive 可以进一步组织成桶,也就是更为细粒度的数据范围划分。

分桶是将数据集分解成更容易管理的若干部分的另一个技术。

分区针对的是数据的存储路径;分桶针对的是数据文件。

2.3 抽样查询


3 函数

3.1 系统内置函数


(1)查看系统自带的函数

hive> show functions;

(2)显示自带的函数的用法

hive> desc function upper;

(3)详细显示自带的函数的用法

hive> desc function extended upper;

3.2 常用内置函数

3.2.1 空字段赋值


NVL:给值为NULL的数据赋值,它的格式是NVL(value,default_value)。它的功能是如果value为NULL,则NVL函数返回default_value的值,否则返回value的值,如果两个参数都为NULL,则返回NULL。

举例:如果员工的 comm 为 NULL,则用 -1 代替。

hive (default)> select comm, nvl(comm, -1) from emp;
OK
comm    _c1
NULL    -1.0
300.0   300.0
500.0   500.0
NULL    -1.0
1400.0  1400.0
NULL    -1.0
NULL    -1.0
NULL    -1.0
NULL    -1.0
0.0     0.0
NULL    -1.0
NULL    -1.0
NULL    -1.0
NULL    -1.0

3.2.2 CASE WHEN THEN ELSE END


举例:求出不同部门男女各多少人。

(1)在 /opt/module/hive-3.1.2/datas 路径上创建emp_sex.txt

[huwei@hadoop101 datas]$ vim emp_sex.txt
悟空	A	男
大海	A	男
宋宋	B	男
凤姐	A	女
婷姐	B	女
婷婷	B	女

(2)创建Hive表并导入数据

hive (default)>
create table emp_sex(
    name string,     --姓名
    dept_id string, --部门id
    sex string       --性别
) 
row format delimited fields terminated by "\t";
load data local inpath '/opt/module/hive-3.1.2/datas/emp_sex.txt' 
into table emp_sex;

(3)按需求查询数据

hive (default)>
select 
    dept_id,
    sum(case sex when '男' then 1 else 0 end) male_count,
    sum(case sex when '女' then 1 else 0 end) female_count
from emp_sex
group by dept_id;

在这里插入图片描述

3.2.3 行转列


行转列,可以是一行转一列多行转一列

CONCAT(string A/col, string B/col…):返回输入字符串连接后的结果,支持任意多个输入字符串。

CONCAT_WS(separator, str1, str2,...):它是一个特殊形式的 CONCAT()。第一个参数剩余参数间的分隔符。分隔符可以是与剩余参数一样的字符串。如果分隔符是NULL,返回值也将为NULL。这个函数会跳过分隔符参数后的任何NULL和空字符串。分隔符将被加到被连接的字符串之间。

注意:CONCAT_WS must be “string or array”

聚合函数,将同一列的值聚合成一个数组。

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

举例:把星座和血型一样的人归类到一起。

(1)在 /opt/module/hive-3.1.2/datas 路径上创建 constellation.txt文件

[huwei@hadoop101 datas]$ vim constellation.txt
孙悟空	白羊座	A
大海	射手座	A
宋宋	白羊座	B
猪八戒	白羊座	A
凤姐	射手座	A
苍老师	白羊座	B

(2)创建Hive表并导入数据

hive (default)>
create table person_info(
    name string,            --姓名
    constellation string, --星座
    blood_type string      --血缘
) 
row format delimited fields terminated by "\t";

load data local inpath "/opt/module/hive-3.1.2/datas/constellation.txt" 
into table person_info;

(3)按需求查询数据

hive (default)>
select 
    t1.c_b, 
    concat_ws("|",collect_set(t1.name))
from (
    select 
        name,
        concat_ws(',',constellation,blood_type) c_b
    from person_info
)t1 
group by t1.c_b;

在这里插入图片描述

3.2.4 列转行


Split(str, separator):将字符串按照后面的分隔符切割,转换成字符array。
EXPLODE(col):explode(array) 将 array 列表里的每个元素生成一行;
explode(map) 将 map 里的每一对元素作为一行,其中 key 为一列,value 为一列;

LATERAL VIEW

  • 用法:LATERAL VIEW udtf(expression) tableAlias AS columnAlias
  • 解释:LATERAL VIEW 用于和 split,explode 等UDTF一起使用,它能够将一行数据拆成多行数据,在此基础上可以对拆分后的数据进行聚合。LATERAL VIEW 允许将一个函数应用于表中的列,并将其结果与原始表中的其他列一起输出,从而生成一个新的虚拟表。

注意mysql 不支持

需求:将电影分类中的数组数据展开

(1)在 /opt/module/hive-3.1.2/datas 路径上,创建 movie.txt 文件

[huwei@hadoop101 datas]$ vim movie_info.txt
《疑犯追踪》	悬疑,动作,科幻,剧情
《Lie to me》	悬疑,警匪,动作,心理,剧情
《战狼2》	战争,动作,灾难

(2)创建hive表并导入数据

hive (default)>
create table movie_info(
    movie string,     --电影名称
    category string   --电影分类
) 
row format delimited fields terminated by "\t";

load data local inpath "/opt/module/hive-3.1.2/datas/movie_info.txt" 
into table movie_info;

(3)按需求查询数据

hive (default)>
select 
    movie,
    category_name 
from movie_info 
lateral view
explode(split(category,",")) movie_info_tmp AS category_name ;

在这里插入图片描述

3.2.5 JSON数据处理


(1)应用场景

JSON数据格式是数据存储及数据处理中最常见的结构化数据格式之一,很多场景下公司都会将数据以 JSON 格式存储在HDFS中,当构建数据仓库时,需要对JSON格式的数据进行处理和分析,那么就需要在Hive中对JSON格式的数据进行解析读取。

例如,当前我们JSON格式的数据如下:

在这里插入图片描述

每条数据都以JSON形式存在,每条数据中都包含4个字段,分别为设备名称【device】、设备类型【deviceType】、信号强度【signal】和信号发送时间【time】,现在我们需要将这四个字段解析出来,在Hive表中以每一列的形式存储,最终得到以下Hive表:

在这里插入图片描述

Hive中为了实现 JSON 格式的数据解析,提供了两种解析JSON数据的方式,在实际工作场景下,可以根据不同数据,不同的需求来选择合适的方式对JSON格式数据进行处理。

  • 方式一:使用JSON函数进行处理
    Hive中提供了两个专门用于解析JSON字符串的函数:get_json_object、json_tuple,这两个函数都可以实现将JSON数据中的每个字段独立解析出来,构建成表。

  • 方式二:使用Hive内置的JSON Serde加载数据
    Hive中除了提供JSON的解析函数以外,还提供了一种专门用于加载JSON文件的Serde来实现对JSON文件中数据的解析,在创建表时指定Serde,加载文件到表中,会自动解析为对应的表格式。

(2)get_json_object

作用:解析JSON字符串,可以从JSON字符串中返回指定的某个对象列的值。

语法格式:

get_json_object(json_txt, path) - Extract a json object from path

参数

  • 第一个参数:指定要解析的JSON字符串
  • 第二个参数:指定要返回的字段,通过$.columnName的方式来指定path

特点:每次只能返回JSON对象中一列的值

使用示例:

--切换数据库
use db_function;

--创建表
create table tb_json_test1 (
  json string
);

--加载数据
load data local inpath '/export/data/device.json' into table tb_json_test1;

--查询数据
select * from tb_json_test1;

-- 获取json中的每个列
select
       --获取设备名称
       get_json_object(json,"$.device") as device,
       --获取设备类型
         get_json_object(json,"$.deviceType") as deviceType,
       --获取设备信号强度
       get_json_object(json,"$.signal") as signal,
       --获取时间
       get_json_object(json,"$.time") as stime
from tb_json_test1;

在这里插入图片描述

(3)json_tuple

作用:用于实现JSON字符串的解析,可以通过指定多个参数来解析JSON返回多列的值。

语法格式:

json_tuple(jsonStr, p1, p2, ..., pn) 
like get_json_object, but it takes multiple names and return a tuple

参数:

  • 第一个参数:指定要解析的JSON字符串
  • 第二个参数:指定要返回的第1个字段
  • ……
  • 第N+1个参数:指定要返回的第N个字段

特点:

  • 功能类似于get_json_object,但是可以调用一次返回多列的值。属于UDTF类型函数
  • 返回的每一列都是字符串类型
  • 一般搭配 lateral view 使用

使用示例:

select
       --解析所有字段
       json_tuple(json,"device","deviceType","signal","time") as (device,deviceType,signal,stime)
from tb_json_test1;

在这里插入图片描述

搭配侧视图使用

select
       json,device,deviceType,signal,stime
from tb_json_test1
lateral view json_tuple(json,"device","deviceType","signal","time") b
as device,deviceType,signal,stime;

(4)JSON Serde

上述解析JSON的过程中是将数据作为一个JSON字符串加载到表中,再通过JSON解析函数对JSON字符串进行解析,灵活性比较高,但是对于如果整个文件就是一个JSON文件,在使用起来就相对比较麻烦。Hive中为了简化对于JSON文件的处理,内置了一种专门用于解析JSON文件的Serde解析器,在创建表时,只要指定使用JSONSerde解析表的文件,就会自动将JSON文件中的每一列进行解析。

使用示例:

--切换数据库
use db_function;

--创建表
create table tb_json_test2 (
   device string,
   deviceType string,
   signal double,
   `time` string
 )
ROW FORMAT SERDE 'org.apache.hive.hcatalog.data.JsonSerDe'
STORED AS TEXTFILE;

--加载数据
load data local inpath '/export/data/device.json' into table tb_json_test2;

-- 查询数据
select * from tb_json_test2;

在这里插入图片描述

不论是Hive中的JSON函数还是自带的JSONSerde,都可以实现对于JSON数据的解析,工作中一般根据数据格式以及对应的需求来实现解析。如果数据中每一行只有个别字段是JSON格式字符串,就可以使用JSON函数来实现处理,但是如果数据加载的文件整体就是JSON文件,每一行数据就是一个JSON数据,那么建议直接使用JSONSerde来实现处理最为方便。

3.2.6 窗口函数(开窗函数)


窗口函数:就是给聚合函数开操作的窗口(在明细查询中,展示汇总的结果

相关函数说明:

  • OVER():指定分析函数工作的数据窗口大小,这个数据窗口大小可能会随着行的变而变化。
  • rows between……and……(默认累积聚合行为是:从第一行聚合到当前行。)
    • CURRENT ROW:当前行。
    • n PRECEDING:往前n行数据。
    • n FOLLOWING:往后n行数据。
    • UNBOUNDED PRECEDING 前无边界,表示从前面的起点。
    • UNBOUNDED FOLLOWING 后无边界,表示到后面的终点。
  • LAG(col,n,default_val):往前第n行数据。
  • LEAD(col,n, default_val):往后第n行数据。
  • FIRST_VALUE (col,true/false):当前窗口下的第一个值,第二个参数为true,跳过空值。
  • LAST_VALUE (col,true/false):当前窗口下的最后一个值,第二个参数为true,跳过空值。
  • NTILE(n):将每个分组内的数据分为指定的若干个桶里(分为若干个部分),并且为每一个桶分配一个桶编号,编号从1开始。如果不能平均分配,则优先分配较小编号的桶,并且各个桶中能放的行数最多相差1。
    有时会有这样的需求:如果数据排序后分为三部分,业务人员只关心其中的一部分,如何将这中间的三分之一数据拿出来呢?NTILE函数即可以满足。

注意:n必须为int类型。

举例:

(1)创建本地 business.txt

[huwei@hadoop101 datas]$ vi business.txt
jack,2017-01-01,10
tony,2017-01-02,15
jack,2017-02-03,23
tony,2017-01-04,29
jack,2017-01-05,46
jack,2017-04-06,42
tony,2017-01-07,50
jack,2017-01-08,55
mart,2017-04-08,62
mart,2017-04-09,68
neil,2017-05-10,12
mart,2017-04-11,75
neil,2017-06-12,80
mart,2017-04-13,94

(2)创建Hive表并导入数据

hive (default)>
create table business(
    name string, 
    orderdate string,
    cost int
) 
row format delimited fields terminated by ',';

load data local inpath "/opt/module/hive-3.1.2/datas/business.txt" 
into table business;

(3)按需求查询数据

需求一:查询在2017年4月份购买过的顾客及总人数

hive (default)>
select 
    name,
    count(*) over () 
from business 
where substring(orderdate,1,7) = '2017-04' 
group by name;

substring(str,pos,len):返回 str 字符串从 pos 位置开始长度为 len 的子串;

在这里插入图片描述

需求二:查询顾客的购买明细及所有顾客的月购买总额

hive (default)>
select
    name,
    orderdate,
    cost,
    sum(cost) over(partition by month(orderdate)) name_month_cost
from business;

在这里插入图片描述

需求三:查询顾客的购买明细及每个顾客的月购买总额

hive (default)>
select
    name,
    orderdate,
    cost,
    sum(cost) over(partition by name,month(orderdate)) name_month_cost
from business;

在这里插入图片描述

需求四:将每个顾客的 cost 按照日期进行累加

hive (default)>
select 
  name, 
  orderdate,
  cost,
  sum(cost) over(partition by name order by orderdate rows between UNBOUNDED PRECEDING and CURRENT ROW) lj
from 
  business; 

order by之后的rows between UNBOUNDED PRECEDING and CURRENT ROW 可以省略不写

在这里插入图片描述

需求五:将所有顾客的cost按照日期进行累加

hive (default)>
select 
  name, 
  orderdate,
  cost,
  sum(cost) over(order by orderdate ) lj
from 
  business; 

在这里插入图片描述

需求六:求所有顾客的购买明细及按照日期进行排序后,求

  • 所有顾客的cost 第一行 到 当前行 累加
  • 所有顾客的cost 上一行 到 当前行 的累加和
  • 所有顾客的cost 上一行 到 下一行 的累加和
  • 所有顾客的cost 当前行 到 下一行 的累加和
  • 所有顾客的cost 当前行 到 最后一行的累加和
hive (default)>
select
  name,
  orderdate,
  cost,
  sum(cost) over(order by orderdate rows between UNBOUNDED PRECEDING and CURRENT ROW) f_c,
  sum(cost) over(order by orderdate rows between 1 PRECEDING and CURRENT ROW ) p_c,
  sum(cost) over(order by orderdate rows between 1 PRECEDING and 1 FOLLOWING ) p_n,
  sum(cost) over(order by orderdate rows between CURRENT ROW and 1 FOLLOWING ) c_n,
  sum(cost) over(order by orderdate rows between CURRENT ROW and UNBOUNDED FOLLOWING ) c_l
from
  business;

在这里插入图片描述

需求七:查询顾客购买明细以及上次的购买时间和下次购买时间

hive (default)>
select
   name,
   cost, 
   orderdate c_orderdate,
   lag(orderdate ,1 ,'1970-01-01') over(partition by name  order by orderdate) p_orderdate,
   lead(orderdate ,1 ,'9999-01-01') over(partition by name  order by orderdate) n_orderdate
from 
  business;

在这里插入图片描述

需求八:查询顾客每个月第一次的购买时间 和 每个月的最后一次购买时间

hive (default)>
select
   name,
   cost, 
   first_value(orderdate,true) over(partition by name,month(orderdate) order by orderdate rows between UNBOUNDED PRECEDING and UNBOUNDED FOLLOWING) first_tme,
   last_value(orderdate,true) over(partition by name,month(orderdate) order by orderdate rows between UNBOUNDED PRECEDING and UNBOUNDED FOLLOWING) lat_time
from
 business;

在这里插入图片描述

需求九:查询前20%时间的订单信息

hive (default)>
select 
  t1.name, 
  t1.orderdate,
  t1.cost ,
  t1.gid
from 
(select
  name, 
  orderdate,
  cost, 
  ntile(5) over(order by orderdate ) gid
from 
  business) t1
where t1.gid = 1 ; 

在这里插入图片描述

总结:

  • over():会为每条数据都开启一个窗口,默认的窗口大小就是当前数据集的大小。
  • over(partition by …) :会按照指定的字段进行分区, 将分区字段的值相同的数据划分到相同的区。每个区中的每条数据都会开启一个窗口,每条数据的窗口大小默认为当前分区数据集的大小。
  • over(order by …) :会在窗口中按照指定的字段对数据进行排序。会为每条数据都开启一个窗口,默认的窗口大小为从数据集开始到当前行。
  • over(partition by … order by …) :会按照指定的字段进行分区, 将分区字段的值相同的数据划分到相同的区, 在每个区中会按照指定的字段进行排序。会为每条数据都开启一个窗口,默认的窗口大小为当前分区中从数据集开始到当前行.
  • over(partition by … order by … rows between … and …) : 指定每条数据的窗口大小。

关键字:

  • order by : 全局排序 或者 窗口函数中排序。
  • distribute by : 分区
  • sort by : 区内排序
  • cluster by : 分区排序
  • partition by : 窗口函数中分区
  • partitioned by : 建表指定分区字段
  • clustered by : 建表指定分桶字段

3.2.7 排名窗口函数


RANK() 排序相同时会重复,总数不会变。
DENSE_RANK() 排序相同时会重复,总数会减少。
ROW_NUMBER() 会根据顺序计算。

上述这三个函数用于分组TopN的场景非常适合。

需求:按照学科进行排名

(1)创建本地score.txt

[huwei@hadoop101 datas]$ vi score.txt
name    subject score
孙悟空  语文    87
孙悟空  数学    95
孙悟空  英语    68
大海    语文    94
大海    数学    56
大海    英语    84
宋宋    语文    64
宋宋    数学    86
宋宋    英语    84
婷婷    语文    65
婷婷    数学    85
婷婷    英语    78

(2)创建hive表并导入数据

hive (default)>
create table score(
    name string,
    subject string, 
    score int
) 
row format delimited fields terminated by "\t";

load data local inpath '/opt/module/hive-3.1.2/datas/score.txt' 
into table score;

(3)按需求查询数据

hive (default)>
select 
    name,
    subject,
    score,
    rank() over(partition by subject order by score desc) rp,
    dense_rank() over(partition by subject order by score desc) drp,
    row_number() over(partition by subject order by score desc) rmp
from score;

在这里插入图片描述

3.3 自定义函数


Hive自带了一些函数,比如:max/min等,但是数量有限,自己可以通过自定义UDF来方便的扩展。

当Hive提供的内置函数无法满足你的业务处理需要时,此时就可以考虑使用用户自定义函数(UDF:user-defined function)。

根据用户自定义函数类别分为以下三种:

  • UDF(User-Defined-Function)
    一进一出。
  • UDAF(User-Defined Aggregation Function)
    用户自定义聚合函数,多进一出。
    类似于:count/max/min
  • UDTF(User-Defined Table-Generating Functions)
    用户自定义表生成函数,一进多出。
    如:lateral view explode()

编程步骤:
(1)继承Hive提供的类

  • org.apache.hadoop.hive.ql.udf.generic.GenericUDF
  • org.apache.hadoop.hive.ql.udf.generic.GenericUDTF;

(2)重写类中的抽象方法

(3)在hive的命令行窗口创建函数

  • 添加jar:add jar linux_jar_path
  • 创建function:create [temporary] function [dbname.]function_name AS class_name;

(4)在hive的命令行窗口删除函数

  • drop [temporary] function [if exists] [dbname.]function_name;

3.4 自定义UDF函数


需求:自定义一个UDF实现计算给定基本数据类型的长度,例如:

hive(default)> select my_len("abcd");
4

(1)创建一个 Maven 工程Hive

(2)导入依赖

<dependencies>
	<dependency>
		<groupId>org.apache.hive</groupId>
		<artifactId>hive-exec</artifactId>
		<version>3.1.2</version>
	</dependency>
</dependencies>

(3)创建一个类

package com.huwei.hive;

import org.apache.hadoop.hive.ql.exec.UDFArgumentException;
import org.apache.hadoop.hive.ql.exec.UDFArgumentLengthException;
import org.apache.hadoop.hive.ql.exec.UDFArgumentTypeException;
import org.apache.hadoop.hive.ql.metadata.HiveException;
import org.apache.hadoop.hive.ql.udf.generic.GenericUDF;
import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspector;
import org.apache.hadoop.hive.serde2.objectinspector.primitive.PrimitiveObjectInspectorFactory;

// 自定义UDF类
// 继承Hive提供的GenericUDF类
public class udf extends GenericUDF {

    /**
     * 初始化方法
     * @param arguments 传入到函数中的参数对应的类型的鉴别器对象
     * @return 指定函数的返回值类型对应的鉴别器对象
     * @throws UDFArgumentException
     */
    @Override
    public ObjectInspector initialize(ObjectInspector[] arguments) throws UDFArgumentException {
        // 1. 校验函数的参数个数
        if(arguments== null || arguments.length != 1 ){
            throw new UDFArgumentLengthException("函数的参数个数不正确!!!");
        }
        // 2. 校验函数的参数类型
        if(!arguments[0].getCategory().equals(ObjectInspector.Category.PRIMITIVE)){
            throw new UDFArgumentTypeException(0,"函数的参数类型不正确!!!");
        }
        // 3. 返回函数的返回值类型对应的鉴别器类型
        return PrimitiveObjectInspectorFactory.javaIntObjectInspector;
    }

    /**
     * 函数的核心处理方法
     * @param arguments 传入到函数的参数
     * @return 函数的返回值
     * @throws HiveException
     */
    @Override
    public Object evaluate(DeferredObject[] arguments) throws HiveException {
        Object argument = arguments[0].get() ;
        if(argument == null){
            return 0 ;
        }
        return argument.toString().length();
    }

    @Override
    public String getDisplayString(String[] strings) {
        return null;
    }
}

(4)创建临时函数

① 打成 jar 包上传到服务器 /opt/module/hive-3.1.2/datas/hive-1.0-SNAPSHOT.jar

② 将jar包添加到 hive 的 classpath,临时生效

hive (default)> add jar /opt/module/hive-3.1.2/datas/hive-1.0-SNAPSHOT.jar;

③ 创建临时函数与开发好的 java class关联

hive (default)> create temporary function my_len as 'com.huwei.hive.udf';

④ 即可在hql中使用自定义的临时函数

hive (default)> 
select 
    ename,
    my_len(ename) ename_len 
from emp;

⑤ 删除临时函数

hive (default)> drop temporary function my_len;

注意:临时函数只跟会话有关系,跟库没有关系。只要创建临时函数的会话不断,在当前会话下,任意一个库都可以使用,其他会话全都不能使用。

4 压缩和存储

4.1 优缺点


优点:

  • 减少存储磁盘空间,降低单节点的磁盘IO。
  • 由于压缩后的数据占用的带宽更少,因此可以加快数据在Hadoop集群流动的速度,减少网络传输带宽

缺点:

  • 需要花费额外的时间/CPU做压缩和解压缩计算。

4.2 Hadoop支持的压缩


MR支持的压缩编码

在这里插入图片描述

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

在这里插入图片描述

压缩性能的比较:

在这里插入图片描述

LZO 和 bzip2 的压缩文件可切分,其中 bzip2 的压缩率较高, LZO 的解压缩速度较快。Snappy的解压缩速度最快,压缩速度可达 250M / s,解压速度可达 500M / s。

4.3 Hive中的压缩设置


首先说明mapreduce哪些过程可以设置压缩:需要分析处理的数据在进入map前可以压缩,然后解压处理,map处理完成后的输出可以压缩,这样可以减少网络I/O(reduce通常和map不在同一节点上),reduce拷贝压缩的数据后进行解压,处理完成后可以压缩存储在hdfs上,以减少磁盘占用量。

4.3.1 开启hive中间传输数据压缩功能


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

(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.3 开启Reduce输出阶段压缩


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

(1)开启Hive最终输出数据压缩功能

hive (default)>set hive.exec.compress.output=true;

(2)开启MapReduce最终输出数据压缩

hive (default)>set mapreduce.output.fileoutputformat.compress=true;

(3)设置MapReduce最终数据输出压缩方式

hive (default)> set mapreduce.output.fileoutputformat.compress.codec =org.apache.hadoop.io.compress.SnappyCodec;

(4)设置mapreduce最终数据输出压缩为块压缩

hive (default)> set mapreduce.output.fileoutputformat.compress.type=BLOCK;

4.4 文件存储格式


Hive支持的存储数的格式主要有:TEXTFILE(行式存储) 、SEQUENCEFILE(行式存储)、ORC(列式存储)、PARQUET(列式存储)。

4.4.1 列式存储和行式存储


在这里插入图片描述

如图所示左边为逻辑表,右边第一个为行式存储,第二个为列式存储。

(1)行存储的特点

优点:

  • 相关的数据是保存在一起,比较符合面向对象的思维,因为一行数据就是一条记录
    这种存储格式比较方便进行 INSERT/UPDATE操作

缺点:

  • 如果查询只涉及某几个列,它会把整行数据都读取出来,不能跳过不必要的列读取。当然数据比较少,一般没啥问题,如果数据量比较大就比较影响性能
    由于每一行中,列的数据类型不一致,导致不容易获得一个极高的压缩比,也就是空间利用率不高,不是所有的列都适合作为索引。

(2)列存储的特点

优点:

  • 查询时,只有涉及到的列才会被查询,不会把所有列都查询出来,即可以跳过不必要的列查询;
  • 高效的压缩率,不仅节省储存空间也节省计算内存和CPU;
  • 任何列都可以作为索引;

缺点:

  • INSERT/UPDATE很麻烦或者不方便;
  • 不适合扫描小量的数据

4.4.2 TextFile格式


默认格式,数据不做压缩,磁盘开销大,数据解析开销大。可结合Gzip、Bzip2使用,但使用Gzip这种方式,hive不会对数据进行切分,从而无法对数据进行并行操作。

4.4.3 Orc格式


Orc (Optimized Row Columnar)是Hive 0.11版里引入的新的存储格式。

如下图所示可以看到每个Orc文件由1个或多个stripe组成,每个stripe一般为HDFS的块大小,每一个stripe包含多条记录,这些记录按照列进行独立存储,对应到Parquet中的row group的概念。每个Stripe里有三部分组成,分别是Index Data,Row Data,Stripe Footer:

在这里插入图片描述

(1)Index Data:一个轻量级的index,默认是每隔1W行做一个索引。这里做的索引应该只是记录某行的各字段在Row Data中的offset。

(2)Row Data:存的是具体的数据,先取部分行,然后对这些行按列进行存储。对每个列进行了编码,分成多个Stream来存储。

(3)Stripe Footer:存的是各个Stream的类型,长度等信息。
每个文件有一个File Footer,这里面存的是每个Stripe的行数,每个Column的数据类型信息等;每个文件的尾部是一个PostScript,这里面记录了整个文件的压缩类型以及FileFooter的长度信息等。在读取文件时,会seek到文件尾部读PostScript,从里面解析到File Footer长度,再读FileFooter,从里面解析到各个Stripe信息,再读各个Stripe,即从后往前读。

4.4.4 Parquet格式


Parquet文件是以二进制方式存储的,所以是不可以直接读取的,文件中包括该文件的数据和元数据,因此Parquet格式文件是自解析的。

(1)行组(Row Group):每一个行组包含一定的行数,在一个HDFS文件中至少存储一个行组,类似于orc的stripe的概念。

(2)列块(Column Chunk):在一个行组中每一列保存在一个列块中,行组中的所有列连续的存储在这个行组文件中。一个列块中的值都是相同类型的,不同的列块可能使用不同的算法进行压缩。

(3)页(Page):每一个列块划分为多个页,一个页是最小的编码的单位,在同一个列块的不同页可能使用不同的编码方式。

通常情况下,在存储Parquet数据的时候会按照Block大小设置行组的大小,由于一般情况下每一个Mapper任务处理数据的最小单位是一个Block,这样可以把每一个行组由一个Mapper任务处理,增大任务执行并行度。Parquet文件的格式。

总结

  • 存储文件的压缩比:ORC > Parquet > textFile
  • 存储文件的查询速度:ORC > TextFile > Parquet
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值