打怪升级之小白的大数据之旅(六十七)
Hive旅程第八站:Hive的函数
上次回顾
上一章,我们学习了如何对数据进行拆分–分区表与分桶表,使用分区表与分桶表,可以加快我们的查询效率。。本章节是Hive除了查询之外的另外一个重点,我们的工作中,Hive的操作就是查询和函数组合来做的
Hive的函数
系统内置函数
系统内置函数有很多,我们可以使用下面的命令,查看系统自带函数,当我们忘记该函数如何使用,除了度娘和我这个博客外,还可以根据下面的命令进行查看
查看系统自带函数
show functions;
查看自带函数的用法
desc function sum;
详细显示自带函数的用法
desc function extended upper;
常用内置函数
我就介绍几个我经常会使用的几个函数,别的函数,大家有需求可以根据上面命令或者问问度娘
空字段赋值 NVL
当我们需要查询的数据有NULL空字段时,我们可以使用nvl函数为空字段进行赋值
语法格式:
/*
它的功能是如果value为NULL,
则NVL函数返回default_value的值,
否则返回value的值,如果两个参数都为NULL ,则返回NULL
*/
NVL( value,default_value)
下面通过示例来演示一下nvl 的用法
- 我们以前面的员工表为测试数据
如果员工的comm为NULL,则用-1代替
select comm,nvl(comm, -1) from emp;
如果员工的comm为NULL,则用领导id代替
select comm, nvl(comm,mgr) from emp;
CASE 语句
CASE 和 JAVA一样 它用于对一个常量进行条件判断
测试数据结构如下:
name | dept_id | sex |
---|---|---|
悟空 | A | 男 |
八戒 | A | 男 |
唐僧 | B | 男 |
紫霞 | A | 女 |
嫦娥 | B | 女 |
观音 | B | 女 |
需求:求出不同部门的男女各多少人
测试数据:
vim /opt/module/hive/dbdata/test/emp_sex.txt
悟空 A 男
八戒 A 男
唐僧 B 男
紫霞 A 女
嫦娥 B 女
观音 B 女
创建表
-- 创建表
create table emp_sex(
name string,
dept_id string,
sex string)
row format delimited fields terminated by "\t";
导入数据
load data local inpath '/opt/module/hive/dbdata/test/emp_sex.txt' into table emp_sex;
使用CASE按照需求查询数据
select
dept_id,
sum(case sex when '男' then 1 else 0 end) man_count,
sum(case sex when '女' then 1 else 0 end) woman_count
from
emp_sex
group by dept_id;
if 函数
和java一样,可以使用case完成的语句,就可以使用if来完成,那么我们直接使用if来完成上面的需求
select
dept_id,
sum(if(sex='男',1,0)) man_count,
sum(if(sex='女',1,0)) woman_count
from emp_sex
group by dept_id;
行转列 CONCAT
行转列就是将一行一行的数据拼接后,生成为一列数据
行转列函数
CONCAT 普通连接
- 返回输入字符串连接后的结果,支持任意个输入字符串;
CONCAT(string A/col, string B/col…)
-- 连接 hello world 两个单词
select concat("hello","world");
/*
结果:
helloworld
*/
CONCAT_WS 指定分隔符连接
-
是特殊的CONCAT,它可以指定分隔符
-
第一个参数是剩余参数间的分隔符,分隔符可以是与剩余参数一样的字符串
-
如果分隔符是 NULL,返回值也将为 NULL
-
这个函数会跳过分隔符参数后的任何 NULL 和空字符串
-
分隔符将被加到被连接的字符串之间
-
CONCAT_WS must be “string or array”
CONCAT_WS(separator, str1, str2,...) -- 使用 | 连接 hello world 两个单词 select concat("|", hello","world"); /* 结果: hello|world */
COLLECT_SET 去重连接
- 函数只接受基本数据类型,它的主要作用是将某字段的值进行去重汇总,产生array类型字段
- collect_set和collect_list的案例我会在下面演示,简单demo无法实现
COLLECT_LIST 不去重连接
- 函数只接受基本数据类型,它的主要作用是将某字段的值进行不去重汇总,产生array类型字段
行转列案例
数据结构如下:
name | constellation | blood_type |
---|---|---|
张三 | 白羊座 | A |
李四 | 射手座 | A |
王五 | 白羊座 | B |
赵六 | 白羊座 | A |
孙七 | 射手座 | A |
周八 | 白羊座 | B |
需求:将星座和血型相同的人归类到一起,最终数据如下
射手座,A 李四|孙七
白羊座,A 张三|赵六
白羊座,B 王五|周八
测试数据
# 创建数据文件
vim /opt/module/hive/dbdata/constellation/constellation.txt
# 数据内容
张三 白羊座 A
李四 射手座 A
王五 白羊座 B
赵六 白羊座 A
孙七 射手座 A
周八 白羊座 B
创建表
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/dbdata/constellation/constellation.txt" into table person_info;
查询星座和血型相同的人,并归类到一起
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;
列转行 EXPLODE
列转行就是将一列数据转为一行一行的数据,我们通常对列转行操作叫做炸开
列转行函数
Split(str, separator)
- 将字符串按照后面的分隔符切割,转换成字符array
select split("hello,world",",");
/*
输出结果:
["hello","world"]
*/
EXPLODE(col)
- 将hive一列中复杂的array或者map结构拆分成多行
- 大家看清楚了,这里接收的参数是array或者map结构的数据
select explode(split("hello,world",","));
/*
输出结果
hello
world
*/
LATERAL VIEW
- ateral view用于和split, explode等UDTF一起使用
- 它能够将一行数据拆成多行数据,在此基础上可以对拆分后的数据进行聚合
- 我们通常称它为侧写表
- lateral view首先为原始表的每行调用UDTF,UTDF会把一行拆分成一或者多行,lateral view再把结果组合,产生一个支持别名表的虚拟表,具体演示请看下面的案例
列转行案例
数据结构
movie | category |
---|---|
《疑犯追踪》 | 悬疑,动作,科幻,剧情 |
《Lie to me》 | 悬疑,警匪,动作,心理,剧情 |
《战狼2 | 战争,动作,灾难 |
需求:将电影分类进行拆分展示,效果如下
《疑犯追踪》 悬疑
《疑犯追踪》 动作
《疑犯追踪》 科幻
《疑犯追踪》 剧情
《Lie to me》 悬疑
《Lie to me》 警匪
《Lie to me》 动作
《Lie to me》 心理
《Lie to me》 剧情
《战狼2》 战争
《战狼2》 动作
《战狼2》 灾难```
测试数据:
```sql
# 创建数据文件
vim /opt/module/hive/dbdata/movie/movie.txt
# 数据如下
《疑犯追踪》 悬疑,动作,科幻,剧情
《Lie to me》 悬疑,警匪,动作,心理,剧情
《战狼2》 战争,动作,灾难
创建表
create table movie_info(
movie string,
category string)
row format delimited fields terminated by "\t";
加载数据
load data local inpath "/opt/module/hive/dbdata/movie/movie.txt" into table movie_info;
使用列转行将电影分类进行拆分
SELECT movie,category_name
FROM movie_info
lateral VIEW
explode(split(category,",")) movie_info_tmp AS category_name;
开窗函数(窗口函数) OVER
开窗函数算是函数中的一个重点,它可以完成使一些复杂的逻辑变得很简单,后面我会通过案例来说明
相关函数
核心的函数:OVER()
- over()函数内部的参数
- CURRENT ROW:当前行
- n PRECEDING:往前n行数据
- n FOLLOWING:往后n行数据
- UNBOUNDED:无边界
- UNBOUNDED PRECEDING 前无边界,表示从前面的起点,
- UNBOUNDED FOLLOWING后无边界,表示到后面的终点
其他函数(over()函数的左边)
- 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开始,对于每一行,NTILE返回此行所属的组的编号。注意:n必须为int类型
开窗函数案例
数据结构
name | orderdate | cost |
---|---|---|
张三 | 2021-01-01 | 10 |
李四 | 2021-01-02 | 15 |
张三 | 2021-02-03 | 23 |
李四 | 2021-01-04 | 29 |
张三 | 2021-01-05 | 46 |
张三 | 2021-04-06 | 42 |
李四 | 2021-01-07 | 50 |
张三 | 2021-01-08 | 55 |
王五 | 2021-04-08 | 62 |
王五 | 2021-04-09 | 68 |
赵六 | 2021-05-10 | 12 |
王五 | 2021-04-11 | 75 |
赵六 | 2021-06-12 | 80 |
王五 | 2021-04-13 | 94 |
需求:
- 查询在2021年4月份购买过商品的顾客及总人数
- 查询顾客的购买明细及月购买总额
- 上述的场景, 将每个顾客的cost按照日期进行累加
- 查询顾客购买明细以及上次的购买时间和下次购买时间
- 查询顾客每个月第一次的购买时间 和 每个月的最后一次购买时间
- 查询前20%时间的订单信息
测试数据:
# 创建数据文件
vim /opt/module/hive/dbdata/order/business.txt
# 数据如下
张三 2021-01-01 10
李四 2021-01-02 15
张三 2021-02-03 23
李四 2021-01-04 29
张三 2021-01-05 46
张三 2021-04-06 42
李四 2021-01-07 50
张三 2021-01-08 55
王五 2021-04-08 62
王五 2021-04-09 68
赵六 2021-05-10 12
王五 2021-04-11 75
赵六 2021-06-12 80
王五 2021-04-13 94
创建表
create table business(
name string,
orderdate string,
cost int
) ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t';
加载数据
load data local inpath "/opt/module/hive/dbdata/order/business.txt" into table business;
查询在2021年4月份购买过商品的顾客及总人数
select
name,
count(*) over()
from business
where month(orderdate)=4
group by name ;
查询顾客的购买明细及月购买总额
select
name,
orderdate,
cost,
sum(cost) over(partition by name,month(orderdate)) name_month_cost
from business;
将每个顾客的cost按照日期进行累加
select
name,
orderdate,
cost,
sum(cost) over(partition by name order by orderdate )
from business;
查询顾客购买明细以及上次的购买时间和下次购买时间
select
name,orderdate,cost,
lag(orderdate,1,'1970-01-01') over(PARTITION by name order by orderdate) prev_time,
lead(orderdate,1,'1970-01-01') over(PARTITION by name order by orderdate) next_time
from business;
查询顾客每个月第一次的购买时间 和 每个月的最后一次购买时间
select
name,
orderdate,
cost,
FIRST_VALUE(orderdate) over(partition by name,month(orderdate) order by orderdate rows between UNBOUNDED PRECEDING and UNBOUNDED FOLLOWING) first_time,
LAST_VALUE(orderdate) over(partition by name,month(orderdate) order by orderdate rows between UNBOUNDED PRECEDING and UNBOUNDED FOLLOWING) last_time
from business;
查询前20%时间的订单信息
select * from (
select name,orderdate,cost, ntile(5) over(order by orderdate) sorted
from business
) t
where sorted = 1;
排序 Rank
Rank排序与order by 最大的不同就在于它只能配合over()开窗函数使用
函数说明
rank()
- 排序相同时会重复(考虑并列,会跳号),总数不会变
- rank就相当于我们学校考试时,有两个第一名(分数相同),那么下一个排名就是第三名,没有第二名
dense_rank()
- 排序相同时会重复(考虑并列,不跳号),总数会减少
- 同样以上述举例,分数相同,依旧有两个第一名,但是下一个学生为第二名
ow_number()
- 会根据顺序计算(不考虑并列,不跳号,行号)
- 没有并列第一这一说,可能会根据某个条件,将分数相同的人排为第一、第二名
为了更形象地说明这几个排序,我就按照学校考试成绩来举例
数据结构
name | subject | score |
---|---|---|
悟空 | 语文 | 87 |
悟空 | 数学 | 95 |
悟空 | 英语 | 68 |
八戒 | 语文 | 94 |
八戒 | 数学 | 56 |
八戒 | 英语 | 84 |
沙僧 | 语文 | 64 |
沙僧 | 数学 | 86 |
沙僧 | 英语 | 84 |
紫霞 | 语文 | 65 |
紫霞 | 数学 | 85 |
紫霞 | 英语 | 78 |
需求:根据每门学科进行成绩排名
测试数据
# 创建测试数据文件
vim /opt/module/hive/dbdata/score/score.txt
# 数据如下
悟空 语文 87
悟空 数学 95
悟空 英语 68
八戒 语文 94
八戒 数学 56
八戒 英语 84
沙僧 语文 64
沙僧 数学 86
沙僧 英语 84
紫霞 语文 65
紫霞 数学 85
紫霞 英语 78
创建表
create table score(
name string,
subject string,
score int)
row format delimited fields terminated by "\t";
加载数据
load data local inpath "/opt/module/hive/dbdata/score/score.txt" into table score;
使用三种排序方式将每科的考试的成绩进行排名
from score;
select name,
subject,
score,
rank() over(partition by subject order by score desc) rank_score,
dense_rank() over(partition by subject order by score desc) denser_score,
row_number() over(partition by subject order by score desc) row_score
from score;
自定义函数
函数分类
- 看到这里,常用的函数就介绍完毕了,当HQL中内置的函数不能满足我们的需求,Hive还为我们提供了自定义函数的方法,学习如何自定义函数前,我们先总结一下函数的分类
- 函数分为三大类
- UDF:一进一出,UDF就类似我们的upper、substr函数
- UDAF:多进一出,UDAF就是我们通常说的聚合函数,例如sum avg
- UDTF:一进多出,UDTF就类似我们前面说的炸开函数,它可以将一行数据炸开为多列
自定义UDF函数
- 我就按照最简单的UDF自定义举例
- 其他自定义函数具体的实现方法可以参考官网的说明文档:https://cwiki.apache.org/confluence/display/Hive/HivePlugins
编程步骤
- 继承Hive提供的自定义函数类
org.apache.hadoop.hive.ql.udf.generic.GenericUDF org.apache.hadoop.hive.ql.udf.generic.GenericUDTF;
- 实现类中抽象方法
- 将创建好的类打包到服务器的hive lib文件夹下
- 在hive命令行窗口创建函数
-- 添加jar包
add jar linux_jar_path
-- 创建函数
create [temporary] function [dbname.]function_name AS class_name;
-- 在hive的命令行窗口删除函数
drop [temporary] function [if exists] [dbname.]function_name;
自定义UDF案例
既然需要添加jar包才可以实现自定义函数,那么必然要进行java编码,我定义一个简单的UDF需求:计算给定字符串的长度
select my_len("hello")
-- 结果:5
创建Maven工程
- 这个就不再演示了,前面写了太多了
导入依赖
// 在pom.xml文件中添加依赖
<dependencies>
<dependency>
<groupId>org.apache.hive</groupId>
<artifactId>hive-exec</artifactId>
<version>3.1.2</version>
</dependency>
</dependencies>
创建并继承自定义函数类,并实现计算方法
package com.company.hive.udf;
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 MyLength extends GenericUDF {
/**
* 初始化方法,里面要做三件事
* 1.约束函数传入参数的个数
* 2.约束函数传入参数的类型
* 3.约束函数返回值的类型
* @param arguments 函数传入参数的类型
* @return
* @throws UDFArgumentException
*/
@Override
public ObjectInspector initialize(ObjectInspector[] arguments) throws UDFArgumentException {
//1.约束函数传入参数的个数
if (arguments.length != 1) {
throw new UDFArgumentLengthException("Input Args Num Error,You can only input one arg...");
}
//2.约束函数传入参数的类型
if (!arguments[0].getCategory().equals(ObjectInspector.Category.PRIMITIVE)) {
throw new UDFArgumentTypeException(0,"Input Args Type Error,You can only input PRIMITIVE Type...");
}
//3.约束函数返回值的类型
return PrimitiveObjectInspectorFactory.javaIntObjectInspector;
}
/**
* 函数逻辑处理方法
* @param arguments 函数传入参数的值
* @return
* @throws HiveException
*/
@Override
public Object evaluate(DeferredObject[] arguments) throws HiveException {
//获取函数传入参数的值
Object o = arguments[0].get();
//将object转换成字符串
int length = o.toString().length();
//因为在上面的初始化方法里面已经对函数返回值类型做了约束,必须返回一个int类型
//所以我们要在这个地方直接返回length
return length;
}
/**
* 返回显示字符串方法,这个方法不用管,直接返回一个空字符串
* @param children
* @return
*/
@Override
public String getDisplayString(String[] children) {
return "";
}
}
打包jar并上传到hive的Lib文件夹下(利用xftp/rz等工具上传),并改一个名字
mv
/opt/module/hive/lib/code-1.0-SNAPSHOT.jar /opt/module/hive/lib/my_len.jar
添加jar包t添加到hive中
add jar /opt/module/hive/lib/my_len.jar;
创建临时函数与开发好的java class关联
create temporary function my_len as "com.company.MyLength";
使用自定义的函数查看效果
select my_len("hello");
-- 输出:5
删除临时函数
drop temporary function my_len;
注意:
- 临时函数只是针对当前的hive命令行窗口有用,关闭、退出当前hive界面就失效了
- 下面为大家介绍如何创建永久函数
创建永久函数
- 在$HIVE_HOME下面创建mylib目录,(为了便于管理,就不放在lib下了)
- 将jar包上传到$HIVE_HOME/mylib下,然后重启hive
- 创建永久函数
create function my_len2 as "com.company.MyLength";
使用自定义的函数查看效果
select my_len2("hello");
-- 输出:5
- 即可在hql中使用自定义的永久函数
drop function my_len2;
注意:
- 永久函数创建的时候,在函数名之前需要自己加上库名,如果不指定库名的话,会默认把当前库的库名给加上。
- 永久函数使用的时候,需要在指定的库里面操作,或者在其他库里面使用的话加上 库名.函数名
总结
本章节介绍了一下Hive中的函数以及一些常用的函数,当我们有特殊需求时,可以使用自定义函数来满足我们的业务逻辑,好了,整个Hive的相关使用就到这里,后面几章我为大家介绍一下Hive的优化以及一个综合案例