【SQL】刷题练习SuperSet

0.来源

知乎-数据分析师SQL刷题, 体验完爆牛客网-数据民工来取经儿

1.题目

1.每个用户消费频次: 来光顾次数越多,自然用户价值越高
  • 考察group by, count()函数;
  • 在订单order_info表中按照userId分组(group by),进而使用count()函数统计其订单数(消费频数)。
-- 我的答案
SELECT oi.userId,count(oi.userId) as order_times
FROM order_info as oi
group by oi.userId
order by order_times desc;

运行结果:

101 8
102 3
112 2
103 2
104 1
105 1
106 1
107 1
108 1
109 1
110 1
111 1
113 1
114 1
115 1
117 1
118 1

-- 网站所给答案
SELECT userid, count(*) as freq from order_info group by userid;

运行结果:

101 8
102 3
103 2
104 1
105 1
106 1
107 1
108 1
109 1
110 1
111 1
112 2
113 1
114 1
115 1
117 1
118 1

2.消费频次的用户数分布: freq, userNum
  • 考察嵌套查询;
  • 将第一题的结果作为查询来源,此次以消费频次为分组依据,随后通过count()函数查询用户数目。
-- 我的答案
select user_freq.order_times as freq, count(user_freq.userId) as userNum
from (SELECT distinct oi.userId,count(oi.userId) as order_times
FROM order_info as oi group by oi.userId order by order_times desc) as user_freq
group by user_freq.order_times;

运行结果:

1 13
2 2
3 1
8 1

-- 网站所给答案
select t1.freq, count(*) as userNum from 
(SELECT userid, count(*) as freq from order_info group by userid) t1
group by t1.freq
order by t1.freq;

运行结果:

1 13
2 2
3 1
8 1

3.每个用户最近一次消费日期: 最后一次消费的用户,更应被关注
  • 考察max()函数;
  • 在对order_info中userId进行group by分组的基础上,通过max()函数筛选出每个用户的消费日期最大值。
-- 我的答案
select userId, max(payTime) as '最近消费时间'
from order_info as oi
group by oi.userId;

运行结果:

101 2019-06-02T21:21:00
102 2019-06-02T21:21:00
103 2019-06-01T23:23:00
104 2019-06-01T22:22:00
105 2019-06-01T22:22:00
106 2019-06-01T22:22:00
107 2019-06-01T22:22:00
108 2019-06-01T22:22:00
109 2019-06-01T22:22:00
110 2019-06-01T22:22:00
111 2019-06-01T22:22:00
112 2019-05-28T12:12:00
113 2019-05-27T14:14:00
114 2019-05-27T14:14:00
115 2019-05-27T14:14:00
117 2019-05-27T14:14:00
118 2019-05-27T14:14:00

-- 网站所给答案
SELECT userid, date(max(payTime)) as freq from order_info group by userid;

运行结果-通过date()函数转化为日期:

101 2019-06-02
102 2019-06-02
103 2019-06-01
104 2019-06-01
105 2019-06-01
106 2019-06-01
107 2019-06-01
108 2019-06-01
109 2019-06-01
110 2019-06-01
111 2019-06-01
112 2019-05-28
113 2019-05-27
114 2019-05-27
115 2019-05-27
117 2019-05-27
118 2019-05-27

4.每天首次消费的用户数,目的查看付费拉新状态: firstPayDay, userNum
  • 考察min()函数;思路和第二题类似;
  • 首次消费是关键词,即用户第一次支付并完成订单;所以本题实际上是以日期(每天)为分组依据,使用count()统计第一次消费的用户;第一次消费日期则通过用户分组下的min()函数筛选出。
-- 我的答案
select user_first_pay_day.firstPayDay,
count(user_first_pay_day.userId)as userNum
from (select oi.userId, date(min(oi.payTime)) as firstPayDay 
from order_info as oi group by oi.userId) as user_first_pay_day
group by user_first_pay_day.firstPayDay;

运行结果:

2019-05-25 3
2019-05-27 6
2019-06-01 8

-- 网站所给答案:别名更精简
select date, count(*) as userNum
from (select userId,date(min(payTime)) as date from order_info 
group by userId) t1
group by date;

运行结果:

2019-05-25 3
2019-05-27 6
2019-06-01 8

5.每次消费时长的用户数分布,按小时数group by, 输出: hourNum, userNum
  • 考察timestampdiff()函数,计算createTime和payTime之间的小时数;
  • timestampdiff()和datediff()函数的用法可以参考《MYSQL中 datediff、timestampdiff函数的使用》;
  • 通过group by消费小时数,使用count()计算用户数,类似于第二题和第四题。
-- 我的答案
select user_hours.hours as hourNum,count(user_hours.userId) as userNum
from(select oi.userId,timestampdiff(hour,oi.createTime,oi.payTime)as hours
from order_info as oi)as user_hours
group by user_hours.hours;

运行结果:

2 1
3 5
4 17
5 4
6 1

-- 网站所给答案
select hourNum, count(*) as userNum
from(select userId,timestampdiff(hour, createTime, payTime) as hourNum 
from order_info) t1
group by hourNum;

运行结果:

2 1
3 5
4 17
5 4
6 1

6.每个桌子累计订单量和用户量: deskId, orderNum, deskNum
  • 考察join连接语法;
  • 考察distinct用法;
  • 按照order_info中的deskId分类,通过count()函数统计orderid直接计算订单数;
  • 而桌子总计的用户数,需要通过join连接desk_info具体座位数得出(错误思路);
  • 而桌子对应的用户量也直接用count()统计userId即可,用户去重用distinct。
-- 我的答案:做错了
select oi.deskid, count(oi.orderid) as orderNum,count(oi.orderid)*di.num as deskNum
from order_info as oi
inner join desk_info as di on di.deskId = oi.deskid 
group by oi.deskId;

运行结果-其实是错误答案,我求出来的是座位总数,偏离题意:

101 3 6
102 3 6
103 1 2
104 1 2
105 4 16
106 4 16
107 6 36
108 1 1
109 2 2
110 3 3

-- 我修改之后的答案
select oi.deskid,count(oi.orderId) as orderNum, 
count(DISTINCT oi.userId) as userNum
from order_info as oi
group by oi.deskid

运行结果:

101 3 1
102 3 1
103 1 1
104 1 1
105 4 4
106 4 4
107 6 6
108 1 1
109 2 1
110 3 2

  • 网站所给答案,使用了left join完全没有必要,其中的t2完全没有用到。
  • 实际上,用到desk_info中的num这一列时才用得到,num这一列应该是座位的数量,因此不适用于本题(计算座位数刚好是我之前做的已经用删除线划掉的错误答案)。
-- 网站索所给答案
select t1.deskId, count(orderId) as orderNum, count(distinct userId) as userNum
from desk_info t1 
left join order_info t2 on t1.deskId=t2.deskId 
group by t1.deskId

运行结果:

101 3 1
102 3 1
103 1 1
104 1 1
105 4 4
106 4 4
107 6 6
108 1 1
109 2 1
110 3 2

7.桌子座位数的对应的订单量, 目的是看用户是喜欢单独过来还是约朋友过来: seatNum, orderNum
  • 考察inner join;
  • 这题可以使用上题的错误思路,因为用到了座位数及对应的订单量;跨越了两个表,需要通过外键字段进行join连接。
  • 以seatNum为分组依据进行group by,使用count()统计其相应桌号对应的订单总数。
-- 我的答案
select di.num as seatNum, count(oi.orderid) as orderNum
from order_info as oi
inner join desk_info as di on di.deskId = oi.deskid 
group by di.num;

运行结果:

1 6
2 8
4 8
6 6

  • 因为每一个订单都有桌号,所以此处left join和inner join效果一致。
-- 网站所给答案
select t1.num, count(orderId) as orderNum
from desk_info t1 
left join order_info t2 on t1.deskId=t2.deskId 
group by t1.num

运行结果:

1 6
2 8
4 8
6 6

8.优惠券过期日期的对于用户数, 目的便于到期前消费提醒, 输出: date, userNum
  • 按照过期日期expireTime分类,使用count()计算用户数;
  • 注意date()函数的使用。
-- 我的答案
select date(ucd.expireTime) as exDate,count(DISTINCT ucd.userId)as userNum
from user_coupon_detail as ucd
group by date(ucd.expireTime);

运行结果:

2019-06-24 3
2019-06-25 1
2019-06-26 7
2019-06-27 2
2019-06-28 1
2019-06-29 1
2019-07-01 11
2019-07-02 2

-- 网站所给答案
select date(expireTime) as expireDate, 
count(distinct userId) as userNum
from user_coupon_detail 
group by date(expireTime);

运行结果:

2019-06-24 3
2019-06-25 1
2019-06-26 7
2019-06-27 2
2019-06-28 1
2019-06-29 1
2019-07-01 11
2019-07-02 2

9.每个用户消费总金额: 这个…直接用钱来衡量了, 假设优惠券类型只有折扣类型,没有满减类型
  • 分组依据是用户;考察:case when用法(这一点最开始我没有想到);
  • 消费总金额=商品单价✖数量✖折扣;
  • 这需要涉及到的表包括:order_info订单信息表(用户分组依据),order_detail订单详情表(关联所买产品及其数量),product产品表(关联产品价格),coupon_info优惠券信息表和user_coupon_detail优惠券详情表(关联优惠券折旧discount)。
-- 我最开始的答案-错了
select oi.userId, (sum(p.price*od.num*ci.discount/100)) as totalMoney
from order_info as oi
inner join order_detail as od on od.orderid = oi.orderid
inner join user_coupon_detail as ucd on ucd.couponId = oi.couponId
inner join coupon_info as ci on ci.type = ucd.type
inner join product as p on p.productid = od.productid 
group by oi.userId
  • 整体思路没有错,但是由于没有考虑到order_info中的某些订单不存在couponId这一列(为null),如图:【SQL】刷题练习SuperSet_第1张图片
  • 因此,需要通过case when过滤掉未使用优惠券的订单(分别处理)。
    运行结果-错误答案:

101 107.36
102 58.08
103 60.72
104 22
105 16.72
106 16.72
107 24.64
108 42.24
109 39.6
110 48.4
111 35.2
112 65.12
113 22
114 22
115 24.64
117 24.64
118 17.6

-- 修改之后的答案:又错了
select oi.userId, 
sum(case when oi.couponId>0 then p.price*od.num*ci.discount/100
else p.price*od.num end) as totalMoney
from order_info as oi
inner join order_detail as od on od.orderid = oi.orderid
inner join user_coupon_detail as ucd on ucd.couponId = oi.couponId
inner join coupon_info as ci on ci.type = ucd.type
inner join product as p on p.productid = od.productid 
group by oi.userId
  • 需要注意的是,select中的内容,除了聚合函数,其余的字段需要在group by中出现,这是规范的写法,并且在部分数据库环境下不遵守这个规范会报错。
  • 上面又写错了,因为想当然一直使用inner join,实际上在couponId不存在的订单中,使用inner join会被省略,因此需要改成left join。
  • 运行结果:再次错误

101 107.36
102 58.08
103 60.72
104 22
105 16.72
106 16.72
107 24.64
108 42.24
109 39.6
110 48.4
111 35.2
112 65.12
113 22
114 22
115 24.64
117 24.64
118 17.6

  • 再次修改之后:
select oi.userId, 
sum(case when oi.couponId>0 then p.price*od.num*ci.discount/100
else p.price*od.num end) as totalMoney
from order_info as oi
inner join order_detail as od on od.orderid = oi.orderid
left join user_coupon_detail as ucd on ucd.couponId = oi.couponId
left join coupon_info as ci on ci.type = ucd.type
left join product as p on p.productid = od.productid 
group by oi.userId

运行结果:

101 216.36
102 58.08
103 60.72
104 22
105 16.72
106 16.72
107 24.64
108 42.24
109 39.6
110 48.4
111 35.2
112 65.12
113 22
114 22
115 24.64
117 24.64
118 17.6

-- 网站所给答案
select t1.userId, 
sum(case when t1.couponId>0 then price*t4.discount/100 else price end) as totalPay
from order_info t1 
left join order_detail t2 on t1.orderId=t2.orderId 
left join user_coupon_detail t3 on t1.couponId=t3.couponId
left join coupon_info t4 on t3.type=t4.type
left join product t5 on t2.productid=t5.productid
group by t1.userId

运行结果:

101 216.36
102 58.08
103 60.72
104 22
105 16.72
106 16.72
107 24.64
108 42.24
109 39.6
110 48.4
111 35.2
112 65.12
113 22
114 22
115 24.64
117 24.64
118 17.6

  • 实际上,网站所给答案也不严谨。理由是它忽略了商品数量,本题中由于商品数量都是1,所以对结果不影响。

你可能感兴趣的:(SQL)