目录
1.常用日期函数(22个)
1.1unix_timestamp
1.2from_unixtime
1.3from_utc_timestamp
1.4current_date
1.5current_timestamp
1.6to_date
1.7year
1.8month
1.9day
1.10hour
1.11minute
1.12second
1.13weekofyear
1.14dayofmonth
1.15dayofweek
1.16months_betwee
1.17add_months
1.18datediff
1.19date_add
1.20date_sub
1.21last_day
1.22date_format
2.常用取整函数(3个)
2.1round
2.2ceil
2.3floor
3.常用字符串操作函数(8个)
3.1upper
3.2lower
3.3length
3.4trim
3.5lpad
3.6rpad
3.7regexp_replace
3.8substring
4.集合操作
4.1size
4.2map_keys
4.3map_values
4.4array_contains
4.5sort_array
5.其他常用内置函数
5.1nvl
5.2case when then [when then] else end [别名]
5.3行转列
5.3.1concat
5.3.2concat_ws
5.3.3collect_set
5.3.4collect_list
5.4列传行
5.4.1explode
5.4.2split
5.4.3laterval view
5.5开窗函数
5.5.1定义及用法
5.5.2range和rows
5.5.3实例
5.6排名函数
5.6.1rank
5.6.2dens_rank
5.6.3row_number
5.7first_value
5.8last_value
6.自定义函数
6.1函数分类
6.2实现步骤
6.3引入自定义函数步骤
6.4自定义UDF函数
6.4.1需求
6.4.2实现
6.4.3创建临时函数
6.4.4创建永久函数
返回当前或指定时间的时间戳(秒)
select unix_timestamp(); -- 1657173785
select unix_timestamp("2020-06-14","yyyy-MM-dd"); -- 1592092800
select unix_timestamp("2020/06/14","yyyy/MM/dd"); -- 1592092800
select unix_timestamp("2020/06/14 15:23:59","yyyy/MM/dd HH:mm:ss"); -- 1592148239
将时间戳(秒)转为日期+时间格式
select from_unixtime(1592092800); -- 2020-06-14 00:00:00
select from_unixtime(1592148239); -- 2020-06-14 15:23:59
from_utc_timestamp
select from_utc_timestamp(1592148239000,'Asia/HongKong'); -- 2020-06-14 15:23:59.000000000
select from_utc_timestamp(1592148239000,'Asia/Tokyo'); -- 2020-06-15 00:23:59.000000000
返回当前日期
select `current_date`(); -- 2022-07-07
当前的日期加时间
select `current_timestamp`(); -- 2022-07-07 14:22:41.118000000
抽取日期部分
select to_date('2020-06-14 15:23:59'); -- 2020-06-14
获取年
select year('2020-06-14 15:23:59'); -- 2020
select year('2021-06-14'); -- 2021
获取月
select month('2020-06-14 15:23:59'); -- 6
select month('2021-03-14'); -- 3
获取日
select day('2020-06-19 15:23:59'); -- 19
select day('2021-03-24'); -- 24
获取时
select hour('2020-06-19 15:23:59'); -- 15
select hour('15:23:59'); -- null
获取分
select minute('2020-06-19 15:23:59'); -- 23
select minute('15:23:59'); -- null
获取秒
select second('2020-06-19 15:23:59'); -- 59
select second('15:23:59'); -- null
当前时间是一年中的第几周
select weekofyear('2020-06-19 15:23:59'); -- 25
select weekofyear('2022-09-13'); -- 37
当前时间是一个月中的第几天
select dayofmonth('2020-06-19 15:23:59'); -- 19
select dayofmonth('2022-09-13'); -- 13
当前时间是一周中的第几天
select `dayofweek`('2022-07-07 14:37:22'); -- 5
select `dayofweek`('2022-02-23'); -- 4
两个日期间的月份
select months_between('2022-10-23','2022-07-07'); -- 3.51612903
select months_between('2022-1-23','2022-07-07'); --- 5.48387097
select months_between('2022-12-23 19:37:22','2022-07-07 14:37:22'); --- 5.52284946
日期加减月
select add_months('2022-07-07',3); -- 2022-10-07
select add_months('2022-07-07',-3); -- 2022-04-07
select add_months('2022-07-07 14:37:22',-6); -- 2022-01-07
两个日期相差的天数
select datediff('2022-12-23 19:37:22','2022-07-07 14:37:22'); -- 169
select datediff('2022-12-23','2022-07-07'); -- 169
日期加天数(正数则加、负数则减)
select date_add('2022-07-07',-3); -- 2022-07-04
select date_add('2022-07-07 14:37:22',6); -- 2022-07-13
日期减天数(正数则减、负数则加)
select date_sub('2022-07-07',-3); -- 2022-07-10
select date_sub('2022-07-07 14:37:22',6); -- 2022-07-01
日期的当月的最后一天
select last_day('2022-07-07'); -- 2022-07-31
select last_day('2022-06-07 14:37:22'); -- 2022-06-30
格式化时间
select date_format('2022-06-07 14:37:22','yyyy/MM/dd HH:mm:ss'); -- 2022/06/07 14:37:22
select date_format('2022-06-07','yyyy/MM/dd'); -- 2022/06/07
select date_format('2022-06-07','yyyy*MM*dd'); -- 2022*06*07
select date_format('2022-06-07','yyyy_MM_dd'); -- 2022_06_07
四舍五入(可指定保留整数或小数点后几位)
select round(6.28); -- 6
select round(6.58); -- 7
select round(6.58410834,2); -- 6.58
select round(6.58470834,3); -- 6.585
select round(6.5,3); -- 6.500
向上取整
select ceil(6.1); -- 7
select ceil(9.001); -- 10
向下取整
select `floor`(9.999); -- 9
select `floor`(7.8); -- 7
转大写
select upper('abcd'); -- ABCD
select upper('XxxYyyZzz'); -- XXXYYYZZZ
转小写
select lower('ABCD'); -- abcd
select lower('XxxYyyZzz'); -- xxxyyyzzz
求长度
select length('dolphinscheduler'); -- 16
select length('aa bb'); -- 5
select length('aabb'); -- 4
前后去空格
desc function extended trim; -- trim(str) - Removes the leading and trailing space characters from str
select trim(' aabb '); -- aabb
select trim(' aa bb '); -- aa bb
向左补齐,到指定长度
desc function extended lpad; -- lpad(str, len, pad) - Returns str, left-padded with pad to a length of len
select lpad('abc',5,'0'); -- 00abc
向右补齐,到指定长度
desc function extended rpad; -- rpad(str, len, pad) - Returns str, right-padded with pad to a length of len
select rpad('abc',5,'0'); -- abc00
使用正则表达式匹配目标字符串,匹配成功后替换
select regexp_replace('2020-10-25','-','/'); -- 2020/10/25
select regexp_replace('aHbHcHdHe','H',' '); -- a b c d e
从指定位置开始截取字符串,角标从1开始数,正数则从左边开始,负数则从右边开始
select substring('字符串',从哪开始截[,截几个])
select substring('abcdefghijk',3); -- cdefghijk
select substring('abcdefghijk',3,2); -- cd
select substring('abcdefghijk',-3); -- ijk
select substring('abcdefghijk',-3,2); -- ij
求集合中的元素个数
select size(`array`(1,2,3,4,5,6)); -- 6
desc function extended `map`; -- map(key0, value0, key1, value1...) - Creates a map with the given key/value pairs
select size(`map`("张三",1,"李四",23,"王五",15)); -- 3
返回map中的key
select map_keys(`map`("张三",1,"李四",23,"王五",15)); -- ["张三","李四","王五"]
返回map中的value
select map_values(`map`("张三",1,"李四",23,"王五",15)); -- [1,23,15]
判断array中是否包含某个元素
select array_contains(`array`(1,2,3,4,5,6),5); -- true
select array_contains(`array`('a','b','c'),'d'); -- false
将array中的元素排序
select sort_array(`array`(22,3,19,17,35,8)); -- [3,8,17,19,22,35]
select sort_array(`array`('zhang','li','wang')); -- ["li","wang","zhang"]
测试表:test1
name | age |
张三 | 12 |
李四 | 22 |
null | 19 |
王五 | 35 |
赵六 | null |
null | 27 |
给值为null的数据复制
select nvl(name,'no name') from test1;
/**
张三
李四
no name
王五
赵六
no name
*/
根据不同的数据,返回不同的值
select
case name
when '张三' then '张小三三'
when '李四' then '李小四四'
else name
end name1
from test1
where name is not null;
/**
name1
张小三三
李小四四
王五
赵六
*/
返回输入字符串连接后的结果,支持任意个输入的字符串
select concat("123","aaa"); -- 123aaa
select concat("123","aaa",'张三'); -- 123aaa张三
指定分隔符拼接字符串或数组(只能是字符串数组)
select concat_ws('$','123','456','789'); -- 123$456$789
select concat_ws('-',split("2020/06/14",'/')); -- 2020-06-14
select concat_ws('_','123',`array`('456','789')); -- 123_456_789
将某字段去重汇总,返回array类型字段
测试表:test2
name |
tom |
marry |
peter |
tom |
tom |
marry |
select collect_set(name) from test2; -- ["tom","marry","peter"]
将某字段不去重汇总,返回array类型字段
select collect_list(name) from test2; -- ["tom","marry","peter","tom","tom","marry"]
将一列复杂的array或map拆分成多行
测试表:test3
category1 | category2 |
["悬疑","动作","科幻","剧情","心理"] | 北京,四川,广东,重庆,浙江 |
select
name
from test3
lateral view explode(category1) tmp as name;
/**
name
悬疑
动作
科幻
剧情
心理
*/
按照regex字符串分割str,返回分割后的字符串数组
select
split(category2,',') name
from test3;
/**
name
["北京","四川","广东","重庆","浙江"]
*/
用法:laterval view UDTF(expression) 临时表名 as 列名
和split、explode等UDTF一起使用,将一行数据拆成多行数据,在此基础上可以对拆分后的数据进行聚合。lateral view首先为原始表的每行调用UDTF,UDTF会把一行拆分成一行或者多行,lateral view再把结果组合,产生一个支持别名表的虚拟表。
select
category2,
name
from test3
lateral view explode(category1) tmp as name;
/**
category2 name
"北京,四川,广东,重庆,浙江" 悬疑
"北京,四川,广东,重庆,浙江" 动作
"北京,四川,广东,重庆,浙江" 科幻
"北京,四川,广东,重庆,浙江" 剧情
"北京,四川,广东,重庆,浙江" 心理
*/
灵活运用窗口函数可以解决很多复杂的问题,如去重、排名、同比及和环比、连续登录等。
用法:function(字段) over ([partition by 字段] [order by 字段] [
function | |
解释 | 支持的函数 |
聚合函数 | sum()、max()、min()、avg()等 |
排序函数 | rank()、dens_rank()、row_number()、ntile()等 |
统计比较函数 | lead()、lag()、first_value()等 |
over() | |
解释 | 指定分析函数工作的数据窗口大小,窗口会随着行的变化而变化 |
partition by | 表示将数据先按字段分区 |
order by | 表示将各个分区内的数据按字段进行排序 |
1)如果不指定partition by,则不对数据进行分区,即所有数据看做一个分区
2)如果不指定order by,则不对各分区进行排序,通常用于那些顺序无关的窗口函数,如sum()
window_expression | |
解释 | 窗口边界的设置 |
n preceding | 往前n行 |
n following | 往后n行 |
current row | 当前行 |
unbounded preceding | 从前面的起点开始 |
unbounded following | 到后面的终点结束 |
1)如果不指定开窗子句:
①前面没有指定order by,则默认使用分区内所有行,相当于
Function() over(rows between unbounded precedeing and unbounded following)
②前面指定了order by,则默认使用分区内的起点到当前行,相当于
Function() over(range between unbounded preceding and current row)
range是逻辑窗口;rows是物理窗口
测试表:id_test
id |
1 |
1 |
3 |
6 |
6 |
6 |
7 |
8 |
9 |
select
id,
sum(id) over () no_order_sum,
sum(id) over (order by id) default_sum,
sum(id) over (order by id range between unbounded preceding and current row ) range_unbounded_sum,
sum(id) over (order by id rows between unbounded preceding and current row ) rows_unbounded_sum,
sum(id) over (order by id range between 1 preceding and 2 following) range_sum_1_2,
sum(id) over (order by id range between 2 preceding and 1 following) range_sum_2_1,
sum(id) over (order by id rows between 1 preceding and 2 following) rows_sum_1_2,
sum(id) over (order by id rows between 2 preceding and 1 following) rows_sum_2_1
from test5;
/**
id no_order_sum default_sum range_unbounded_sum rows_unbounded_sum range_sum_1_2 range_sum_2_1 rows_sum_1_2 rows_sum_2_1
1 47 2 2 1 5 2 5 2
1 47 2 2 2 5 2 11 5
3 47 5 5 5 3 5 16 11
6 47 23 23 11 33 25 21 16
6 47 23 23 17 33 25 25 21
6 47 23 23 23 33 25 27 25
7 47 30 30 30 42 33 30 27
8 47 38 38 38 24 44 24 30
9 47 47 47 47 17 24 17 24
*/
/**
1.理解 no_order_sum :无partition by、order by,即无分区,无窗口大小限定:
id no_order_sum
1 47 =1+1+3+6+6+6+7+8+9
1 47 =1+1+3+6+6+6+7+8+9
3 47 =1+1+3+6+6+6+7+8+9
6 47 ...
6 47 ...
6 47 ...
7 47 ...
8 47 ...
9 47 ...
2.理解 default_sum :无partition by,指定order by,未指定开窗子句,则默认range between unbounded preceding and current row,即第一行id值到当前行id值的范围内的值都要sum(注意这是逻辑区间,当前行不一定真的就只到当前行):
id default_sum
1 2 =1+1 解释:第一行的id值为1,当前行的id值为1,即id在 1<=id<=1 这个区间内都要被sum,第二行的1也在这个范围,这叫逻辑区间
1 2 =1+1 解释:第一行的id值为1,当前行的id值为1,即id在 1<=id<=1 这个区间内都要被sum
3 5 =1+1+3 解释:第一行的id值为1,当前行的id值为3,即id在 1<=id<=3 这个区间内都要被sum
6 23 =1+1+3+6+6+6 解释:第一行的id值为1,当前行的id值为6,即id在 1<=id<=6 这个区间内都要被sum
6 23 =1+1+3+6+6+6 解释:第一行的id值为1,当前行的id值为6,即id在 1<=id<=6 这个区间内都要被sum
6 23 =1+1+3+6+6+6 解释:第一行的id值为1,当前行的id值为6,即id在 1<=id<=6 这个区间内都要被sum
7 30 =1+1+3+6+6+6+7 解释:第一行的id值为1,当前行的id值为7,即id在 1<=id<=7 这个区间内都要被sum
8 38 =1+1+3+6+6+6+7+8 解释:第一行的id值为1,当前行的id值为8,即id在 1<=id<=8 这个区间内都要被sum
9 47 =1+1+3+6+6+6+7+8+9 解释:第一行的id值为1,当前行的id值为9,即id在 1<=id<=9 这个区间内都要被sum
3.理解 range_unbounded_sum :属于第二种的默认情况,略
4.理解 rows_unbounded_sum :无partition by,指定order by、开窗子句rows between unbounded preceding and current row,即第一行到当前行(注意这是物理区间,当前行一定是当前行):
id rows_unbounded_sum
1 1 =1 解释:从第一行加到当前行,当前是哪一行就加到这一行为止,这叫物理区间,下同
1 2 =1+1 解释:从第一行加到当前行
3 5 =1+1+3 解释:从第一行加到当前行
6 11 =1+1+3+6 解释:从第一行加到当前行
6 17 =1+1+3+6+6 解释:从第一行加到当前行
6 23 =1+1+3+6+6+6 解释:从第一行加到当前行
7 30 =1+1+3+6+6+6+7 解释:从第一行加到当前行
8 38 =1+1+3+6+6+6+7+8 解释:从第一行加到当前行
9 47 =1+1+3+6+6+6+7+8+9 解释:从第一行加到当前行
5.理解 range_sum_1_2 :无partition by,指定order by、开窗子句range between 1 preceding and 2 following,即id值的前一个值到id值的后两个值之间的值都要sum
id range_sum_1_2
1 5 =1+1+3 解释:当前行的id值为1,根据开窗子句,id的范围在 1-1<=id<=1+3,即id为0、1、2、3的都要sum,显然当前行后有1、3要sum
1 5 =1+1+3 解释:当前行的id值为1,根据开窗子句,id的范围在 1-1<=id<=1+3,即id为0、1、2、3的都要sum,显然当前行前后有1、3要sum
3 3 =3 解释:当前行的id值为3,根据开窗子句,id的范围在 3-1<=id<=3+2,即id为2、3、4、5的都要sum,显然只有3,当前行前后没有2、4、5
6 33 =6+6+6+7+8 解释:当前行的id值为6,根据开窗子句,id的范围在 6-1<=id<=6+2,即id为5、6、7、8的都要sum,显然当前行后6、7、8要sum
6 33 =6+6+6+7+8 解释:当前行的id值为6,根据开窗子句,id的范围在 6-1<=id<=6+2,即id为5、6、7、8的都要sum,显然当前行前后6、7、8要sum
6 33 =6+6+6+7+8 解释:当前行的id值为6,根据开窗子句,id的范围在 6-1<=id<=6+2,即id为5、6、7、8的都要sum,显然当前行前后6、7、8要sum
7 42 =6+6+6+7+8+9 解释:当前行的id为7,根据开窗子句,id的范围在 7-1<=id<=7+2,即id为6、7、8、9的都要sum,显然当前行前后6、7、8、9要sum
8 24 =7+8+9 解释:当前行的id为8,根据开窗子句,id的范围在 8-1<=id<=8+2,即id为7、8、9、10的都要sum,显然当前行前后没有10
9 17 =8+9 解释:当前行的id为9,根据开窗子句,id的范围在 9-1<=id<=9+2,即id为8、9、10、11的都要sum,显然没有10、11
6.理解 range_sum_2_1 :无partition by,指定order by、开窗子句range between 2 preceding and 1 following,即id值的前两个值到id值的后一个值之间的值都要sum
id range_sum_2_1
1 2 =1+1 解释:当前行的id值为1,根据开窗子句,id的范围在 1-2<=id<=1+1,即id为-1、0、1、2的都要sum,显然当前行前后没有-1、0、2
1 2 =1+1 解释:当前行的id值为1,根据开窗子句,id的范围在 1-2<=id<=1+1,即id为-1、0、1、2的都要sum,显然当前行前后没有-1、0、2
3 5 =1+1+3 解释:当前行的id值为3,根据开窗子句,id的范围在 3-2<=id<=3+1,即id为1、2、3、4的都要sum,显然当前行前后没有2、4
6 25 =6+6+6+7 解释:当前行的id值为6,根据开窗子句,id的范围在 6-2<=id<=6+1,即id为4、5、6、7的都要sum,显然当前行前后没有4、5
6 25 =6+6+6+7 解释:当前行的id值为6,根据开窗子句,id的范围在 6-2<=id<=6+1,即id为4、5、6、7的都要sum,显然当前行前后没有4、5
6 25 =6+6+6+7 解释:当前行的id值为6,根据开窗子句,id的范围在 6-2<=id<=6+1,即id为4、5、6、7的都要sum,显然当前行前后没有4、5
7 33 =6+6+6+7+8 解释:当前行的id值为7,根据开窗子句,id的范围在 7-2<=id<=7+1,即id为5、6、7、8的都要sum,显然当前行前后没有5
8 44 =6+6+6+7+8+9 解释:当前行的id值为8,根据开窗子句,id的范围在 8-2<=id<=8+1,即id为6、7、8、9的都要sum,显然当前行前后都有
9 24 =7+8+9 解释:当前行的id值为9,根据开窗子句,id的范围在 9-2<=id<=9+1,即id为7、8、9、10的都要sum,显然当前行前后没有10
7.理解 rows_sum_1_2 :无partition by,指定order by、开窗子句rows between 1 preceding and 2 following,即当前行的前一行到后两行都要sum
id rows_sum_1_2
1 5 =1+1+3 解释:当前行前一行加到后两行
1 11 =1+1+3+6 解释:当前行前一行加到后两行
3 16 =1+3+6+6 解释:当前行前一行加到后两行
6 21 =3+6+6+6 ...
6 25 =6+6+6+7 ...
6 27 =6+6+7+8 ...
7 30 =6+7+8+9 ...
8 24 =7+8+9 ...
9 17 =8+9 ...
8.理解 rows_sum_2_1 :无partition by,指定order by、开窗子句rows between 2 preceding and 1 following,即当前行的前两行到后一行都要sum
di rows_sum_2_1
1 2 =1+1 解释:当前行前两行加到后一行
1 5 =1+1+3 解释:当前行前两行加到后一行
3 11 =1+1+3+6 解释:当前行前两行加到后一行
6 16 =1+3+6+6 解释:当前行前两行加到后一行
6 21 =3+6+6+6 ...
6 25 =6+6+6+7 ...
7 27 =6+6+7+8 ...
8 30 =6+7+8+9 ...
9 24 =7+8+9 ...
*/
总结:
range是逻辑分区,窗口大小与被统计的字段本身的值有关
rows是物理分区,窗口大小与被统计的字段本身的值无关,仅仅是当前行在物理位置上上下移动多少
测试表:test4
name | orderdate | cost |
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)需求一:查询在2017年4月份购买过的顾客及总人数
-- 找出2017-04购买过的所有顾客
select
name
from test4
where substring(orderdate,1,7) = '2017-04';
/**
name
jack
mart
mart
mart
mart
*/
-- 正确sql:
select
name,
count(name) over () count_name
from test4
where substring(orderdate,1,7) = '2017-04'
group by name;
-- 理解:按name分组后对count函数开窗,未指定partition by,则将分组后的整个数据看做一个分区
/**
name count_name
mart 2
jack 2
*/
-- =====================================================================================================================
-- 观察下列写法:
select
name,
count(name) count_name
from test4
where substring(orderdate,1,7) = '2017-04'
group by name;
-- 理解:按name分组后不开窗,则组内统计
/**
name count_name
mart 4
jack 1
*/
-- =========================================================================================
-- 观察下列写法:
select
name,
count(name) over (partition by name) count_name
from test4
where substring(orderdate,1,7) = '2017-04'
group by name;
-- 理解:按name分组后对count函数开窗,指定partition by,则在每个分区内统计
/**
name count_name
mart 1
jack 1
*/
-- =========================================================================================
-- 观察下列写法:
select
name,
count(name) over (partition by name) count_name
from test4
where substring(orderdate,1,7) = '2017-04';
-- 理解:未分组对count函数开窗,指定partition by,则在每个分区内统计
/**
name count_name
mart 4
mart 4
mart 4
mart 4
jack 1
*/
2)需求二:查询顾客的购买明细及月购买金额
select
name,
orderdate,
cost,
sum(cost) over (partition by name,date_format(orderdate,'yyyy-MM')) sum_cost
from test4;
-- 理解:未分组(即使分组也不改变结果,因为每一行都不一样,分组不会改变原数据)对sum函数开窗,并指定partition by,按照name,年-月一分区求sum
/**
name orderdate cost sum_cost
jack 2017-01-01 10 111
jack 2017-01-05 46 111
jack 2017-01-08 55 111
tony 2017-01-02 15 94
tony 2017-01-04 29 94
tony 2017-01-07 50 94
jack 2017-02-03 23 23
jack 2017-04-06 42 42
mart 2017-04-08 62 299
mart 2017-04-09 68 299
mart 2017-04-11 75 299
mart 2017-04-13 94 299
neil 2017-05-10 12 12
neil 2017-06-12 80 80
*/
3)需求三:将每个顾客的cost按照日期进行累加
-- 写法一:
select
name,
orderdate,
cost,
sum(cost) over(partition by name order by orderdate) sum_cost
from test4;
-- 理解:指定order by,未指定窗口边界,则默认从分区内的起点的值到当前行的值的范围内
/**
name orderdate cost sum_cost
jack 2017-01-01 10 10 =10
jack 2017-01-05 46 56 =10+46
jack 2017-01-08 55 111 =10+46+55
jack 2017-02-03 23 134 =10+46+55+23
jack 2017-04-06 42 176 =10+46+55+23+42
neil 2017-05-10 12 12 =12
neil 2017-06-12 80 92 =12+80
tony 2017-01-02 15 15 =15
tony 2017-01-04 29 44 =15+29
tony 2017-01-07 50 94 =15+29+50
mart 2017-04-08 62 62 =62
mart 2017-04-09 68 130 =62+68
mart 2017-04-11 75 205 =62+68+75
mart 2017-04-13 94 299 =62+68+75+94
观察窗口函数是如何运行的
先划定分区(partition by),再排序(order by),随着排好的顺序一次往下sum
*/
-- 写法二
select
name,
orderdate,
cost,
sum(cost) over(partition by name order by orderdate rows between unbounded preceding and current row) sum_cost
from test4;
-- 理解:指定order by,且指定窗口边界:从前面的起点到当前行,则按照指定的边界进行sum
/**
name orderdate cost sum_cost
jack 2017-01-01 10 10
jack 2017-01-05 46 56
jack 2017-01-08 55 111
jack 2017-02-03 23 134
jack 2017-04-06 42 176
neil 2017-05-10 12 12
neil 2017-06-12 80 92
tony 2017-01-02 15 15
tony 2017-01-04 29 44
tony 2017-01-07 50 94
mart 2017-04-08 62 62
mart 2017-04-09 68 130
mart 2017-04-11 75 205
mart 2017-04-13 94 299
*/
-- =========================================================================================
-- 观察下列写法:
select
name,
orderdate,
cost,
sum(cost) over(partition by name order by orderdate rows between 1 preceding and current row) sum_cost
from test4;
/**
name orderdate cost sum_cost
jack 2017-01-01 10 10 =10
jack 2017-01-05 46 56 =10+46
jack 2017-01-08 55 101 =46+55
jack 2017-02-03 23 78 =55+23
jack 2017-04-06 42 65 =23+42
neil 2017-05-10 12 12 =12
neil 2017-06-12 80 92 =12+80
tony 2017-01-02 15 15 =15
tony 2017-01-04 29 44 =15+29
tony 2017-01-07 50 79 =29+50
mart 2017-04-08 62 62 =62
mart 2017-04-09 68 130 =62+68
mart 2017-04-11 75 143 =68+75
mart 2017-04-13 94 169 =75+94
*/
-- 理解:指定order by,且指定窗口边界:从前一行到当前行,则按照指定的边界进行sum
-- =========================================================================================
-- 观察下列写法:
select
name,
orderdate,
cost,
sum(cost) over(partition by name order by orderdate rows between 1 preceding and 1 following) sum_cost
from test4;
/**
name orderdate cost sum_cost
jack 2017-01-01 10 56 =10+46
jack 2017-01-05 46 111 =10+46+55
jack 2017-01-08 55 124 =46+55+23
jack 2017-02-03 23 120 =55+23+42
jack 2017-04-06 42 65 =23+42
neil 2017-05-10 12 92 =12+80
neil 2017-06-12 80 92 =12+80
tony 2017-01-02 15 44 =15+29
tony 2017-01-04 29 94 =15+29+50
tony 2017-01-07 50 79 =29+50
mart 2017-04-08 62 130 =62+68
mart 2017-04-09 68 205 =62+68+75
mart 2017-04-11 75 237 =68+75+94
mart 2017-04-13 94 169 =75+94
*/
-- 理解:指定order by,且指定窗口边界:从前一行到下一行,则按照指定的边界进行sum
-- 体会窗口边界的用法
4)需求:四:查看顾客上次的购买时间(lag)
lag()函数:lag(列名[,往上几行] [,默认值])
用于统计窗口内往上第几行的值,取不到时(如第一行,往上没有)给默认值
select
name,
orderdate,
lag(orderdate,1,'1970-01-01') over (partition by name order by orderdate) last_time
from test4;
/**
name orderdate last_time
jack 2017-01-01 1970-01-01 =默认值
jack 2017-01-05 2017-01-01 =上一行数据
jack 2017-01-08 2017-01-05 =上一行数据
jack 2017-02-03 2017-01-08 =上一行数据
jack 2017-04-06 2017-02-03 =上一行数据
neil 2017-05-10 1970-01-01 =默认值
neil 2017-06-12 2017-05-10 =上一行数据
tony 2017-01-02 1970-01-01 =默认值
tony 2017-01-04 2017-01-02 =上一行数据
tony 2017-01-07 2017-01-04 =上一行数据
mart 2017-04-08 1970-01-01 =默认值
mart 2017-04-09 2017-04-08 =上一行数据
mart 2017-04-11 2017-04-09 =上一行数据
mart 2017-04-13 2017-04-11 =上一行数据
*/
-- 理解:对orderdate字段:统计窗口内往上1行的数据,取不到时给定默认值1970-01-01
-- =========================================================================================
-- 观察下列写法:
select
name,
orderdate,
lag(orderdate,2,'1970-01-01') over (partition by name order by orderdate) last_time
from test4;
/**
name orderdate last_time
jack 2017-01-01 1970-01-01 =默认值
jack 2017-01-05 1970-01-01 =默认值
jack 2017-01-08 2017-01-01 =往上二行的数据
jack 2017-02-03 2017-01-05 =往上二行的数据
jack 2017-04-06 2017-01-08 =往上二行的数据
neil 2017-05-10 1970-01-01 =默认值
neil 2017-06-12 1970-01-01 =默认值
tony 2017-01-02 1970-01-01 =默认值
tony 2017-01-04 1970-01-01 =默认值
tony 2017-01-07 2017-01-02 =往上二行的数据
mart 2017-04-08 1970-01-01 =默认值
mart 2017-04-09 1970-01-01 =默认值
mart 2017-04-11 2017-04-08 =往上二行的数据
mart 2017-04-13 2017-04-09 =往上二行的数据
*/
-- 理解:对orderdate字段:统计窗口内往上2行的数据,取不到时给定默认值1970-01-01
5)需求五:查看顾客下次的购买时间(lead)
lead()函数:lead(列名[,往上几行] [,默认值])
用于统计窗口内往下第几行的值,取不到时(如最后一行行,往下没有)给默认值
与lag()函数正好相反
select
name,
orderdate,
lead(orderdate,1,'9999-12-31') over (partition by name order by orderdate) next_time
from test4;
/**
name orderdate next_time
jack 2017-01-01 2017-01-05 =下一行数据
jack 2017-01-05 2017-01-08 =下一行数据
jack 2017-01-08 2017-02-03 =下一行数据
jack 2017-02-03 2017-04-06 =下一行数据
jack 2017-04-06 9999-12-31 =默认值
neil 2017-05-10 2017-06-12 =下一行数据
neil 2017-06-12 9999-12-31 =默认值
tony 2017-01-02 2017-01-04 =下一行数据
tony 2017-01-04 2017-01-07 =下一行数据
tony 2017-01-07 9999-12-31 =默认值
mart 2017-04-08 2017-04-09 =下一行数据
mart 2017-04-09 2017-04-11 =下一行数据
mart 2017-04-11 2017-04-13 =下一行数据
mart 2017-04-13 9999-12-31 =默认值
*/
-- 理解:对orderdate字段:统计窗口内往下1行的数据,取不到时给定默认值9999-12-31
-- =========================================================================================
-- 观察下列写法:
select
name,
orderdate,
lead(orderdate,2,'9999-12-31') over (partition by name order by orderdate) next_time
from test4;
/**
name orderdate next_time
jack 2017-01-01 2017-01-08 =往下第2行的数据
jack 2017-01-05 2017-02-03 =往下第2行的数据
jack 2017-01-08 2017-04-06 =往下第2行的数据
jack 2017-02-03 9999-12-31 =默认值
jack 2017-04-06 9999-12-31 =默认值
neil 2017-05-10 9999-12-31 =默认值
neil 2017-06-12 9999-12-31 =默认值
tony 2017-01-02 2017-01-07 =往下第2行的数据
tony 2017-01-04 9999-12-31 =默认值
tony 2017-01-07 9999-12-31 =默认值
mart 2017-04-08 2017-04-11 =往下第2行的数据
mart 2017-04-09 2017-04-13 =往下第2行的数据
mart 2017-04-11 9999-12-31 =默认值
mart 2017-04-13 9999-12-31 =默认值
*/
-- 理解:对orderdate字段:统计窗口内往下第2行的数据,取不到时给定默认值9999-12-31
6)需求六:查询前20%时间的订单信息(ntile)
ntile()函数:ntile(数值)
为已排序的行,均分为指定数量的组,组号按顺序排列,返回组号,不支持rows between
/**
分析:表中一共14行数据,前20%大约是14*0.2≈3行数据,但是数据每天都在变化,某一天变成29行数据,前20%大约是29*0.2≈6行,如果仅仅是简单的排序然后limit 3就写死了,数据变化后统计出来的数据就不再是前20%了,此时就要用到ntile函数,20%即1/5,name将排好序的数据均分成5份,再作为子查询,取序号为1的。数据变化变多时,每一份数据也跟着变多
*/
select
name,
orderdate,
cost,
ntile(5) over (order by orderdate) sort_
from test4;
/**
name orderdate cost sort_
jack 2017-01-01 10 1
tony 2017-01-02 15 1
tony 2017-01-04 29 1
jack 2017-01-05 46 2
tony 2017-01-07 50 2
jack 2017-01-08 55 2
jack 2017-02-03 23 3
jack 2017-04-06 42 3
mart 2017-04-08 62 3
mart 2017-04-09 68 4
mart 2017-04-11 75 4
mart 2017-04-13 94 4
neil 2017-05-10 12 5
neil 2017-06-12 80 5
*/
-- 返回前20%即sort_=1
select
name,
orderdate,
cost
from (select name,
orderdate,
cost,
ntile(5) over (order by orderdate) sort_
from test4) t1
where sort_=1;
/**
name orderdate cost
jack 2017-01-01 10
tony 2017-01-02 15
tony 2017-01-04 29
*/
排名相同时会重复,总数不变
select
orderdate,
rank() over (order by orderdate) rk
from test4
/**
orderdate rk
2017-01-01 1
2017-01-02 2
2017-01-02 2
2017-01-05 4
2017-01-07 5
*/
-- 理解:rank函数需要窗口内数据有序,所以指定order by
排名相同时会重复,总数减少
select
orderdate,
dense_rank() over (order by orderdate) dr
from test4;
/**
orderdate dr
2017-01-01 1
2017-01-02 2
2017-01-02 2
2017-01-05 3
2017-01-07 4
*/
-- 理解:dense_rank函数需要窗口内数据有序,所以指定order by
排名相同时忽略重复,按顺序排
select
orderdate,
row_number() over (order by orderdate) rn
from test4;
/**
orderdate rn
2017-01-01 1
2017-01-02 2
2017-01-02 3
2017-01-05 4
2017-01-07 5
*/
-- 理解:row_number函数需要窗口内数据有序,所以指定order by
first_value(字段,true/false),对某字段取窗口内第一个值,ture表示忽略null值,false表示不忽略null值
测试表:test5
id | dt | point |
1001 | 2022-05-01 | null |
1001 | 2022-05-02 | null |
1001 | 2022-05-01 | null |
1001 | 2022-05-01 | 张三_01 |
1001 | 2022-05-03 | 李四_02 |
1002 | 2022-05-04 | null |
1002 | 2022-05-01 | null |
1002 | 2022-05-05 | 王五_03 |
1003 | 2022-05-01 | null |
1003 | 2022-05-06 | null |
1003 | 2022-05-06 | null |
1003 | 2022-05-07 | 赵六_04 |
select
id,
dt,
point,
first_value(point,true) over (partition by id order by dt rows between unbounded preceding and unbounded following)
from test5;
/**
id dt point first_point
1001 2022-05-01 null 张三_01 解释:1001分区内所有数据第一个值:“张三_01”
1001 2022-05-01 null 张三_01 解释:1001分区内所有数据第一个值:“张三_01”
1001 2022-05-01 张三_01 张三_01 解释:1001分区内所有数据第一个值:“张三_01”
1001 2022-05-02 null 张三_01 解释:1001分区内所有数据第一个值:“张三_01”
1001 2022-05-03 李四_02 张三_01 解释:1001分区内所有数据第一个值:“张三_01”
1002 2022-05-01 null 王五_03 解释:1002分区内所有数据第一个值:“王五_03”
1002 2022-05-04 null 王五_03 解释:1002分区内所有数据第一个值:“王五_03”
1002 2022-05-05 王五_03 王五_03 解释:1002分区内所有数据第一个值:“王五_03”
1003 2022-05-01 null 赵六_04 解释:1003分区内所有数据第一个值:“赵六_04”
1003 2022-05-06 null 赵六_04 解释:1003分区内所有数据第一个值:“赵六_04”
1003 2022-05-06 null 赵六_04 解释:1003分区内所有数据第一个值:“赵六_04”
1003 2022-05-07 赵六_04 赵六_04 解释:1003分区内所有数据第一个值:“赵六_04”
*/
-- -- 理解:对point取first_value,开窗指定partition by、order by,窗口子句rows between unbounded preceding and unbounded following,即窗口大小为分区内、排序后的整个数据
last_value(字段,true/false),对某字段取窗口内最后一个值,ture表示忽略null值,false表示不忽略null值
测试表:test6
id | dt | point |
1001 | 2022-05-01 | 张三_01 |
1001 | 2022-05-02 | null |
1001 | 2022-05-01 | null |
1001 | 2022-05-01 | null |
1001 | 2022-05-03 | 李四_02 |
1002 | 2022-05-04 | null |
1002 | 2022-05-01 | 王五_03 |
1002 | 2022-05-05 | null |
1003 | 2022-05-01 | 赵六_04 |
1003 | 2022-05-06 | null |
1003 | 2022-05-06 | null |
1003 | 2022-05-07 | null |
select
id,
dt,
point,
last_value(point,true) over (partition by id order by dt) last_point
from test6;
/**
id dt point last_point
1001 2022-05-01 张三_01 张三_01 解释:由于是range,窗口为第一行到第三行,忽略null值,则该窗口内最后一个值:“张三_01”
1001 2022-05-01 null 张三_01 解释:...,窗口为分区内第一行到第三行,忽略null值,则该窗口内最后一个值:“张三_01”
1001 2022-05-01 null 张三_01 解释:...,窗口为分区内第一行到第三行,忽略null值,则该窗口内最后一个值:“张三_01”
1001 2022-05-02 null 张三_01 解释:...,窗口为分区内第一行到第四行,忽略null值,则该窗口内最后一个值:“张三_01”
1001 2022-05-03 李四_02 李四_02 解释:...,窗口为分区内第一行到第五行,忽略null值,则该窗口内第一个值:“张三_01”,最后一个值:“李四_02”
1002 2022-05-01 王五_03 王五_03 解释:...,窗口为分区内第一行到第一行,忽略null值,则该窗口内最后一个值:“王五_03”
1002 2022-05-04 null 王五_03 解释:...,窗口为分区内第一行到第二行,忽略null值,则该窗口内最后一个值:“王五_03”
1002 2022-05-05 null 王五_03 解释:...,窗口为分区内第一行到第三行,忽略null值,则该窗口内最后一个值:“王五_03”
1003 2022-05-01 赵六_04 赵六_04 解释:...,窗口为分区内第一行到第一行,忽略null值,则该窗口内最后一个值:“赵六_04”
1003 2022-05-06 null 赵六_04 解释:...,窗口为分区内第一行到第二行,忽略null值,则该窗口内最后一个值:“赵六_04”
1003 2022-05-06 null 赵六_04 解释:...,窗口为分区内第一行到第三行,忽略null值,则该窗口内最后一个值:“赵六_04”
1003 2022-05-07 null 赵六_04 解释:...,窗口为分区内第一行到第四行,忽略null值,则该窗口内最后一个值:“赵六_04”
*/
-- 理解:对point取last_value,开窗指定partition by、order by,窗口子句默认range between unbounded preceding and current row
实例表:game_user
id | dt |
1001 | 2022-05-01 23:21:33 |
1003 | 2022-05-02 23:21:33 |
1002 | 2022-05-01 23:21:33 |
1003 | 2022-05-01 23:21:33 |
1001 | 2022-05-03 23:21:33 |
1003 | 2022-05-04 23:21:33 |
1002 | 2022-05-01 23:21:33 |
1001 | 2022-05-05 23:21:33 |
1001 | 2022-05-01 23:21:33 |
1002 | 2022-05-06 23:21:33 |
1001 | 2022-05-06 23:21:33 |
1001 | 2022-05-07 23:21:33 |
需求:求每个用户的最大连续登录次数,断一天也算连续,如1、3、5则视为连续5天登录
-- 思路:
/**
1001 2022-05-01 null 1001 2022-05-01 1001_2022-05-01 1001 2022-05-01 1001_2022-05-01
1001 2022-05-03 2022-05-01 1001 2022-05-03 null 1001 2022-05-03 1001_2022-05-01
1001 2022-05-05 2022-05-03 1001 2022-05-05 null 1001 2022-05-05 1001_2022-05-01
lag 1001 2022-05-06 2022-05-05 if 1001 2022-05-06 null last_value 1001 2022-05-06 1001_2022-05-01
原数据===> 1001 2022-05-07 2022-05-06 ====> 1001 2022-05-07 null =====> 1001 2022-05-07 1001_2022-05-01
1002 2022-05-01 null 1002 2022-05-01 1002_2022-05-01 1002 2022-05-01 1002_2022-05-01
1002 2022-05-06 2022-05-01 1002 2022-05-06 1002_2022-05-06 1002 2022-05-06 1002_2022-05-06
1003 2022-05-01 null 1003 2022-05-01 1003_2022-05-01 1003 2022-05-01 1003_2022-05-01
1003 2022-05-02 2022-05-01 1003 2022-05-01 null 1003 2022-05-01 1003_2022-05-01
1003 2022-05-04 2022-05-02 1003 2022-05-04 null 1003 2022-05-04 1003_2022-05-01
在分组找出最大连续登录,取最大值
*/
select
id,
max(lianxu) max_login
from (select id,
datediff(max(dt), min(dt)) + 1 lianxu
from (select id,
dt,
last_value(point, true) over (partition by id order by dt) last_point
from (select id,
dt,
`if`(datediff(dt, before_day) > 2 or before_day is null, concat(id, '_', dt), null) point
from (select id,
date_format(dt, 'yyyy-MM-dd') dt,
lag(date_format(dt, 'yyyy-MM-dd'), 1) over (partition by id order by dt) before_day
from game_user
group by id, dt) t1) t2) t3
group by id, last_point) t4
group by id;
/**
id max_login
1001 7
1002 1
1003 4
*/
1)UDF:一进一出
2)UDAF:聚合函数:多进一出,如:count、sum...
3)炸裂函数:一进多出,如:explode...
1)继承hive提供的类
org.apache.hadoop.hive.ql.udf.generic.GenericUDF
org.apache.hadoop.hive.ql.udf.generic.GenericUDTF
2)实现类中的抽象方法
3)在hive命令行窗口创建函数
1)添加jar包
hive(default)> add jar linux_jar_path;
2)创建function
hive(default)> create [temporary] function [dbname.]function_name AS class_name;
3)删除自定义函数
hive(default)> drop [temporary] function [if exists] [dbname.]function_name;
自定义一个UDF函数实现计算给定字符串的长度:
hive(default)> select my_len("abcd");
ok
4
1)创建一个Maven工程Hive
2)在工程项目的pom.xml文件中导入依赖
org.apache.hive
hive-exec
3.1.2
3)创建一个类
package com.atguigu.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函数,需要继承GenericUDF类
* 需求: 计算指定字符串的长度
*/
public class MyStringLength extends GenericUDF {
/**
*
* @param arguments 输入参数类型的鉴别器对象
* @return 返回值类型的鉴别器对象
* @throws UDFArgumentException
*/
@Override
public ObjectInspector initialize(ObjectInspector[] arguments) throws UDFArgumentException {
// 判断输入参数的个数
if(arguments.length !=1){
throw new UDFArgumentLengthException("Input Args Length Error!!!");
}
// 判断输入参数的类型
if(!arguments[0].getCategory().equals(ObjectInspector.Category.PRIMITIVE)){
throw new UDFArgumentTypeException(0,"Input Args Type Error!!!");
}
//函数本身返回值为int,需要返回int类型的鉴别器对象
return PrimitiveObjectInspectorFactory.javaIntObjectInspector;
}
/**
* 函数的逻辑处理
* @param arguments 输入的参数
* @return 返回值
* @throws HiveException
*/
@Override
public Object evaluate(DeferredObject[] arguments) throws HiveException {
if(arguments[0].get() == null){
return 0 ;
}
return arguments[0].get().toString().length();
}
@Override
public String getDisplayString(String[] children) {
return "";
}
}
4)打成jar包上传到服务器/opt/module/hive/datas/myudf.jar
5)将jar包添加到hive的classpath
hive (default)> add jar /opt/module/hive/datas/myudf.jar;
1)创建临时函数与开发好的java class关联
hive (default)> create temporary function my_len as "com.atguigu.hive. MyStringLength";
2)在hql中使用自定义的函数
hive (default)> select ename,my_len(ename) len_word ename_len from emp;
OK
ename len_word
fanfan 6
SMITH 5
ALLEN 5
WARD 4
JONES 5
MARTIN 6
BLAKE 5
CLARK 5
SCOTT 5
KING 4
TURNER 6
ADAMS 5
JAMES 5
FORD 4
MILLER 6
注意:临时函数只跟会话有关系,跟库没有关系,只要创建临时函数的会话不断,再当前会话下,任意一个库都可以使用,其他会话全部不能使用
1)创建
hive (default)>
create function my_len2
as "com.atguigu.hive.udf.MyUDF"
using jar "hdfs://hadoop102:8020/udf/myudf.jar";
2)使用
hive (default)>
select
ename,
my_len2(ename) ename_len
from emp;
3)删除
hive (default)> drop function my_len2;
注意:永久函数跟会话没有关系,创建函数的会话断了以后,其他会话也可以使用。
永久函数创建的时候,在函数名之前需要自己加上库名,如果不指定库名的话,会默认把当前库的库名给加上。
永久函数使用的时候,需要在指定的库里面操作,或者在其他库里面使用的话加上 库名.函数名。