24-40, 涉及RFM分类,累计问题,异常值,多行合并concat,表的加法UNOIN,having 用法补充,lead窗口函数,sql执行顺序等
【补充】
HAVING 语法与 WHERE 语法类似,
- HAVING 可以包含聚合函数
- HAVING 子句可以引用选择列表中显示的任意项
- HAVING 不可以使用聚合函数之后的别名(orderby可以)
24.RFM用户分类
是2020年1月份某电商平台的用户行为记录表(用户编号,商品编号,产品分类编号,用户行为类型,用户活跃日期,消费金额)。
RFM-最近一次消费时间间隔(R)、消费频率(F)、消费金额(M)。定义
R:最近一次购买的时间到现在(1.31日)间隔
F:一月购买次数
M:一月购买总金额
根据RFM得分制定打分规则, 比如R很小, F和M很大认为是好的用户
if(分数>60, "及格", "不及格")
-- 计算RFM+打分
insert into t_RFM
select 用户编号,
(case
when R>15 then 1
when R>5 and R<=15 then 2
when R<=5 then 3
end) as R分,
........
.... -- FM同理
....
from
(
select 用户编号,
min(datediff("2020-01-31",用户活跃日期)) as R
count(1) AS F,
sum(购买金额) as M
from 用户行为表
where 用户行为类型 = "购买"
and 用户活跃日期>="2020-01-01"
group by 用户编号
);
-- 计算平均值,并比较用户相对于平均值的高低
select 用户id,(case
when r_type = "高" and f_type = "高" and m_type = "高" then "重要价值用户"
when r_type = "高" and f_type = "低" and m_type = "高" then "重要发展用户"
when r_type = "低" and f_type = "高" and m_type = "高" then "重要保持用户"
when r_type = "低" and f_type = "低" and m_type = "高" then "重要挽留用户"
...
...
end
) as rfm分类结果
from
(
select 用户id,
if(R分 > r_mean, "高", "低") as r_type,
if(f分 > f_mean, "高", "低") as f_type,
if(m分 > m_mean, "高", "低") as m_type,
from
t_RFM t1
left join (
select avg(R分) r_mean,avg(f分) f_mean ,avg(m分) m_mean
from t_RFM
) t2 --可以没有on的筛选条件
) tmp;
28.在职员工发放奖金
雇员奖金表是给员工发放奖金的记录:雇员编号,接收日期,奖金类型。其中奖金有3种类型:奖金类型的值是1,表示奖金金额为薪水salary的10%,奖金类型的值是2,表示奖金金额为薪水salary的20%,奖金类型的值是3,表示奖金金额为薪水salary的30%
雇员表:编号,出生日期,名,姓,性别,雇用日期
薪水表:编号,薪水,起始日期,结束日期(薪水变动的记录都在,最新的薪水结束日期为'9999-01-01')
问题:查找雇员编号、名、姓、奖金类型、对应的当前薪水以及奖金金额。
select *,
(case
when c.奖金类型 = "1" then 0.1*当前薪水
when c.奖金类型 = "2" then 0.3*当前薪水
when c.奖金类型 = "3" then 0.3*当前薪水
else null
end) as 奖金金额
from
(
select a.雇员编号,a.名,a.姓,b.薪水 as 当前薪水,c.奖金类型
from 雇员表 a
left join 薪水表 b
on a.编号 = b.编号 and b.结束日期 = '9999-01-01'
left join 奖金表 c
on a.编号 = c.编号
) tmp
29. 贝壳找房—成交分析
“成交订单表”里记录了某房产平台(类似链家、贝壳等)每日房屋成交的明细。(贝壳面试题)字段“成交客源渠道”中的值是“客源角色人”、“业主线上委托”、“”表示线下渠道,其余的成交客源渠道是线上。字段“房屋用途”中的值有“住房”、”车位”、”车库”等。
1.当月截止昨天二手线上成交占比(含车位)>=50%的门店可获奖;
(线上成交占比=线上成交单量/总成交单量)
2.符合获奖条件的门店的第1单线上成交的,经纪人可获得800贝壳币(可以用于兑换奖金),第2单可获400贝壳币,第3单及以上可获200贝壳币,但车库不奖励(字段“房屋用途”中的值是”车位”、”车库”认为是车库);
3.在一个连续的SQL中实现以上需求,不能拆分成多个SQL,必须输出奖励发放表格字段如下(可增加);
(价格,经纪人,客源,时间,用途)
-
datediff(start, end) mysql
-
datediff(end, start) hive sql / sql server
-- 输出奖励发放表(输出表格中增加了3个字段,分别是:是否线上、经纪人所在门店的线上占比、该单应发的贝壳币)
select from t3.成交价........., t3.是否线上,t3.线上成交占比,
(case
when 是否获奖门店 = "获奖门店" and 线上房屋成交时间排序 = 1 then 800 --符合获奖条件的门店的第1单线上成交的,经纪人可获得800贝壳币
when 是否获奖门店 = "获奖门店" and 线上房屋成交时间排序 = 2 then 400
when 是否获奖门店 = "获奖门店" and 线上房屋成交时间排序 = 3 then 200
else 0
end
) as 应发放贝壳币
from
(
select
t1.*,t2.线上成交占比,
(case when 成交客源渠道 in (“客源角色人”,“业主线上委托”,“”) then "非线上" else "线上" end) as 是否线上,
row_number() over(partition by 签约经纪人门店 order by (case when (房屋用途 in ("车位","车库"))or(成交客源渠道 in (“客源角色人”,“业主线上委托”,“”)) then null else 签约时间) asc) as 线上房屋成交时间排序,
(case when t2.线上成交占比<0.5 then "非获奖门店" else "获奖门店") as 是否获奖门店--二手线上成交占比(含车位)>=50%的门店
from 成交订单表 t1
left join
(
select
签约经纪人门店
count(distinct (case when 成交客源渠道 in (“客源角色人”,“业主线上委托”,“”) then null else 协议id end)) / count(distinct 协议id) as 线上成交占比
from 成交订单表
where datediff(d, 签约时间, getdate())>0 and datediff(m,签约时间, getdate()) = 0-- 当月截止昨天
group by 签约经纪人门店;
) t2
on t1.签约经纪人门店 = t2.签约经纪人门店
where datediff(d, 签约时间, getdate())>0 and datediff(m, 签约时间, getdate()) = 0
) t3;
30.累计求和问题
“薪水表”中记录了员工发放的薪水。包含雇员编号,薪水、起始日期、结束日期。其中,薪水是指该雇员在起始日期到结束日期这段时间内的薪水。当前员工是指结束日期 = '9999-01-01'的员工。
业务问题:按照雇员编号升序排列,查找薪水的累计和(累计薪水)。其中累计薪水是前N个当前员工( 结束日期 = '9999-01-01')的薪水的累计和,其他以此类推。
select 雇员编号, 薪水
sum() over(order by 雇员编号 asc) as 累计薪水
from 薪水表
where 结束日期 = "9999-01-01";
31.复购用户
"课程订单表”里记录了某在线教育App的用户购买课程的信息:用户id, 购买时间, 课程类型, 消费金额。
请使用sql将购买记录表中的信息,提取为复购分析表的格式,并用一条sql语句写出。
日期 | 当日首次购买用户数 | 当月复购用户数 | 次月复购用户数 | 第三月..... |
---|---|---|---|---|
复购用户:如果一个用户的首次购买日期是a,且该用户在a之后的第n月内,也有购买行为,这个用户被算做第n月复购用户。
datediff(m, 购买时间, 复购时间) = n:第n月复购用户
select
a.日期,
count(distinct (case when b.购买时间 is null then a.用户id else null end)) 当日首次购买用户数,
count(distinct (case when datediff(m, b.购买时间, a.购买时间)=0 then a.用户id else null end)) as 当月复购用户数,-- b.购买时间 就是复购时间
count(distinct (case when datediff(m, b.购买时间, a.购买时间)=1 then a.用户id else null end)) as 次月复购用户数,
count(distinct (case when datediff(m, b.购买时间, a.购买时间)=2 then a.用户id else null end)) as 第三月复购用户数,
.......
from 订单表 a
left join b
on a.用户id = b.用户id
and a.购买时间<b.购买时间
group by a.日期;
32. 金融行业实战
下表是某金融App的原始数据表:用户id,投资金额,年龄,性别,省份,城市等等
为方便后期分析,我们对数据进行分类。数据通常分为3类:用户数据、行为数据、产品数据。
- 用户数据:年龄、性别、手机省份、手机城市、注册时间、用户注册终端、用户注册渠道、会员级别等
- 最近登录数据:用户id,省份,城市,终端,ip,设备,时间
- 活跃数据:用户id+是否...7天内...类的问题
- 行为数据:用户id,投资金额,托管,开通日期,首次充值时间,首投时间,等等
问题:
1.理解数据
如果将该数据建立一个数据库,有哪些表,各表之间的关系是什么?
2.数据整理
假设该Excel数据是一个数据表(Datatable),请用SQL写出:
(1)最近一次登录,各城市各有多少用户在表中?
(2)“用户ID”之间的可能关系?(比如家庭成员,情侣等)【解法】ip地址相同但是uid不同,连接用户表看看差异
(3)请找出数据表中的异常值,并以此阐述你如何控制数据质量并以本数据为例设计数据质量报表。
【回答】
1.首先分析业务流程:访问->注册->浏览商品->充值 投资托管 ,日常登录-浏览商品
各个表按照用户id链接,用户数据-行为数据-登录数据-活跃数据
2.定义变量&赋值(mysql中不用先声明)
-- 除了set select 也可以:
select @变量名:= 字段名 from 表名 where 过滤语句;
select @num:=value;
-- (1)
select 省份,城市,count(用户id)
from 最近登录表
group by 省份,城市
-- (2)家庭成员,情侣,特点:ip地址一致id不同,
select *
from 用户表
where 用户ID in (
select distinct 用户id
from 最近登录表
group by ip地址
having count(distinct 用户id)>1
);
-- (3)异常值
select
用户id
from 用户表
left join
(
select
mean(年龄)+ 3*stdev(年龄) as 异常值2,
mean(年龄)- 3*stdev(年龄) as 异常值1
from 用户表
) tmp
where 年龄<异常值1 or 年龄>异常值2;
33.交易记录分析
某商场为了分析用户购买渠道。表1是用户交易记录表,记录了用户id、交易日期、交易类型和交易金额。表2是用户类型表,记录了用户支付类型(微信、支付宝、信用卡等),分别有type1、type2。
要求:
1.请在 type1的用户类型中,找出总交易金额最大的用户。
2.筛选每个用户的第2笔交易记录。
3.如下表:如何实现表3的数据格式?【group_concat】
用户id | 交易日期 | 交易类型 |
---|---|---|
A1 | 2017-06-01,2018-01-01... | 抽奖,游戏,..... |
-- (1)总交易金额最大的用户
select t1.用户id, 总交易金额
from 用户类型表 t1
left join
(
select 用户id, sum(交易金额) as 总交易金额
from 用户交易记录表
group by 用户id
) t2
on t1.用户id = t2.用户id
and t1.用户类型 = "type1"
order by 总交易金额
limit ,1; -- 返回第一行记录
-- (2)每个用户的第2笔交易记录
select *
from(
select *,row_number()over(partition by 用户id order by 交易日期 asc) as rk
from 用户交易记录表
) tmp
where rk =2;
--(3)实现表3的数据格式
select 用户id,
group_concat(日期) as 交易日期,
group_concat(交易类型) as 交易类型
from 用户交易记录表
group by 用户id;
34.月消费分析
交易表记录了每天交易的客户交易时间、客户号、消费类型和消费金额。其中,交易类型有两种值:消费和转账。
客户表记录了客户信息,包括客户号,客户名称和客户所属的银行分行号
分行表记录每个分行的信息,包括分行号、分行名称及对应上级分行。
【问题】该理财银行要求对客户及销售额分析报告,要求如下:
1.计算2016年1-3月的消费总金额
2.2016年3月消费金额>=1288的客户信息,及其3月内首次达到1288的时间
3.汇总各省分行(省分行下属支行也需要汇总至省分行)的2016年3月的总消费金额
总行对应分行号=1,省分行=上级分行为1的分行
--(1)
select
sum(case when 交易时间 between "2016-01-01" and "2016-01-31" then 消费金额 else 0) as 2016年1月,....
from 交易表
--(2)
select t1.客户号, t2.客户名称,
min(交易时间) as 首次达到1288的时间,
max(累计消费金额) as 总消费金额
from
(
select 客户号, 交易时间,
sum(消费金额) over (partition by 客户号 order by 交易时间 asc) as 累计消费金额
from 交易表
where 交易时间 between "2016-03-01" and "2016-03-31"
order by 客户号, 累计消费金额 asc
) t1
left join 客户表 t2 on t1.客户号 = t2.客户号
where t1.累计消费金额>1288
group by t1.客户号;
-- (3)交易表-客户号-分行号-分行表
-- 傻逼题目
select 分行号, sum(消费金额) as 客户总消费
from 客户表 t1
left join 交易表 t2
on t1.客户号 = t2.客户号
where t2.交易时间 between "2016-03-01" and "2016-03-31"
group by 分行号;
36.中位数
成绩表:用户编号,岗位,成绩
问题:写一个sql语句查询每个岗位的成绩中位数位置的范围
C++岗位的中位数位置范围为[2,2],也就是2。因为C++岗位总共3个人,是奇数,所以中位数位置为2。Java岗位的中位数位置范围为[1,2]。因为Java岗位总共2个人,是偶数,所以要知道中位数,需要知道2个位置的数字,而因为只有2个人,所以中位数位置为[1,2]。
求余数: % 或者 mod
select 岗位
(case when 人数%2=0 then 人数/2 else (人数-1)/2)as 起始位置,
(case when 人数%2=0 then 人数/2+1 else (人数+1)/2)as 终止位置
from
(
select 岗位,
count(用户编号) as 人数,
from 成绩表
group by 岗位
) tmp;
37.平均薪水
薪水表中记录了员工的编号,所在部门编号,和薪水。查询出每个部门除去最高、最低薪水后的平均薪水,并保留整数。(字节跳动面试题)
保留整数:format(数字, 小数位数)
select 部门编号, format(avg(薪水),0) as 平均薪水
from
(
select *,
dense_rank() over(partition by 部门编号 order by 薪水 desc) as rk1
dense_rank() over(partition by 部门编号 order by 薪水 asc) as rk2
from 薪水表
) tmp
where rk1>1 and rk2>2
group by 部门编号;
38.课程续费分析
新学员购买50节课为一个学习阶段,学习完想要进入下个阶段必须再次购买,即续费(假设所有学员只能续费一次)并且每个学员可选择不同老师进行学习。表一:学员上课表,表二:续费表
1.现求出续费学员在续费前3个月内的总课量,3个月给学员上课老师数量
2.现求出每个续费学员在续费前的最后一节课的时间,以及对应的上课老师。
--(1)
select 学员id , count(上课时间) as 总课量, count distinct(老师id) as 老师数
from 学院上课表 t1
left join 购买表 t2
on t1.学生id = t2.学生id
and t2.订单类型 = 2
and datediff(m, t2.续费时间, t1.上课时间)<3 --续费前3个月内
group by 学员id;
--(2)
select 学员id ,上课时间, 老师id
from
(
select 学员id ,上课时间, 老师id, row_number()over(partition by 学员id order by 上课时间) as rk
from 学院上课表 t1
left join 购买表 t2
on t1.学生id = t2.学生id
and t2.订单类型 = 2
and datediff(d, t2.续费时间, t1.上课时间)>0 --在续费之前
group by 学员id
) tmp
where rk = 1
39.小红书用户行为
用户订单表:记录用户id、购买的商品id、用户下单的时间及商品的种类。
用户收藏商品表:记录用户id、用户收藏的商品id及收藏时间。
问题:请用一句sql语句得出以下查询结果,得到所有用户的商品行为特征,其中用户行为分类为4种:已购买、购买未收藏、收藏未购买、收藏且购买。
(
select t1.用户id, t1.商品id
1 as "已购买"
(case when t2.商品id is null then 1 else 0 end) as "购买未收藏"
0 as "收藏未购买"
(case when t2.商品id is not null then 1 else 0 end) as "收藏且购买"
from
(
select distinct 用户id, 商品id
from 用户订单表
group by 用户id, 商品id
) t1
left join
(
select distinct 用户id, 商品id
from 用户收藏表
group by 用户id, 商品id
) t2
on t1.用户id = t2.用户id and t1.商品id = t2.商品id
order by t1.用户id
)
-- 上面是所有购买的,考虑是否有过收藏
union
-- 下面补充没购买的,但是有收藏的
(
select t2.用户id, t2.商品id
0 as "已购买"
0 as "购买未收藏"
1 as "收藏未购买"
0 "收藏且购买"
from
(
select distinct 用户id, 商品id
from 用户订单表
group by 用户id, 商品id
) t1
right join
(
select distinct 用户id, 商品id
from 用户收藏表
group by 用户id, 商品id
) t2
on t1.用户id = t2.用户id and t1.商品id = t2.商品id
order by t1.用户id
where t1.用户id is null -- 未购买
);
40.连续得分的球员
两只篮球队进行了激烈的比赛,比分交替上升。比赛结束后,你有一张两队分数的明细表:球队、球员号码、球员姓名、得分分数以及得分时间。
问题:每个球队里为该球队连续三次(及以上)得分的球员
窗口函数 **lead(想查询的列, 向下n行)**over (....),一般用于计算差值,例如:
1)计算花费时间。例如:某数据是每个用户浏览网页的时间记录,将记录的时间错位之后,进行两列相减就可以得到每个用户浏览每个网页实际花费的时间。
2)计算与上次相比薪水涨幅。
-- 首先按照球队和得分时间排序,再找到连续出现三次的球员姓名
-- 用一个lead作窗口函数就可以解决
select distinct 球员姓名
from
(
select 球员姓名,
lead(球员姓名,1) over(partition by 球队 order by 得分时间) as 姓名1,
lead(球员姓名,2) over(partition by 球队 order by 得分时间) as 姓名2
from 分数表
) tmp
where 球员姓名 = 姓名1 and 姓名1 = 姓名2;
【补充】SQL运行顺序:
from join on where group by avg/sum having select distinct order by