Hive函数介绍:
最初Hive函数分为 内置函数 和 用户自定义函数两大类, 其中用户自定义函数又被分为3类, 分别是:
内置函数: 属于Hive自带的.
用户自定义函数:
UDF: 全称叫 User Defined Functions, 普通函数, 即: 一进一出.
例如: select * from stu;
UDAF: 全称叫 User Defined Aggregation Functions, 聚合函数, 即: 多进一出.
例如: select count(id) from stu;
UDTF: 全称叫 User Defined Table-Generating Functions, 表生成函数, 即: 一进多出.
例如: select explode(array(11, 22, 33));
后来发现用 UDF, UDAF, UDTF来划分Hive函数实在是太方便了, 于是提出了1个词, 叫: 函数标准扩大化, 即:
UDF, UDAF, UDTF本来是形容用户自定义函数的, 现在, hive中的函数没有内置函数 和 自定义函数之分了, 取而代之的是: UDF, UDAF, UDTF
问题: 如果某个函数我不会用怎么办?
答案: 查看Hive针对于该函数的 说明文档.
-- 查看Hive所有的内置函数
show functions ;
-- 查看函数的说明信息
describe function 函数; -- 简单信息
describe function extended 函数; -- 详细信息,包括 测试案例(有的没有)
相关函数:
字符串相关函数:
1. 字符串切割
-- spilt(参数1,参数2) 参数1: 要操作的参数. 参数2: 切割符
select spilt("aa,bb,cc",","); -- ["aa","bb","cc"]
-- substr(参数1,参数2,参数3) 和 substring(参数1,参数2,参数3)
-- 参1: 要操作的字符串. 参2: 起始索引, 参数3: 个数
-- 效果一样, 没有区别
select substr('2024-01-13 11:51:27', 1, 10);
select substring('2024-01-13 11:51:27', 1, 10);
2. 字符串拼接
-- concat() 拼接符默认为空
select concat('aa','bb','cc'); aabbcc
-- concat_ws(参数1,参数2) 可以指定拼接符 参数1:拼接符 参数2:要拼接的内容
select concat_ws('-','aa','bb','cc'); aa-bb-cc
3. 获取字符串长度
select length('abc');
4. 转大小写
select lower('ABC'); -- abc 转小写
select upper('abc'); -- ABC 转大写
5. 移除首尾空格
select trim(' aa bb '); -- 'aa bb'
6. 正则替换, 参数1:要被处理的字符串. 参数2:正则表达式. 参数3:用来替换的内容
select regexp_replace('100-200', '\\d+', '夯哥');
7. URL解析.
select parse_url('http://www.itcast.cn/path/p1.php?query=1', 'HOST'); -- www.itcast.cn, 主机名.
select parse_url('http://www.itcast.cn/path/p1.php?query=1', 'PATH'); -- /path/p1.php, 资源路径
select parse_url('http://www.itcast.cn/path/p1.php?username=admin01&password=pwd111', 'QUERY'); -- username=admin01&password=pwd111, 访问参数
select parse_url('http://www.itcast.cn/path/p1.php?username=admin01&password=pwd111', 'QUERY', 'username'); -- admin01, username参数值.
-- 8. 处理json字符串, 它是一种特定格式的字符串, 主要用于 传输数据的, 把数据封装成json字符串格式, 在各个框架中进行传输.
-- json解析函数:get_json_object(json_txt, path)
-- 注意: 整个json字符串用单引号'包裹, json字符串中的键, 值用双引号"包裹.
-- json字符串的格式: {键:值, 键: 值}
-- json数组的格式: [{键:值, 键: 值}, {键:值, 键: 值}, {键:值, 键: 值}] -- 索引从 0 开始.
select get_json_object('{"name":"杨过", "age":"18"}', '$.name'); -- 杨过, $表示json对象
select get_json_object('[{"name":"杨过", "age":"18"}, {"name":"小龙女", "age":"26"}]', '$.[1].name');
日期相关:
select current_date(); -- 获取当前时间, 年月日格式
select unix_timestamp(); -- 获取当前时间, unix格式, 即: 从时间原点(1970-01-01 00:00:00)截止到当前时间的 秒值.
select unix_timestamp('2024/01/12', 'YYYY/mm/dd'); -- 根据指点的时间, 获取其unix时间.
-- 根据秒值(unix时间) 获取其对应的 日期.
select from_unixtime(1684477644); -- 2023-05-19 06:27:24
-- 获取年月日.
select year('2023-05-19 14:31:02'); -- 2023
select month('2023-05-19 14:31:02'); -- 5
select day('2023-05-19 14:31:02'); -- 19
-- 时间相关操作, 增加, 减少, 比较.
select date_add('2023-05-19', 2); -- 2023-05-21
select date_add('2023-05-19', -2); -- 2023-05-17
select datediff('2023-05-19', '2023-05-20'); -- 前 - 后, -1
-- 扩展, 计算某年的2月有多少天.
-- 思路: 把时间设置为该年的3月1日, 然后往前推1天, 判断该天是月中的第几天, 即为结果.
select day(date_add('2023-03-01', -1));
select dayofmonth(date_add('2024-03-01', -1));
数字相关函数:
select rand(); -- 0.0 ~ 1.0, 包左不包右.
select round(4.3); -- 四舍五入, 原理: +0.5, 然后求地板数.
select abs(-10); -- 求绝对值
select ceil(5.3); -- 天花板数, 比该数大的所有数字中, 最小的那个整数(包括本身) 6
select ceil(5.0); -- 天花板数, 比该数大的所有数字中, 最小的那个整数(包括本身) 5
select floor(5.6); -- 地板数, 比该数小的所有数字中, 最大的那个整数(包括本身) 5
-- 自己实现 四舍五入.
select `floor`(5.3 + 0.5); -- 5
非空校验 相关函数:
-- if函数(条件表达式, 值1, 值2), 先执行条件表达式, 看起结果是否成立, 成立执行值1, 否则执行值2
select if(5 > 3, '郑州', '新乡'); -- 郑州
-- 非空校验.
select isnull('zz'); -- false
select isnull(null); -- true
select isnotnull('zz); -- true
select isnotnull(null); -- false
-- 空值转换, 判断第1个值是否为null, 如果不是就获取第1个值, 否则就用第2个值(类似于 默认值)
select nvl('zz', '张三'); -- zz
select nvl(null, '张三'); -- 张三
-- 非空查找 coalesce(值1, 值2....), 获取参数列表中, 第1个非null值
select coalesce(null, null, null); -- null
select coalesce(null, 11, 22); -- 11
-- case when转换.
select
case
when 5 > 3 then '大于'
when 5 < 3 then '小于'
else '等于'
end as eq ;
-- 如果只做等于判断, 可以变形为:
select
case 5
when 1 then '周1'
when 3 then '周3'
when 5 then '周5'
else '不知道周几, 睡迷糊了!'
end as week;
杂项函数 相关函数:
-- 类型转换相关.
select cast(10.3 as int); -- 10
select cast(10 as string); -- '10'
select cast('12.3' as double); -- 12.3
select cast('12.3a' as double); -- null
-- 获取哈希值.
select hash('张三'); -- -838675700
select mask_hash('张三'); -- 1d841bc0ee98309cb7916670b7f0fdef5f4c35150711a41405ef3633b56322cf
-- mask()函数, 脱敏函数, 顺序是: 大写字母, 小写字母, 数字
select mask('abc123ABC'); -- xxxnnnXXX, 默认: 大写字母X, 小写字母x, 数字n
select mask('abc123ABC', '大','小', '*'); -- xxxnnn***, 指定: 大写字母 大, 小写字母 小, 数字*
-- 对数组元素排序, 默认: 升序.
select sort_array(array(11, 33, 55, 22)); -- [11,22,33,55]
-- 加密函数.
select md5('pwd111'); -- 130353326a7bfab601f57757033b5b4a
行列转换入门:
-- 1. 建表, 添加表数据, 源数据格式为: "Chicago Bulls,1991|1992|1993|1996|1997|1998"
-- 处理后, 格式为: "Chicago Bulls", ""
create table the_nba_championship(
team_name string, -- 队名
champion_year array<string> -- 夺冠年份
) row format delimited
fields terminated by ',' -- 切割后, 数据格式为: "Chicago Bulls", "1991|1992|1993|1996|1997|1998"
collection items terminated by '|'; -- 切割后, 数据格式为: "Chicago Bulls", ["1991", "1992", "1993", "1996", "1997", "1998"]
-- 2. 查看表数据.
select * from the_nba_championship;
-- 3. 需求, 把上述的数据, 按照 队名, 年份(只有1个值)的方式做打印, 例如:
-- 实际开发中: 侧视图 lateral view() 一般会和 explode() 结合使用.
/*
列名: team_name champion_year
第1行: Chicago Bulls 1991
第2行: Chicago Bulls 1992
第3行: Chicago Bulls 1993
...
*/
-- 3.1 想办法对数据进行炸开, 这里用到炸裂函数 explode();
select explode(array('aa', 'bb', 'cc'));
-- 3.2 对夺冠年份进行 炸裂操作.
select explode(champion_year) from the_nba_championship;
-- 3.3 到这, 我们发现数据(夺冠年份)已经炸开了, 那么直接和原表一起查询即可. 如下:
select team_name, explode(champion_year) from the_nba_championship; -- 报错
-- 4. 最终版, 通过侧视图存储 炸裂后的内容, 然后和原表数据一起查询即可.
-- 侧视图解释: lateral view, 理解为: 临时的存储炸裂的内容.
-- 侧视图格式: lateral view 炸裂函数 侧视图名 as 炸裂后的列名.
select a.team_name, b.champion_year from the_nba_championship a
lateral view explode(champion_year) b as champion_year;
行列转换之: 行转列:
-- 1. 建表, 上传源文件.
create table row2col2(
col1 string,
col2 string,
col3 int
)row format delimited fields terminated by '\t';
-- 2. 查询表数据.
select * from row2col2;
-- 3. 实现: 行转列.
/*
行转列涉及到的函数:
数据采集函数:
collect_list() 采集数据, 可重复, 有序
collect_set() 采集数据, 唯一, 无序.
数据拼接函数:
concat() 拼接符: 默认为空.
concat_ws() 拼接符: 可以自定义.
*/
select concat(1, 2, 3);
select concat_ws('-', 1, 2, 3); -- 报错, concat_ws()拼接的内容必须是字符串形式.
select concat_ws('-', cast(1 as string), cast(2 as string), cast(3 as string)); -- 报错, concat_ws()拼接的内容必须是字符串形式.
-- 最终写法: 行转列代码实现.
select
col1, col2,
-- cast(col3 as string) 把 col3内容转成 字符串, 方便后续 concat_ws()拼接.
-- collect_list(cast(col3 as string)) 把 col3列的内容, 全部采集到, 有序, 可重复.
-- concat_ws('-', collect_list(cast(col3 as string))) 把 上述的内容, 按照-, 拼接到一起.
concat_ws('-', collect_list(cast(col3 as string))) as col3
from row2col2 group by col1, col2;
行列转换之: 列转行:
-- 1. 建表, 上传源文件.
create table col2row2(
col1 string,
col2 string,
col3 string
)row format delimited fields terminated by '\t';
-- 2. 查询表数据.
select * from col2row2;
-- 3. 实现: 列转行.
-- 3.1 把 col3列给炸开.
select explode(split(col3, ',')) from col2row2; -- '1,2,3' => ['1', '2', '3']
-- 3.2 侧视图 lateral view + explode()炸裂函数实现 列转行.
select t1.col1, t1.col2, t2.col3 from col2row2 t1
lateral view explode(split(col3, ',')) t2 as col3;
处理json字符串:
-- 思路1: 先用字符串把json内容存储下来, 然后对其进行处理.
-- 源数据格式: '{"device":"device_30","deviceType":"kafka","signal":98.0,"time":1616817201390}'
-- 1. 建表, 上传表数据.
create table test1_json(
json string
);
-- 2. 查询表数据.
select * from test1_json;
-- 3. 处理json.
-- 3.1 get_json_object()函数处理, 只能逐个处理, 属于 UDF函数, 一进一出.
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 `time`
from test1_json;
-- 3.2 json_tuple()函数处理, 批量处理, 属于 UDTF函数, 一进多出.
select
json_tuple(json, 'device', 'deviceType', 'signal', 'time') as (device, deviceType, signal, `time`)
from test1_json;
-- 3.3 上述json_tuple()的变形写法, 可以用侧视图(lateral view)来存储处理后的数据, 进行查询.
select device, deviceType, signal, `time` from test1_json
-- 侧视图格式 侧视图存储的内容 侧视图名字 侧视图中每一项(列)的别名
lateral view json_tuple(json, 'device', 'deviceType', 'signal', 'time') lv as device, deviceType, signal, `time`;
-- 思路2: 在建表的时候, 直接对 json字符串进行处理.
-- 1. 建表(直接对json进行处理, 采用指定的SerDe类, 即: JsonSerDe), 上传表数据.
create table test2_json(
device string,
deviceType string,
signal string,
`time` string
) row format serde 'org.apache.hive.hcatalog.data.JsonSerDe'; -- 如果不用默认的LazySimpleSerDe类, 采用指定SerDe类处理.
-- 2. 查询表数据.
select * from test2_json;
cte表达式
概述:
全称叫 Common Table Expression, 公共表表达式, 用来(临时)存储表结果的, 后续可以重复使用.
格式:
with CTE表达式的别名 as (
被CTE所存储的内容, 即: SQL查询语句
)
select ... from cte表达式别名;
-- 1. CTE表达式入门.
with t1 as (
select * from stu
)
select * from t1;
-- 2. from风格.
with t1 as (
select * from stu
)
from t1 select name, age;
-- 3. 链式写法(链式编程)
with t1 as (select * from stu),
t2 as (select * from t1 where id > 95010),
t3 as (select id, name, gender,age from t2 where id > 95010)
select name, gender from t3;
-- 4. CTE表达式结合 union 使用.
with t1 as (
select * from stu
)
select * from t1
union all -- 合并, 不去重.
select * from t1 limit 3; -- 44条 => 3条
-- 5. 用表 把 CTE的结果 永久存储.
create table hg1 as
with t1 as (
select * from stu
)
select id, name, age from t1;
select * from hg1;
-- 6. 用视图 把 CTE的结果 "永久"存储.
create view hg2 as
with t1 as (
select * from stu
)
select id, name, age from t1;
select * from hg2;
窗口函数相关
概述:
窗口函数指的是 over()函数, 它可以结合特定的函数一起使用, 完成不同的功能.
目的/作用:
窗口函数 = 给表新增一列, 至于新增的内容是什么, 取决于窗口函数和什么函数一起使用.
格式:
能和窗口函数一起使用的函数 over(partition by 分组字段 order by 排序字段 rows between 起始行 and 结束行)
能和窗口函数一起使用的函数解释:
聚合函数: count(), sum(), max(), min(), avg()
排序函数: row_number(), rank(), dense_rank(), ntile()
其它函数: lag(), lead(), first_value(), last_value()
注意:
1. 窗口函数相当于给表新增一列, 至于新增的内容是什么, 取决于窗口函数和什么函数一起使用.
2. 如果不写partition by, 表示: 统计表中所有的数据, 如果写了 表示统计组内所有的数据.
3. 如果不写order by, 表示: 统计组内所有的数据, 如果写了, 表示统计组内第一行截止到当前行的数据.
4. rows between表示统计的范围, 它可以写的关键字如下:
unbounded preceding 第一行
unbounded following 最后一行
n preceding 向上几行
n following 向下几行
current row 当前行
5. ntile(数字)表示几分之几, 里边的数字表示把数据分成几份, 如果不够分, 优先参考最小分区.
例如: 7条数据分成3份, 则最终结果为: 1, 1, 1 2, 2 3, 3
-- ------------------------------------- 以下是窗口函数案例 窗口 + 聚合 --------------------------------
-- 补充概述: PV: Page View 页面浏览量, UV: user view, 用户访问数 IP: ip(独立访客)访问数
-- 例如: 我通过我电脑的谷歌浏览器访问了京东的10个页面, 通过Edge浏览器访问了京东的7个页面, 请问: PV, UV, IP分别是多少?
-- 答案: IP: 1个, UV: 2个, PV: 17个
-- 案例2: 演示 窗口函数 + 聚合函数一起使用.
-- 需求:求出网站总的pv数 所有用户所有访问加起来
-- 如果over()里边什么都不写, 默认操作的是: 表中 该列所有的数据.
select *, sum(pv) over() as total_pv from website_pv_info; -- 不写partition by 统计表中所有的数据.
-- 需求: 求出每个用户总pv数
-- 方式1: sum() + group by 一起使用.
select cookieid, sum(pv) total_pv from website_pv_info group by cookieid;
-- 方式2: 聚合函数 + 窗口函数一起使用.
-- 细节: 如果写了partition by(表示分组): 则默认操作 组内所有的数据.
select *, sum(pv) over(partition by cookieid) as total_pv from website_pv_info; -- 写partition by 统计组内所有的数据.
-- 细节: 如果写了order by(表示排序): 则默认操作 组内第一行 至 当前行的数据.
select *, sum(pv) over(partition by cookieid order by createtime) as total_pv from website_pv_info; -- 写order by 统计组内 第一行 截止到 当前行的数据..
-- 上述的代码, 等价于如下的内容:
select *, sum(pv) over(partition by cookieid order by createtime rows between unbounded preceding and current row ) as total_pv from website_pv_info;
-- 需求: 统计每个cookieID的pv(访问量), 只统计: 当前行及 向前3行 向后1行
select * from website_pv_info;
select *, sum(pv) over(partition by cookieid order by createtime rows between 3 preceding and 1 following ) as total_pv from website_pv_info;
-- ------------------------------------- 以下是窗口函数案例 窗口 + 排序 --------------------------------
-- 需求: 根据点击量(pv)做排名, 组内排名.
-- 这里的排序函数指的是: row_number(), rank(), dense_rank(), 它们都可以做排名, 不同的是, 对相同值的处理结果.
-- 例如: 数据是100, 90, 90, 60, 则: row_number是: 1, 2, 3, 4, rank: 1, 2, 2, 4, dense_rank: 1, 2, 2, 3
select
*,
row_number() over (partition by cookieid order by pv desc) rn,
rank() over (partition by cookieid order by pv desc) rk,
dense_rank() over (partition by cookieid order by pv desc) drk
from website_pv_info;
-- 需求: 根据cookieID进行分组, 获取每组点击量最高的前4名数据, 这个就是经典的案例: 分组求TopN
-- Step1: 根据cookieID进行分组, 根据点击量进行排名.
select
*,
dense_rank() over (partition by cookieid order by pv desc) drk
from website_pv_info where drk <= 4; -- 报错.
-- 细节: where只能筛选表中已经有的列(数据)
-- Step2: 把上述的查询结果当做一张表, 然后从中获取我们要的数据即可.
with t1 as (
select
*,
dense_rank() over (partition by cookieid order by pv desc) drk
from website_pv_info
)
select * from t1 where drk <= 4;
-- ntile(数字,表示分成几份) 采用均分策略, 每份之间的差值不超过1, 优先参考最小的那个部分, 即: 7分成3份, 则是: 3, 2, 2
select
*,
ntile(3) over (partition by cookieid order by pv desc) nt
from website_pv_info;
-- 需求: 按照cookieid分组, 按照点击量降序排列, 只要每组前三分之一的数据.
with t1 as (
select
*,
ntile(3) over (partition by cookieid order by pv desc) nt
from website_pv_info
)
select * from t1 where nt = 1;
select * from website_pv_info;
-- ------------------------------------- 以下是窗口函数案例 窗口 + 其它 --------------------------------
-- 1. LAG 用于统计窗口内往上第n行值
-- 需求: 显示用户上一次的访问时间, 格式: lag(字段, n, 默认值) 向上获取字段的第n个值, 如果没有写写默认值, 找不到就是null, 如果写了默认值, 找不到就用默认值.
select
*,
-- 向上1个, 找到就显示, 找不到就显示为 null
lag(createtime) over(partition by cookieid order by createtime) `lag1`,
-- 向上2个, 找到就显示, 找不到就用默认值: '2023-05-20 10:52:05'
lag(createtime, 2, '2023-05-20 10:52:05') over(partition by cookieid order by createtime) `lag2`
from website_url_info;
-- 根据cookieID分组, createtime升序排序, 获取当前行 向上2行的createtime列的值, 找不到就用默认值(夯哥)填充.
-- 2. LEAD 用于统计窗口内往下第n行值
select
*,
lead(createtime) over(partition by cookieid order by createtime) `lead1`,
lead(createtime, 2, '夯哥') over(partition by cookieid order by createtime) `lead2`
from website_url_info;
-- 3. FIRST_VALUE 取分组内排序后,截止到当前行,第一个值
select
*,
first_value(createtime) over(partition by cookieid order by createtime) `first_value`
from website_url_info;
-- 4. LAST_VALUE 取分组内排序后,截止到当前行,最后一个值
select
*,
last_value(createtime) over(partition by cookieid order by createtime) `last_value`
from website_url_info;