0%

Hadoop-19-Hive4分区表、分桶表与函数

分区表与分桶表

我们创建一个hive表时,此时在hdfs上就在默认路径下创建了一个以表的名字命名的文件夹。

image-20230809140220687

Hive表中的数据在hdfs上则是对应文件夹下的所有文件。在查询表中数据时,其实就是将文件夹下的所有文件进行读取,在海量数据的场景下,这无疑是非常耗时的,并且在实际生产环境中,往往会进行查询过滤。

所以,如何在海量数据的场景下进行高效的查询过滤呢?

分区表

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

分区表基本操作

  • 创建分区表

例如,我们对前面的部分信息表进行模拟,将部门信息表按照时间进行分区存储。

1
2
3
4
5
6
7
8
9
10
create table if not exists dept_partition(
deptno int,
dname string,
loc string
)

partitioned by (day string)
row format delimited
fields terminated by '\t'
lines terminated by '\n';

注意:分区字段不能在表中出现,可以将分区字段看作表的伪列。

  • 数据准备

为每个分区准备数据,我们根据日期对日志进行管理,通过部门信息模拟,首先在目录/opt/module/hive-3.1.2/datas/创建以下三个文件。

dept_20200401.log

1
2
10	行政部	1700
20 财务部 1800

dept_20200402.log

1
2
30	教学部	1900
40 销售部 1700

dept_20200403.log

1
2
50	运营部	2000
60 人事部 1900

接下来向dept_partition表中的分区加载数据

1
2
3
4
5
6
load data local inpath '/opt/module/hive-3.1.2/datas/dept_20200401.log'
into table dept_partition partition (day="20200401");
load data local inpath '/opt/module/hive-3.1.2/datas/dept_20200402.log'
into table dept_partition partition (day="20200402");
load data local inpath '/opt/module/hive-3.1.2/datas/dept_20200403.log'
into table dept_partition partition (day="20200403");

数据加载完后,在HDFS中即可看到对应的分区目录。

image-20230809141518726

  • 查询分区表的分区详情
1
show partitions dept_partition;

image-20230809141834015

  • 使用分区表进行数据查询
1
select * from dept_partition;

image-20230809142049381

直接将分区表数据全部打印出来,可以看到其实分区字段在使用的时候可以当成普通字段使用。

单分区查询:

1
select * from dept_partition where day="20200402";

image-20230809141944051

多分区联合查询:

1
select * from dept_partition where day in ("20200401", "20200403");

image-20230809142212251

  • 增加分区

增加单个分区:

1
2
alter table dept_partition add partition (day="20200404");
show partitions dept_partition;

image-20230809142349468

增加多个分区

1
2
alter table dept_partition add partition (day="20200405") partition (day="20200406");
show partitions dept_partition;

添加多个分区时使用的是空格去连接不同的分区名。

image-20230809142441721

  • 删除分区

删除单个分区

1
2
alter table dept_partition drop partition (day="20200406");
show partitions dept_partition;

image-20230809142648384

删除多个分区

1
2
alter table dept_partition drop partition (day="20200405"), partition (day="20200404");
show partitions dept_partition;

image-20230809142730153

二级分区

思考: 在根据日期分区后,如果一天的日志数据量也很大,如何再将数据拆分?

这个时候考虑创建二级分区,在天目录下再细分为小时。

创建二级分区表

1
2
3
4
5
6
7
8
9
10
create table if not exists dept_partition2(
deptno int,
dname string,
loc string
)

partitioned by (day string, hour string)
row format delimited
fields terminated by '\t'
lines terminated by '\n';

加载数据

1
2
3
4
5
6
7
8
9
10
11
12
-- 加载数据
-- 向分区表中加载数据(需指定分区)
load data local inpath '/opt/module/hive-3.1.2/datas/dept_20200401.log'
overwrite into table dept_partition2 partition (day="20200401", hour="8");
load data local inpath '/opt/module/hive-3.1.2/datas/dept_20200401.log'
overwrite into table dept_partition2 partition (day="20200401", hour="9");
load data local inpath '/opt/module/hive-3.1.2/datas/dept_20200401.log'
overwrite into table dept_partition2 partition (day="20200401", hour="10");
load data local inpath '/opt/module/hive-3.1.2/datas/dept_20200402.log'
overwrite into table dept_partition2 partition (day="20200402", hour="11");
load data local inpath '/opt/module/hive-3.1.2/datas/dept_20200403.log'
overwrite into table dept_partition2 partition (day="20200403", hour="12");

这个时候进入到HDFS中就可以看到,进入到以及分区目录下后,可以看到二级分区的目录。

image-20230809144053919

在二级分区内部就有我们上传的具体数据文件。

image-20230809144136441

直接查询整个表的数据。

1
select * from dept_partition2;

image-20230809144216851

查询二级分区表的分区结果

1
show partitions dept_partition2;

image-20230809144336881

思考一个问题,我们在HDFS相应的目录下可以直接看到对应的分区目录,那么如果我们直接把对应的数据文件放到HDFS上,并且按照相应的规则手动创建好分区目录,在Hive中能否查询出来呢?

例如:我们手动在HDFS上创建目录/user/hive/warehouse/dept_partition2/day=20200404/hour=10,然后自己手动将数据上传到该目录下

image-20230809144715467

此时能够通过Hive查询到该数据呢?

1
select * from dept_partition2 where day="20200404" and hour="10";

此时明显是无法查询出来的。

image-20230809144824074

原因其实也很简单,我们在这里只是机械的将文件放入到了Hive中,而Hive中的数据是由元数据+实际数据构成,我们在这里实际上是只上传了实际数据,但是Hive并不知道。

要想要Hive知道该数据也很简单,一般有两种方式:

  1. 第一种方式是自己手动创建对应的分区,相当于自己手动告诉Hive元数据。

    1
    alter table dept_partition2 add partition (day="20200404", hour="10");

    此时再次查找对应的数据就可以找到了

    1
    select * from dept_partition2 where day="20200404" and hour="10";

    image-20230809145201244

  2. 第二种方式是让Hive自己去修复这个表的元数据和实际数据的对应规则和关系

    执行以下命令进行修复

    1
    msck repair table dept_partition2;

    此时Hive会执行MR去修复元数据与真实数据的对应关系,如果元数据缺失分区会自动创建。

    为了演示效果,首先,执行以下命令将数据复制到day=20200405/hour=10

    1
    hdfs dfs -cp /user/hive/warehouse/dept_partition2/day=20200404 /user/hive/warehouse/dept_partition2/day=20200405

    接下来执行修复命令

    1
    msck repair table dept_partition2;

    image-20230809150022429

    此时就会自动在Hive中添加分区的元数据信息。

动态分区

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

动态分区参数设置

  1. 开启动态分区功能(默认true,开启)

    1
    set hive.exec.dynamic.partition=true;
  2. 设置为非严格模式(动态分区的模式,默认strict,表示必须指定至少一个分区为静态分区,nonstrict模式表示允许所有的分区字段都可以使用动态分区。)

    1
    set hive.exec.dynamic.partition.mode=nonstrict;
  3. 在所有执行MR的节点上,最大一共可以创建多少个动态分区。默认1000

    1
    set hive.exec.max.dynamic.partitions=1000;
  4. 每个执行MR的节点上,最大可以创建多少个动态分区。

    该参数需要根据实际的数据来设定。比如:源数据中包含了一年的数据,即day字段有365个值,那么该参数就需要设置成大于365,如果使用默认值100,则会报错。

    1
    set hive.exec.max.dynamic.partitions.pernode=100;
  5. 整个MR Job中,最大可以创建多少个HDFS文件。默认100000

    1
    set hive.exec.max.created.files=100000;
  6. 当有空分区生成时,是否抛出异常。一般不需要设置。默认false

    1
    set hive.error.on.empty.partition=false;

案例实操:

dept表中的数据按照地区(loc字段),插入到目标表dept_partition_dynamic的相应分区中。

创建分区表:

1
2
3
4
5
6
7
8
create table if not exists dept_partition_dynamic(
deptno int,
dname string
)
partitioned by (loc int)
row format delimited
fields terminated by '\t'
lines terminated by '\n';

注意这里要使用loc字段进行自动分区,那么在创建表的时候就不应该包含该字段。

以动态分区的方式向dept_partition_dynamic表中插入数据

1
2
3
4
-- 设置为非严格模式
set hive.exec.dynamic.partition.mode="nonstrict";
-- 以动态分区的方式向`dept_partition_dynamic`表中插入数据
insert into table dept_partition_dynamic partition (loc) select deptno, dname, loc from dept;

要使用动态分区,在查询的时候必须将分区字段放到最后,例如这里的loc字段。

此时在HDFS下的目录/user/hive/warehouse/dept_partition_dynamic中就可以看到以下分区目录。

image-20230809151635615

查询dept_partition_dynamic分区结果

1
show partitions dept_partition_dynamic;

image-20230809151817331

自动分区完成。

分桶表

分区提供一个隔离数据和优化查询的便利方式。但是,并非所有的数据集都可形成合理的分区。

对于一张表或者分区,Hive 可以进一步组织成桶,也就是更为细粒度的数据范围划分。

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

分区针对的是数据的存储路径(细分文件夹);分桶针对的是数据文件(按规则多文件放一起)。

创建分桶表

1
2
3
4
5
6
7
8
9
10
11
12
create table emp_bucket(
empno int,
ename string,
job string,
sal double,
deptno int
)
clustered by(deptno)
into 4 buckets -- 放到4个桶
row format delimited
fields terminated by '\t'
lines terminated by '\n';

注意,在这里一定要保证设置的reduce数量大于等于分桶的数量,否则会导致无法分桶报错。

也即参数mapreduce.job.reduces的值。

加载数据

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

此时会提交2MR任务将数据分为4个桶进行存储。

image-20230809153038541

注意:此时如果采用本地文件可能会导致报错,因为我们最终是把这个任务封装成了一个MR任务提交到集群,那么该集群在执行MR任务的时候回去找相关的本地文化,如果该MR任务提交到了其他服务器中,其他服务器内是没有这个数据的,可能会导致报错,报错几率约等于2/3

image-20230809155430938

所以建议这里不要采用本地文件数据local参数。

函数

系统内置函数

  • 查看系统所有自带函数
1
show functions;

image-20230809213137724

可以看到系统中一共有289个内置函数。

  • 显示自带的函数用法
1
desc function year;

image-20230809213253395

  • 显示详细自带函数的用法
1
desc function extended year;

image-20230809213335922

常用内置函数

空字段赋值(NVL)

函数说明:

1
desc function extended nvl;

image-20230809213432521

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

如果两个参数都为NULL,则返回NULL

案例实操:采用数据表emp的数据。

  1. 如果job的值为NULL,那么将其赋值为总经理

    1
    select ename, nvl(job, '总经理') from emp;

    image-20230809213935406

  2. 如果job值为NULL,那么使用其deptno代替。

    1
    select ename, nvl(job, deptno), deptno from emp;

    image-20230809214118705

判断语句(CASE WHEN THEN ELSE END, IF)

函数说明:

1
desc function extended case;

image-20230809214247246

函数功能:

1
CASE a WHEN b THEN c [WHEN d THEN e]* [ELSE f] END

如果a=b,则返回c;如果a=d,则返回e;其他情况返回f

案例实操:

首先,准备数据,在/opt/module/hive-3.1.2/datas/下创建emp_sex.txt文件,内容如下:

1
2
3
4
5
6
悟空,A,男
大海,A,男
宋宋,B,男
凤姐,A,女
婷姐,B,女
婷婷,B,女
1
2
3
4
5
6
7
8
9
10
11
-- 数据准备
create table emp_sex(
name string,
dept_id string,
sex string
)
row format delimited
fields terminated by ","
LINES TERMINATED BY '\n';
load data local inpath "/opt/module/hive-3.1.2/datas/emp_sex.txt" into table emp_sex;
select * from emp_sex;

image-20230809214814111

  • 求出不同部门男女各多少人
1
2
3
4
5
6
7
-- 求出不同部门男女各多少人。
select
dept_id,
sum(case sex when '男' then 1 else 0 end) man_num,
sum(case sex when '女' then 1 else 0 end) woman_num
from emp_sex
group by dept_id;

image-20230809215229665

IF语句比较简单

IF(条件表达式,A,B):当条件表达式成立时返回A,条件表达式不成立时返回B

1
select if(1>2, '111', '000');

image-20230810145528930

行转列

image-20230809215312257

相关函数说明:

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

    1
    select concat('1', '2', '3', 'a', 'b', 'c');

    image-20230809215503902

  2. CONCAT_WS(separator, str1, str2,...):也是将字符串进行连接,不过在这里可以使用分隔符进行连接,第一个参数就是指定的分隔符。

    1
    select concat_ws(',', '2', '3', 'a', 'b', 'c')

    image-20230809215621833

  3. COLLECT_SET(col):将某个字段的值放入一个数组中,并对结果进行去重。

    1
    select collect_set(deptno) from emp;

    image-20230809215814523

  4. COLLECT_LIST(col):将某个字段的值放入一个数组中,这里不对结果进行去重,这个是与COLLECT_SET的主要区别。

    1
    select collect_list(deptno) from emp;

    image-20230809220016443

案例实操:

name constellation blood_type
孙悟空 白羊座 A
大海 射手座 A
宋宋 白羊座 B
猪八戒 白羊座 A
凤姐 射手座 A
苍老师 白羊座 B

案例需求:把星座和血型一样的人归类到一起。结果如下:

image-20230809220144977

首先进行数据准备,依然是在datas目录下创建文件constellation.txt

1
2
3
4
5
6
孙悟空,白羊座,A
大海,射手座,A
宋宋,白羊座,B
猪八戒,白羊座,A
凤姐,射手座,A
唐三藏,白羊座,B

数据导入:

1
2
3
4
5
6
7
8
9
10
create table if not exists constellation(
name string,
constellation string,
blood_type string
)
row format delimited
fields terminated by ','
lines terminated by '\n';
load data local inpath '/opt/module/hive-3.1.2/datas/constellation.txt' into table constellation;
select * from constellation;

image-20230809220511397

案例实现sql

1
2
3
4
5
6
select t1.newcol, concat_ws('|', collect_list(name))
from (
select name, concat_ws(',', constellation, blood_type) newcol
from constellation
) t1
group by newcol;

image-20230809221045116

列转行

image-20230809221105025

相关函数说明:

首先查询,前面我们创建的persioninfo表,内容如下所示。

image-20230809221351594

  1. EXPLODE(col):hive表的一列中复杂的array或者map结构拆分成多行。

    1
    select explode(friends) from personinfo;

    image-20230809221424569

    如果是map那么拆分出来为keyvalue两列。

    1
    select explode(children) from personinfo;

    image-20230809221517491

  2. SPLIT(string str, string regex): 按照regex字符串分割str,会返回分割后的字符串数组。

    1
    select split('1,2,3,4,5,6,a,b,c', ',');

    image-20230809221620770

  3. LATERAL VIEW

    LATERAL VIEW udtf(expression) tableAlias AS columnAlias

    用于和split,explodeUDTF一起使用,它能够将一列数据拆成多行数据,在此基础上可以对拆分后的数据进行聚合。lateral view首先为原始表的每行调用UDTFUDTF会报一行拆分成一行或者多行,lateral view再把结果组合,产生一个支持别名表的虚拟表。

案例演练:

movie category
《疑犯追踪》 悬疑,动作,科幻,剧情
《Lie to me》 悬疑,警匪,动作,心理,剧情
《战狼2》 战争,动作,灾难

将上述数据转化为以下格式:

1
2
3
4
5
6
7
8
9
10
11
12
《疑犯追踪》      悬疑
《疑犯追踪》 动作
《疑犯追踪》 科幻
《疑犯追踪》 剧情
《Lie to me》 悬疑
《Lie to me》 警匪
《Lie to me》 动作
《Lie to me》 心理
《Lie to me》 剧情
《战狼2》 战争
《战狼2》 动作
《战狼2》 灾难

数据准备,在datas下创建文件movie_info.txt内容如下:

1
2
3
《疑犯追踪》	悬疑,动作,科幻,剧情
《Lie to me》 悬疑,警匪,动作,心理,剧情
《战狼2》 战争,动作,灾难

然后创建数据表,并导入数据:

1
2
3
4
5
6
7
8
9
create table if not exists movie_info(
movie_name string,
category string
)
row format delimited
fields terminated by '\t'
lines terminated by '\n';
load data local inpath '/opt/module/hive-3.1.2/datas/movie_info.txt' overwrite into table movie_info;
select * from movie_info;

image-20230809222242387

案例实现sql

1
2
3
select movie_name, category_
from movie_info
LATERAL VIEW explode(split(category, ',')) tmp_table as category_;

image-20230809222536557

窗口函数(开窗函数)

1
Function(arg1 ……) over([partition by arg1 ……] [order by arg1 ……] [<window_expression>])

image-20230809222818063

  1. 如果不指定partition by,则不对数据进行分区,换句话说,所有数据看作同一个分区。

  2. 如果不指定order by, 则不对各分区进行排序,通常用于那些顺序无关的窗口函数,如sum()

  3. 如果不指定窗口子句:

    不指定order by,默认使用分区内所有行,等同于

    1
    Function() over(rows between unbounded precedeing and unbounded following)

    如果指定order by,默认使用分区内第起点到当前行,等同于

    1
    Function() over(rows between unbounded preceding and current row)

案例演练,接下来使用多个案例,具体演示相关开窗函数的用法:

首先,进行数据准备。在datas下新建文件business.txt,内容如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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

导入数据:

1
2
3
4
5
6
7
8
9
10
11
-- 开窗函数
create table if not exists business(
name string,
ordertime string,
cost int
)
row format delimited
fields terminated by ','
lines terminated by '\n';
load data local inpath '/opt/module/hive-3.1.2/datas/business.txt' into table business;
select * from business;

image-20230810110800883

  1. 查询在20174月份购买过的顾客及总人数

    1
    2
    3
    4
    5
    6
    select
    name,
    count(1) over()
    from business
    where month(ordertime) == '4'
    group by name;

    image-20230810111247528

  2. 查询顾客的购买明细及月购买总额

    1
    2
    3
    4
    5
    6
    select
    name,
    ordertime,
    cost,
    sum(cost) over(partition by name, month(ordertime))
    from business;

    image-20230810111607101

    加了partition by分区后,默认的窗口大小是整个分区。

  3. 上述的场景, 将每个顾客的cost按照日期进行累加

    1
    2
    3
    4
    5
    6
    select
    name,
    ordertime,
    cost,
    sum(cost) over(partition by name, month(ordertime) order by ordertime)
    from business;

    image-20230810111806118

    加了order by后,每行的分区大小是从分区的起始位置到当前行。rows between unbounded perceding and current now

  4. 所有行进行累加

    1
    2
    3
    4
    5
    6
    select
    name,
    ordertime,
    cost,
    sum(cost) over()
    from business;

    image-20230810112240048

  5. 按照name 分组,组内数据累加

    1
    2
    3
    4
    5
    6
    select
    name,
    ordertime,
    cost,
    sum(cost) over(partition by name)
    from business;

    image-20230810113443238

  6. 按照name分区,组内数据按照日期有序累加

    1
    2
    3
    4
    5
    6
    select
    name,
    ordertime,
    cost,
    sum(cost) over(partition by name order by ordertime)
    from business;

    image-20230810113506709

  7. 按照name分区,组内数据按照日期排序,由起点到当前行进行累加

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    -- 案例7:按照name分区,组内数据按照日期排序,由起点到当前行进行累加
    select
    name,
    ordertime,
    cost,
    sum(cost)
    over(
    partition by name
    order by ordertime
    rows between unbounded preceding and current row )
    from business;

    image-20230810113537273

  8. 按照name分区,组内数据按照日期排序,由前一行和当前行进行做聚合

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    --- 案例8:按照name分区,组内数据按照日期排序,由前一行和当前行进行做聚合
    select
    name,
    ordertime,
    cost,
    sum(cost)
    over(
    partition by name
    order by ordertime
    rows between 1 preceding and current row )
    from business;

    image-20230810113604957

  9. 按照name分区,组内数据按照日期排序,由当前行和前一行和后一行做聚合

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    -- 案例9:按照name分区,组内数据按照日期排序,由当前行和前一行和后一行做聚合
    select
    name,
    ordertime,
    cost,
    sum(cost)
    over(
    partition by name
    order by ordertime
    rows between 1 preceding and 1 following )
    from business;

    image-20230810113630949

  10. 按照name分区,组内数据按照日期排序,由当前行和后面所有行作聚合

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    -- 案例10:按照name分区,组内数据按照日期排序,由当前行和后面所有行作聚合
    select
    name,
    ordertime,
    cost,
    sum(cost)
    over(
    partition by name
    order by ordertime
    rows between current row and unbounded following )
    from business;

    image-20230810113653276

  11. 查询每个顾客上次的购买时间

    1
    2
    3
    4
    5
    6
    7
    -- 案例11:查询每个顾客上次的购买时间
    select
    name,
    ordertime,
    lag(ordertime, 1) over (partition by name order by ordertime) before_ordertime,
    cost
    from business;

    image-20230810113715743

  12. 查询每个顾客下次的购买时间

    1
    2
    3
    4
    5
    6
    7
    -- 案例12:查询每个顾客下次的购买时间
    select
    name,
    ordertime,
    lead(ordertime, 1) over (partition by name order by ordertime) next_ordertime,
    cost
    from business;

    image-20230810113748134

  13. 查询前20%时间的订单信息

    1
    2
    3
    4
    5
    6
    7
    8
    9
    select
    t1.name, ordertime, cost, part_num
    from (select name,
    ordertime,
    cost,
    ntile(5) over (order by ordertime) part_num
    from business
    ) t1
    where t1.part_num = 1;

    image-20230810113958505

    ntile函数的原理是将所有数据按照分区将数据分为若干组,每条数据都会给上一个组号,这个时候我们再按照组号进行筛选就可以筛选出对应的时间。

    1
    2
    3
    4
    5
    select 	name,
    ordertime,
    cost,
    ntile(5) over (order by ordertime) part_num
    from business

    image-20230810114013507

排序函数(RANK, DENSE_RANK, ROW_NUMBER)

  1. RANK():排序相同时会重复,总数不会变 重复的名次一样但是下一名名次会以前面人数+1来定。
  2. DENSE_RANK():排序相同时会重复,总数会减少 就是若有重复则最后一名的名次不会和总数相等即并列。
  3. ROW_NUMBER():会根据顺序计算,字段相同就按排头字段继续排。

案例演练:

首先进行数据准备,在datas目录下创建文件score.txt,文件内容如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
孙悟空	语文	87
孙悟空 数学 95
孙悟空 英语 68
大海 语文 94
大海 数学 56
大海 英语 84
宋宋 语文 64
宋宋 数学 86
宋宋 英语 84
婷婷 语文 65
婷婷 数学 85
婷婷 英语 78

创建表并导入数据

1
2
3
4
5
6
7
8
9
10
create table if not exists score(
name string,
subject string,
score int
)
row format delimited
fields terminated by '\t'
lines terminated by '\n';
load data local inpath '/opt/module/hive-3.1.2/datas/score.txt' overwrite into table score;
select * from score;

image-20230810114521208

接下来分别使用三个排序函数,计算每门学科成绩排名

1
2
3
4
5
6
7
select
name,
score,
rank() over (partition by subject order by score desc) rk_order,
dense_rank() over (partition by subject order by score desc) drk_order,
row_number() over (partition by subject order by score desc) rn_order
from score;

image-20230810115005589

其他常用函数

常用日期函数

  1. unix_timestamp:返回当前或指定时间的时间戳

    1
    2
    select unix_timestamp();
    select unix_timestamp("2020-10-28",'yyyy-MM-dd');
  2. from_unixtime:将时间戳转为日期格式

    1
    select from_unixtime(1603843200);
  3. current_date:当前日期

    1
    select current_date;
  4. current_timestamp:当前的日期加时间

    1
    select current_timestamp;
  5. to_date:抽取日期部分

    1
    select to_date('2022-10-28 12:12:12');
  6. year:获取年

    1
    select year('2022-10-28 12:12:12');
  7. month:获取月

    1
    select month('2022-10-28 12:12:12');
  8. day:获取日

    1
    select day('2022-10-28 12:12:12');
  9. hour:获取时

    1
    select hour('2022-10-28 12:13:14');
  10. minute:获取分

    1
    select minute('2020-10-28 12:13:14');
  11. second:获取秒

    1
    select second('2020-10-28 12:13:14');
  12. weekofyear:当前时间是一年中的第几周

    1
    select weekofyear('2020-10-28 12:12:12');
  13. dayofmonth:当前时间是一个月中的第几天

    1
    select dayofmonth('2020-10-28 12:12:12');
  14. months_between: 两个日期间的月份

    1
    select months_between('2020-04-01','2020-10-28');
  15. add_months:日期加减月

    1
    select add_months('2020-10-28',-3);
  16. datediff:两个日期相差的天数

    1
    select datediff('2020-11-04','2020-10-28');
  17. date_add:日期加天数

    1
    select date_add('2020-10-28',4);
  18. date_sub:日期减天数

    1
    select date_sub('2020-10-28',-4);
  19. last_day:日期的当月的最后一天

    1
    select last_day('2020-02-30');
  20. date_format(): 格式化日期

    1
    select date_format('2020-10-28 12:12:12','yyyy/MM/dd HH:mm:ss');

常用取整函数

  1. round: 四舍五入

    1
    2
    select round(3.14);
    select round(3.54);
  2. ceil: 向上取整

    1
    2
    select ceil(3.14);
    select ceil(3.54);
  3. floor: 向下取整

    1
    2
    select floor(3.14);
    select floor(3.54);

常用字符串操作函数

  1. upper: 转大写

    1
    select upper('low');
  2. lower: 转小写

    1
    select lower('low');
  3. length: 长度

    1
    select length("atguigu");
  4. trim: 前后去空格

    1
    select trim(" atguigu ");
  5. lpad: 向左补齐,到指定长度

    1
    select lpad('atguigu',9,'g');
  6. rpad: 向右补齐,到指定长度

    1
    select rpad('atguigu',9,'g');
  7. regexp_replace:使用正则表达式匹配目标字符串,匹配成功后替换!

    1
    SELECT regexp_replace('2020/10/25', '/', '-');

集合操作

  1. size: 集合中元素的个数

    1
    select size(friends) from test3;
  2. map_keys: 返回map中的key

    1
    select map_keys(children) from test3;
  3. map_values: 返回map中的value

    1
    select map_values(children) from test3;
  4. array_contains: 判断array中是否包含某个元素

    1
    select array_contains(friends,'bingbing') from test3;
  5. sort_array: 将array中的元素排序

    1
    select sort_array(friends) from test3;

多维分析

grouping sets:多维分析

自定义函数

自定义函数:Hive提供的内置函数无法满足你的业务处理需要时

此时就可以考虑使用用户自定义函数(UDF:user-defined function)。

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

  1. UDFUser-Defined-Function)—> 一进一出,例如:length
  2. UDAFUser-Defined Aggregation Function) —> 聚合函数多进一出,例如:count/max/min
  3. UDTFUser-Defined Table-Generating Functions)—> 炸裂函数一进多出,例如:explode()

自定义UDF函数

需求:自定义一个UDF实现计算给定字符串的长度,例如:

1
2
3
hive(default)> select my_len("abcd");
ok
4

案例实操:

创建Maven工程,然后创建包com.tipdm.MyHiveFunction接下来在包中创建类MyStringLength,输入以下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
package com.tipdm.MyHiveFunction;

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函数,需要继承自GenericUDF
* 需求:计算指定字符串的长度
*/
public class MyStringLength extends GenericUDF {
/**
* 初始化方法
* @param arguments
* The ObjectInspector for the arguments
* @return
* @throws UDFArgumentException
*/
@Override
public ObjectInspector initialize(ObjectInspector[] arguments) throws UDFArgumentException {
// 1. 判断输入参数的个数
if(arguments.length != 1)
throw new UDFArgumentLengthException("只允许输入一个参数!");

// 2. 判断输入参数的数据类型
if(!arguments[0].getCategory().equals(ObjectInspector.Category.PRIMITIVE))
throw new UDFArgumentTypeException(0, "请输入一个字符串");

// 3. 返回参数的鉴别器
return PrimitiveObjectInspectorFactory.javaStringObjectInspector;
}

/**
* 核心代码,计算方法。函数的逻辑处理
* @param arguments
* The arguments as DeferedObject, use DeferedObject.get() to get the
* actual argument Object. The Objects can be inspected by the
* ObjectInspectors passed in the initialize call.
* @return
* @throws HiveException
*/
@Override
public Object evaluate(DeferredObject[] arguments) throws HiveException {
if (arguments[0] == null)
return 0;

return arguments[0].get().toString().length();
}

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

接下来打包Maven工程,然后上传到datas目录。

image-20230810140225867

image-20230810140245372

创建临时函数

进入Hive客户端,然后输入以下内容,导入jar包,并创建临时函数。

1
2
3
4
-- 添加jar包
add jar /opt/module/hive-3.1.2/datas/MRDemo-1.0-SNAPSHOT.jar;
-- 添加函数
create temporary function my_len as "com.tipdm.MyHiveFunction.MyStringLength";

调用自定义临时函数:

1
select my_len("123123123");

image-20230810140432931

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

创建永久函数

因为add jar的方式本身也是临时生效,所以在创建永久函数的时候,需要执行路径(应且因为元数据的原因,这个路径还得是HDFS上的路径)

首先将jar包导入到HDFS.

1
hdfs dfs -put /opt/module/hive-3.1.2/datas/MRDemo-1.0-SNAPSHOT.jar /input/

然后在Hive客户端下使用以下命令创建永久函数。

1
create function my_len2 as "com.tipdm.MyHiveFunction.MyStringLength" using jar "hdfs://hadoop102:8020/input/MRDemo-1.0-SNAPSHOT.jar";

此时重新创建一个新的Hive客户端。

1
select ename, my_len2(ename) from emp;

image-20230810140935638

在使用时就会自动的去导入对应的jar包,并添加函数。

-------------本文结束感谢您的阅读-------------