TopN问题
需要确定使用什么排名函数,包含三种函数:row_number()、rank()、dense_rank()
每个班级的分数为前3名的学生
--建表语句
create table score(sid string, class string, score int)
row format delimited fields terminated by ' ';
load data local inpath '/temp/sql.txt' into table score;
--数据:学生id、班级、分数
s1 A 89
s2 C 88
s3 A 92
s4 A 89
s5 B 90
s6 B 86
s7 C 92
s8 C 90
s9 A 85
s10 C 86
s11 B 86
s12 B 86
--sql语句
select * from (
select class, sid, score, dense_rank() over(partition by class order by score desc) as rank
from score
) t1
where t1.rank < 4;
--结果
t1.class t1.sid t1.score t1.rank
A s3 92 1
A s4 89 2
A s1 89 2
A s9 85 3
B s5 90 1
B s11 86 2
B s12 86 2
B s6 86 2
C s7 92 1
C s8 90 2
C s2 88 3
行列转换问题
(1)一行转换为多行
将每个电影的分类列表拆分出单个分类
--建表语句
create table movie(movie_name string, category string)
row format delimited fields terminated by ' ';
load data local inpath '/temp/sql.txt' into table movie;
--数据:电影名、分类
让子弹飞 动作、年代
长江七号 科幻
大进军 战争
--sql语句
select movie_name, category_name
from movie
lateral view explode(split(category, '、')) t1 as category_name;
--结果
movie_name category_name
让子弹飞 动作
让子弹飞 年代
长江七号 科幻
大进军 战争
(2)多行转换为一行
将年龄相同的姓名合并在一起
--建表语句
create table person(name string, age int)
row format delimited fields terminated by ' ';
load data local inpath '/temp/sql.txt' into table person;
--数据:姓名、年龄
A 20
B 18
C 20
D 24
--sql语句
select concat_ws('|', collect_set(name)) as name_list, age
from person
group by age;
--结果
name_list age
B 18
A|C 20
D 24
将可枚举的值作为新的字段
--建表语句
create table game(year int, class string, score int)
row format delimited fields terminated by ' ';
load data local inpath '/temp/sql.txt' into table game;
--数据:年份、班级、得分
2020 ClassA 10
2020 ClassB 12
2020 ClassC 9
2021 ClassA 12
2021 ClassB 8
--sql语句
select year,
max(case when class = 'ClassA' then score end) as A,
max(case when class = 'ClassB' then score end) as B,
max(case when class = 'ClassC' then score end) as C
from game
group by year;
--结果
year a b c
2020 10 12 9
2021 12 8 NULL
连续性问题
连续3天登录的用户
--建表语句
create table login(uid int, dt date, status int)
row format delimited fields terminated by ' ';
load data local inpath '/temp/sql.txt' into table login;
--数据:用户id、日期、是否登录
1 2022-03-01 0
1 2022-03-02 1
1 2022-03-03 1
1 2022-03-04 1
1 2022-03-05 1
1 2022-03-06 0
1 2022-03-07 1
2 2022-03-01 0
2 2022-03-02 1
2 2022-03-03 1
2 2022-03-04 0
2 2022-03-05 0
2 2022-03-06 1
2 2022-03-07 1
3 2022-03-01 1
3 2022-03-02 1
3 2022-03-03 1
3 2022-03-04 0
3 2022-03-05 1
3 2022-03-06 1
3 2022-03-07 1
--sql语句
select *
from (
--查询用户在起始日期及其之后的连续登录次数
select t1.uid, min(t1.dt) as start_day, count(*) as day_count
from (
--按照用户分组,按照日期递增排序,计算连续登录操作的锚定日期
select uid, dt, date_sub(dt, row_number() over(partition by uid order by dt asc)) as diff
from login
where status = 1 --筛选登录的日期
) t1
group by t1.uid, t1.diff --按照用户、锚定日期分组
) t2
where t2.day_count > 2; --筛选出至少3次的连续登录操作
--结果
t2.uid t2.start_day t2.day_count
1 2022-03-02 4
3 2022-03-01 3
3 2022-03-05 3
连续3年获得冠军的队伍
--建表语句
create table champion(year int, team string)
row format delimited fields terminated by ' ';
load data local inpath '/temp/sql.txt' into table champion;
--数据:年份、获得冠军的队伍
2000 Sun
2001 Lakers
2002 Rockets
2003 Rockets
2004 Rockets
2005 Spurs
2006 Spurs
2007 Sun
2008 Lakers
2009 Lakers
2010 Lakers
2011 Lakers
2012 Warriors
2013 Warriors
2014 Warriors
2015 Heat
2016 Warriors
2017 Cavaliers
2018 Warriors
2019 Sun
2020 Warriors
2021 Warriors
2022 Raptors
--sql语句
--查询队伍在起始年份及其之后的连续获得冠军的次数
select t1.team, min(t1.year) as start_year, count(*) as count
from (
--按照队伍分组,按照年份递增排序,计算队伍获得冠军的锚定年份
select year, team, year - row_number() over(partition by team order by year asc) as diff
from champion
) t1
group by t1.team, t1.diff --按照队伍、锚定年份分组
having count > 2; --筛选出至少3次的连续获得冠军
--结果
t2.team t2.start_year t2.count
Lakers 2008 4
Rockets 2002 3
Warriors 2012 3
间隔连续问题
给定每天登录游戏的所有用户,返回每个用户连续登录的最长天数,间隔一天的两次登录也可以看作连续登录
--建表语句
create table login(id int, dt date)
row format delimited fields terminated by ' ';
load data local inpath '/temp/sql.txt' into table login;
--数据:用户id、登录日期
1001 2022-07-01
1002 2022-07-01
1003 2022-07-01
1001 2022-07-02
1002 2022-07-03
1001 2022-07-04
1002 2022-07-05
1003 2022-07-05
1001 2022-07-07
1002 2022-07-07
1003 2022-07-08
1001 2022-07-09
1002 2022-07-09
--sql语句
--按照用户id分组,查询每个用户连续登录的最长天数
select id, max(day_count) as day_count
from (
--按照用户id、flag字段分组,计算登录日期的最大值、最小值之差,表示一次连续登录的天数
select id, datediff(max(dt), min(dt)) + 1 as day_count
from (
--按照用户id分组,按照登录日期递增排序,判断当前登录、上次登录的日期之差是否大于2,累计求和,
--使得属于连续登录的所有日期具有相同取值的flag字段
select id, dt,
sum(if(diff > 2, 1, 0)) over(partition by id order by dt asc) as flag
from (
--查询当前登录、上次登录的日期之差diff
select id, dt,
case when pre_dt is null then 0
else datediff(dt, pre_dt) end as diff
from (
--按照用户id分组,按照登录日期递增排序,查询当前登录日期、上次登录日期
select id, dt,
lag(dt) over(partition by id order by dt asc) as pre_dt
from login
) t1
) t2
) t3
group by id, flag
) t4
group by id;
--结果
id day_count
1001 4
1002 9
1003 1
波峰波谷问题
股票价格在时间点上的波峰与波谷
--建表语句
create table price(stock string, time string, price int)
row format delimited fields terminated by ' ';
load data local inpath '/temp/sql.txt' into table price;
--数据:股票id、时间点、价格
A1 06:00 12
A1 09:00 16
A1 12:00 24
A1 15:00 17
A1 18:00 11
A1 21:00 13
B1 06:00 18
B1 09:00 12
B1 12:00 13
B1 15:00 13
B1 18:00 15
B1 21:00 17
C1 06:00 12
C1 09:00 13
C1 12:00 15
C1 15:00 17
C1 18:00 18
C1 21:00 20
--sql语句
select * from (
--查询波峰点
select t1.stock, t1.time, t1.price, 'top' as top
from (
select stock, time, price,
--按照股票分组,按照时间点递增排序
lag(price) over(partition by stock order by time asc) as previous, --前面时间点的价格
lead(price) over(partition by stock order by time asc) as next --后面时间点的价格
from price
) t1
where t1.price > t1.previous and t1.price > t1.next --筛选出波峰点
union
--查询波谷点
select t2.stock, t2.time, t2.price, 'down' as top
from (
select stock, time, price,
lag(price) over(partition by stock order by time asc) as previous,
lead(price) over(partition by stock order by time asc) as next
from price
) t2
where t2.price < t2.previous and t2.price < t2.next --筛选出波谷点
) t3;
--结果
t3.stock t3.time t3.price t3.top
A1 12:00 24 top
A1 18:00 11 down
B1 09:00 12 down
浏览时长问题
给定用户在多个时间点上的点击浏览记录,如果两次点击浏览的时间间隔不超过30个单位,则两次浏览属于相同的会话。查询用户在每次会话中的浏览时长、浏览步长,步长表示点击浏览的次数
--建表语句
create table click(id string, time int)
row format delimited fields terminated by ' ';
load data local inpath '/temp/sql.txt' into table click;
--数据:用户id、点击浏览时间
a 1001
a 1005
a 1020
a 1048
a 1078
a 1230
a 1245
a 1270
a 1282
b 1101
b 1132
b 1156
b 1180
b 1200
b 1230
b 1345
b 1370
b 1400
--sql语句
--查询用户在每个会话中的起始时间点、点击浏览次数、浏览时长
select id, min(time) as start_time, count(*) as count, max(time) - min(time) as total_time
from (
--分组排序后,从上到下计算value列的累加和。如果求和结果相同,则表示属于相同的会话
select t2.id, t2.time, sum(t2.value) over(partition by id order by time asc) as stage
from (
--如果与前一次点击的时间之差超过30,则value列为1,否则为0
--value列为1表示这次点击属于一个新的会话,为0表示这次点击与前一次属于相同的会话
select id, time, diff, case when nvl(diff, 9999) > 30 then 1 else 0 end value
from (
--按照用户id分组,按照点击浏览时间递增排序,计算前后两次点击的时间之差
select id, time, time - lag(time) over(partition by id order by time asc) as diff
from click
) t1
) t2
) t3
group by id, stage;
--结果
id start_time count total_time
a 1001 5 77
a 1230 4 52
b 1101 1 0
b 1132 5 98
b 1345 3 55
活动时长问题
每个品牌具有多个打折活动,给定每个活动的开始时间、结束时间,返回每个品牌实际参与打折的天数,重复日期不计算在内
--建表语句
create table discount(brand int, start_dt date, end_dt date)
row format delimited fields terminated by ' ';
load data local inpath '/temp/sql.txt' into table discount;
--数据:品牌id、活动开始时间、活动结束时间
1001 2022-07-01 2022-07-03
1001 2022-07-05 2022-07-10
1002 2022-07-02 2022-07-08
1002 2022-07-06 2022-07-09
1003 2022-07-12 2022-07-20
1003 2022-07-15 2022-07-18
1004 2022-07-20 2022-07-25
1004 2022-07-22 2022-07-26
1004 2022-07-28 2022-07-30
--sql语句
--按照品牌id分组,计算打折活动的不重复天数之和day_count
select brand, sum(count) as day_count
from (
--根据当前结束时间的最大值max_dt,计算每行打折活动可以贡献的活动天数count
select brand, start_dt, end_dt, max_dt,
case when max_dt is null then datediff(end_dt, start_dt) + 1
when max_dt < start_dt then datediff(end_dt, start_dt) + 1
when max_dt < end_dt then datediff(end_dt, max_dt)
else 0 end as count
from (
--按照品牌id分组,按照开始时间、结束时间递增排序,查询当前结束时间的最大值max_dt
select brand, start_dt, end_dt,
max(end_dt) over(partition by brand order by start_dt asc, end_dt asc rows
between unbounded preceding and 1 preceding) as max_dt
from discount
) t1
) t2
group by brand;
--结果
brand day_count
1001 9
1002 8
1003 9
1004 11
同时在线问题
给定每个用户在线的开始时间、结束时间,返回一个时间段与人数,这个时间段具有最多的在线人数
--建表语句
create table live(id int, start_dt date, end_dt date)
row format delimited fields terminated by ' ';
load data local inpath '/temp/sql.txt' into table live;
--数据:用户id、开始时间、结束时间
1001 2022-07-01 2022-07-02
1001 2022-07-04 2022-07-05
1001 2022-07-07 2022-07-10
1001 2022-07-13 2022-07-18
1002 2022-07-01 2022-07-02
1002 2022-07-04 2022-07-05
1002 2022-07-07 2022-07-08
1002 2022-07-10 2022-07-11
1002 2022-07-13 2022-07-14
1002 2022-07-16 2022-07-17
1002 2022-07-19 2022-07-20
1003 2022-07-01 2022-07-20
1004 2022-07-04 2022-07-08
1004 2022-07-12 2022-07-16
1005 2022-07-03 2022-07-06
1005 2022-07-09 2022-07-11
1006 2022-07-04 2022-07-06
1007 2022-07-09 2022-07-12
1008 2022-07-06 2022-07-08
1008 2022-07-11 2022-07-13
1009 2022-07-06 2022-07-08
1009 2022-07-18 2022-07-19
1010 2022-07-11 2022-07-14
--sql语句
select dt, online
from (
select dt,
sum(count) over(order by dt asc) as online
from (
select dt, sum(value) as count
from (
select id, start_dt as dt, 1 as value from live
union
select id, date_add(end_dt, 1) as dt, -1 as value from live
) t1
group by dt
) t2
) t3
order by online desc, dt asc;
--结果
dt online
2022-07-04 6
2022-07-06 6
2022-07-07 6
2022-07-11 6
2022-07-13 6
2022-07-10 5
2022-07-12 5
2022-07-14 5
2022-07-09 4
2022-07-16 4
2022-07-01 3
2022-07-15 3
2022-07-17 3
2022-07-18 3
2022-07-19 3
2022-07-03 2
2022-07-20 2
2022-07-21 0
区间合并问题
给定多个时间段,每个时间段分为开始时间、结束时间,将相互重叠的多个时间段合并为一个区间
--建表语句
create table time_merge(id int, start_time int, end_time int)
row format delimited fields terminated by ' ';
load data local inpath '/temp/sql.txt' into table time_merge;
--数据:id、开始时间、结束时间
1 12 15
2 57 58
3 29 32
4 30 31
5 17 19
6 44 44
7 56 57
8 16 18
--sql语句
--按照区间序号进行分组,查询每个分组的最小开始时间作为区间开始时间,最大结束时间作为区间结束时间
select flag, min(start_time) as start_time, max(end_time) as end_time
from (
--判断哪些时间段属于相同区间,flag表示时间段归属的区间序号,值相同表示属于相同区间
select id, start_time, end_time,
sum(count) over(order by start_time asc, end_time asc) as flag
from (
--根据当前结束时间的最大值max_dt进行比较,标记每个时间段是否为新的区间
select id, start_time, end_time,
case when max_dt is null then 1 --作为一个新的区间
when max_dt < start_time then 1 --作为一个新的区间
else 0 end as count --与前面的区间具有重叠
from (
--按照开始时间、结束时间递增排序,查询当前结束时间的最大值max_dt
select id, start_time, end_time,
max(end_time) over(order by start_time asc, end_time asc rows
between unbounded preceding and 1 preceding) as max_dt
from time_merge
) t1
) t2
) t3
group by flag
;
--结果
flag start_time end_time
1 12 15
2 16 19
3 29 32
4 44 44
5 56 58
共同好友问题
给定每个用户的好友列表,好友关系是互相对称的,返回任意两个用户的共同好友列表
--建表语句
create table common_friend(id string, friends string)
row format delimited fields terminated by ' ';
load data local inpath '/temp/sql.txt' into table common_friend;
--数据:用户id、好友id列表
A B,C,D
B A,C,E
C A,B,D,E,F
D A,C,F
E B,C
F C,D
--sql语句
--创建临时表,将好友关系分解为最细粒度
create table friend as
select id, friend from common_friend lateral view explode(split(friends, ',')) temp as friend;
--按照用户的两两组合进行分组,将所有的共同好友放入列表
select t1.ids, concat_ws(',', collect_list(t1.friend)) as common_friend
from (
--将好友关系表与自身进行连接,查询每个用户是哪两个用户的共同好友
select a.friend, concat(a.id, ',', b.id) as ids
from friend a
join friend b
on a.friend = b.friend --按照共同好友进行连接
where a.id < b.id --筛选出重复记录
) t1
group by t1.ids
;
--结果
t1.ids common_friend
A,B C
A,C B,D
A,D C
A,E B,C
A,F C,D
B,C A,E
B,D A,C
B,E C
B,F C
C,D A,F
C,E B
C,F D
D,E C
D,F C
E,F C
可能好友问题
给定每个用户的好友列表,好友关系是互相对称的,返回每个用户的可能好友。如果两个用户不是好友关系,并且两者拥有至少一个(或者两个)共同好友,则两者互相是可能好友
--建表语句
create table maybe_friend(id string, friends string)
row format delimited fields terminated by ' ';
load data local inpath '/temp/sql.txt' into table maybe_friend;
--数据:用户id、好友id列表
A B,C,D
B A,C,E
C A,B,D,E,F
D A,C,F
E B,C
F C,D
--sql语句
--创建临时表,将好友关系分解为最细粒度
with friend as (
select id, friend from common_friend lateral view explode(split(friends, ',')) temp as friend)
--将具有至少两个共同好友的临时表与好友关系表进行连接,如果临时表的两个用户是好友关系,则在好友关系表中存在对应记录,否则不存在对应记录,
--表示两者是可能好友
select t2.id1, t2.id2
from (
--查询具有至少两个共同好友的任意两个用户
select t1.id1, t1.id2
from (
--将好友关系表与自身进行连接,查询任意两个用户具有的共同好友
select a.id as id1, b.id as id2, a.friend
from friend a
join friend b
on a.friend = b.friend
where a.id < b.id
) t1
group by t1.id1, t1.id2
having count(t1.friend) >= 2
) t2
left join friend
on t2.id1 = friend.id
and t2.id2 = friend.friend
where friend.id is null --排除真实好友,筛选可能好友
;
--结果
t2.id1 t2.id2
A E
A F
B D
推荐商品问题
给定一个用户购买一次商品的记录,返回每个用户可能想要购买的商品。如果其余用户与这个用户购买至少两个相同的商品,则其余用户购买、这个用户没有购买的商品,就是这个用户可能想要购买的商品
--建表语句
create table shop(id string, product int)
row format delimited fields terminated by ' ';
load data local inpath '/temp/sql.txt' into table shop;
--数据:用户id、商品id
A 1
A 2
A 1
A 3
B 2
B 3
B 4
B 5
B 2
C 1
C 2
C 1
D 1
D 3
D 6
--sql语句
--按照用户、商品进行分组、去重
with temp as (
select id, product from shop group by id, product)
--将已购买与推荐购买的临时表与已购买表进行连接,如果临时表的商品已购买,则在已购买表中存在对应记录,否则不存在对应记录,表示推荐商品
select t4.id1 as id, t4.product
from (
--查询每个用户已购买与推荐购买的商品
select t3.id1, t3.product
from (
--查询每个用户、以及具有相同购买倾向的其余用户、其余用户已购买的商品
select t2.id1, t2.id2, temp.product
from (
--查询已购买至少两个相同商品的任意两个用户
select t1.id1, t1.id2
from (
--查询已购买相同商品的任意两个用户
select a.id as id1, b.id as id2, a.product
from temp a
join temp b
on a.product = b.product
and a.id != b.id
) t1
group by t1.id1, t1.id2
having count(t1.product) >= 2
) t2
join temp
on t2.id2 = temp.id
) t3
group by t3.id1, t3.product
) t4
left join temp
on t4.product = temp.product
and t4.id1 = temp.id --相同用户购买相同商品
where temp.product is null --排除已购买商品,筛选推荐商品
;
--结果
id t4.product
A 4
A 5
A 6
B 1
C 3
D 2
登录行为分析
有关的统计指标包含:访问量、活跃用户、新增用户、留存用户、流失用户、沉默用户、回流用户
含义解释:(1)活跃用户,每日登录应用的用户,(2)新增用户,在当前日期第一次登录应用的用户,(3)留存用户,在当前日期登录应用的用户,并且在之前日期登录过应用,(4)流失用户,指定时间内没有登录应用的用户,(5)沉默用户,只有第一次登录应用的用户,之后没有登录过应用,(6)回流用户,在当前日期登录应用的用户,并且在之前的指定时间内没有登录过应用
角色分配:活跃、新增 a、留存 b、留存 c、流失 d、沉默 e、回流 f
--建表语句
create table login_action(uid string, login_date int)
row format delimited fields terminated by ' ';
load data local inpath '/temp/sql.txt' into table login_action;
--数据:用户id、登录日期
d 20220321
e 20220321
f 20220321
a 20220322
b 20220322
d 20220322
a 20220323
b 20220323
c 20220323
a 20220324
b 20220324
c 20220324
a 20220325
b 20220325
c 20220325
f 20220325
--活跃用户:
--新增用户:需要一张全量的用户表,一张某日的登录行为表
--将日期20220321的登录用户作为初始表,表示已知的用户,然后查询日期20220322的新增用户
--登录行为表与全量用户表进行关联,行为表中存在、用户表中不存在的记录表示这个日期的新增用户
--查询出每个日期的新增用户后,需要追加到全量的用户表中
--全量的用户表:表示用户成为新增用户的日期
create table user_add(uid string, add_date int);
insert into table user_add (select * from login_action where login_date = 20220321);
select t1.uid, t1.login_date
from login_action t1
left join user_add t2
on t1.uid = t2.uid
where t1.login_date = 20220322 --查询日期20220322的新增用户
and t2.uid is null --查询在用户表中不存在的记录,即这个日期的新增用户
;
--20220322的新增用户
t1.uid t1.add_date
a 20220322
b 20220322
--所有日期的新增用户
user_add.uid user_add.add_date
d 20220321
e 20220321
f 20220321
a 20220322
b 20220322
c 20220323
--留存用户:需要一张全量的用户表,一张某日的登录行为表
--登录行为表与全量用户表进行关联,行为表中的登录日期与用户表中的登录日期之差为1天,表示1日的留存用户
--使用union all合并1日、2日的留存用户
select t1.uid, t1.login_date, t2.add_date
from login_action t1
join user_add t2
on t1.uid = t2.uid
where t1.login_date = 20220324 --查询日期20220324的留存用户
and t2.add_date = (20220324 - 1) --查询1日留存用户
union all --合并1日、2日留存用户
select t1.uid, t1.login_date, t2.add_date
from login_action t1
join user_add t2
on t1.uid = t2.uid
where t1.login_date = 20220324 --查询日期20220324的留存用户
and t2.add_date = (20220324 - 2) --查询2日留存用户
;
--20220324的留存用户:1日、2日
_u1.uid _u1.login_date _u1.add_date
c 20220324 20220323
b 20220324 20220322
a 20220324 20220322
--流失用户:需要一张全量的登录行为表
--查询每个用户的登录日期的最大值,与当前日期之差超过2日,表示流失用户
select uid, max(login_date) as last_login
from login_action
where login_date <= 20220325 --查询日期20220325的流失用户
group by uid
having max(login_date) < (20220325 - 2) --超过2日表示流失用户
;
--20220325的流失用户
uid last_login
d 20220322
e 20220321
--沉默用户:需要一张全量的登录行为表
--查询每个用户的登录日期的数量,只有一次登录操作,表示沉默用户
select uid, max(login_date) as once_login
from login_action
group by uid
having count(login_date) = 1 --只有一次登录操作的用户表示沉默用户
;
--20220325的沉默用户
uid once_login
e 20220321
--回流用户:需要一张全量的登录行为表
--日期20220325的活跃用户,如果在之前的日期为流失用户,则在日期20220325为回流用户
select t1.uid, t1.login_date, t2.last_login
from (
select uid, login_date
from login_action
where login_date = 20220325 --查询20220325的活跃用户
) t1
join
(
select uid, max(login_date) as last_login
from login_action
where login_date < 20220325 --查询20220325之前的流失用户
group by uid
having max(login_date) < (20220325 - 2)
) t2
on t1.uid = t2.uid
;
--20220325的h用户
t1.uid t1.login_date t2.last_login
f 20220325 20220321
购买行为分析
有关的统计指标包含:点击量、下单数、支付数
点击行为分析
JSON解析
解析嵌套Json字符串
get_json_object()方法
原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/281141.html