30道经典SQL面试题讲解(11-20)

共 14894字,需浏览 30分钟

 ·

2021-03-10 20:12

本篇节选自书籍《对比Excel,轻松学习SQL数据分析》一书,主要讲解数据分析面试中常见的30道SQL面试题。1-10题见:30道经典SQL面试题讲解(1-10)

11 行列互换

现在我们有下面这么一个表row_col_table,这个表中每年每月的销量是一行数据:

year_nummonth_numsales
20191100
20192200
20193300
20194400
20201200
20202400
20203600
20204800

我们需要把上面这种纵向存储数据的方式改成下表所示的横向存储:

year_numm1m2m3m4
2019100200300400
2020200400600800

自己先想一下代码怎么写,然后再参考我的代码。

select
    year_num
    ,sum(case when month_num = 1 then sales end) as m1
    ,sum(case when month_num = 2 then sales end) as m2
    ,sum(case when month_num = 3 then sales end) as m3
    ,sum(case when month_num = 4 then sales end) as m4
from
    demo.row_col_table
group by
    year_num

解题思路:

我们要把纵向数据表转换成横向数据表,首先是把多行的年数据转化为一年是一行,可以通过group by实现;group by一般需要与聚合函数一起使用,但是不是对所有数据进行聚合,所以我们通过case when来达到对指定月份数据进行聚合。

12 多列比较

现在表col_table中有col_1、col_2、col_3三列数据,我们需要根据这三列数据生成最后一列结果列,结果列的生成规则为:如果col_1大于col_2时选col_1列,如果col_2大于col_3列时选col_3列,否则选col_2列。

col_table表如下所示:

col_1col_2col_3
5107
1106
935
529
1043
529
586
886

自己先想一下代码怎么写,然后再参考我的代码。

select
 col_1
 ,col_2
 ,col_3
 ,(case when col_1 > col_2 then col_1
     when col_2 > col_3 then col_3
   else col_2
   end) as all_result
from
 demo.col_table

解题思路:

这个多列比较其实就是一个多重判断的过程,借助case when即可实现,先去判断col_1和col_2的关系,然后再去判断col_2和col_3的关系。这里需要注意一下各判断的执行顺序,先去执行第一行case when,然后再去执行第二行的。最后运行结果如下:

col_1col_2col_3all_result
51077
11066
9359
5295
104310
5295
5866
8866

13 对成绩进行分组

现在有一个某科目的学生成绩表subject_table,这张表存储了每位学生的id、score(成绩)以及其他信息,我们想知道60分以下、60-80分、80-100分这三个成绩段内分别有多少学生,该怎实现呢?

subject_table表如下所示:

idscore
156
291
367
454
556
669
761
883
999

自己先想一下代码怎么写,然后再参考我的代码。

select
    (case 
        when score < 60 then "60分以下"
        when score < 80 then "60-80分"
        when score < 100 then "80-100分"
    else "其他"
    end) as score_bin
    ,count(id) as stu_cnt
from
    demo.subject_table
group by 
    (case 
        when score < 60 then "60分以下"
        when score < 80 then "60-80分"
        when score < 100 then "80-100分"
    else "其他"
    end)

解题思路:

我们现在需要知道每个成绩段内的学生数,需要做的第一件事就是对成绩进行分段,利用的就是case when,对成绩分段完成以后再对分段结果进行group by,然后再在组内计数获得每个分段内的学生数。最后运行结果如下:

score_binstu_cnt
60分以下3
80-100分3
60-80分3

14 周累计数据获取

现在我们有一个订单明细表order_table,这张表中存储了order_id(订单id)、order_date(订单日期)以及其他订单相关信息,现在我们需要每天获取本周累计的订单数,本周累计是指本周一到获取数据当天,比如今天是周三,那么本周累计就是周一到周三。这个该怎么实现呢?

order_table表如下所示:

order_idorder_date
12019/1/8
22019/1/9
32019/1/10
42019/1/11
52020/1/8
62020/1/9
72020/1/10
82020/1/11
92020/1/12

自己先想一下代码怎么写,然后再参考我的代码。

select
    curdate()
    ,count(order_id) as order_cnt
from
    demo.order_table
where 
    weekofyear(order_date) = weekofyear(curdate())
    and year(order_date) = year(curdate())

解题思路:

我们是要获取本周累计的订单数,只需要把本周的订单明细筛选出来,然后对订单id进行计数就是我们想要的。那该怎么把本周的订单明细筛选出来呢?让订单日期所属的周与程序运行当日所属的周是一个周,且所属的年是同一年。后面这个条件一定要注意,因为周数在不同年份是会重复的,但是在同一年内是不重复的。比如2019年有一个52周,2020年也会有,但是不会在一年里面出现两个52周。最后运行结果如下:

cur_dateorder_cnt
2020-01-125

15 周环比数据获取

我们现在需要根据订单明细表order_table,获取当日的订单数;当日的环比订单数,即昨天的数据。

自己先想一下代码怎么写,然后再参考我的代码。

select
    count(order_id) as order_cnt
    ,count(if(date_sub(curdate(),interval 1 day) = order_date,order_id,null)) last_order_cnt
from
    demo.order_table

解题思路:

当日的订单数比较好获取,主要是环比数据的获取,当订单日期等于当日日期向前偏移1天的日期时,对order_id进行计数就是昨日的订单数。这里面需要注意的是,当if条件不满足时,结果为null,而不能是别的,因为count(null)=0,而count()其他内容不等于0。最后运行结果如下:

order_cntlast_order_cnt
91

16 查找获奖同学信息

现在有一张学生信息表table1,这张表记录了id、name等一些其他信息;还有另外一张获奖名单表table2,这张表记录了获奖学生的id和name。现在我们想要通过table1获取获奖学生的更多信息。

table1表如下所示:

idname
1王小凤
2刘诗迪
3李思雨
4张文华
5张青云
6徐文杰
7李智瑞
8徐雨秋
9孙皓然

table2表如下所示:

idname
1王小凤
2刘诗迪
3李思雨
7李智瑞
8徐雨秋
9孙皓然

自己先想一下代码怎么写,然后再参考我的代码。

select
    table1.*
from
    demo.table1
left join
    demo.table2
    on table1.id = table2.id
where
    table2.id is not null

解题思路:

我们要获取获奖同学的全部信息,已知table1表中存储了全部学生的全部信息,我们用table1去左连接table2,如果该同学有获奖,就会在table2中能找到,反之则找不到。所以我们就可以利用table2的id是否为空来判断该同学有没有获奖,进而把我们想要的信息通过where条件筛选出来。最后运行结果如下:

idname
1王小凤
2刘诗迪
3李思雨
7李智瑞
8徐雨秋
9孙皓然

17 计算用户留存情况

现在有一张用户登陆表user_login,这张表记录了每个用户每次的登陆时间,uid(用户id)和login_time(登陆时间)。我们想看用户的次日留存数、三日留存数、七日留存数,只要用户从首次登陆以后再有登陆就算留存下来了,该怎么实现呢?

user_login表如下所示:

uidlogin_time
12019/1/1 6:00
12019/1/2 10:00
12019/1/4 19:00
22019/1/2 10:00
22019/1/3 9:00
22019/1/9 14:00
32019/1/3 8:00
32019/1/4 10:00

自己先想一下代码怎么写,然后再参考我的代码。

select
    (case when t3.day_value = 1 then "次日留存"
          when t3.day_value = 3 then "三日留存"
          when t3.day_value = 7 then "七日留存"
    else "其他"
    end) as type
    ,count(t3.uid) uid_cnt
from
    (select
        t1.uid
        ,t1.first_time
        ,t2.last_time
        ,datediff(t2.last_time,t1.first_time) day_value
    from
        (select
            uid
            ,date(min(login_time)) as first_time
        from
            demo.user_login
        group by
            uid)t1
    left join
        (select
            uid
            ,date(max(login_time)) as last_time
        from
            demo.user_login
        group by
            uid)t2
    on t1.uid = t2.uid)t3
group by
        (case when t3.day_value = 1 then "次日留存"
              when t3.day_value = 3 then "三日留存"
              when t3.day_value = 7 then "七日留存"
        else "其他"
        end)

解题思路:

留存是指用户用户从首次登陆以后再有登陆就算留存下来,不同时长的留存表示这么时长以后仍会再次登陆,比如三日登陆表示用户自首次登陆以后第三天也会进行登陆。我们现在要计算不同留存时长的用户数,首先需要计算不同用户的留存时长,可以用该用户的最后一次登陆时间与首次登陆时间做差就是该用户的留存时长,然后再对留存时长进行分组聚合就得到了我们想要的不同留存时长的用户数。最后运行结果如下:

typeuid_cnt
三日留存1
七日留存1
次日留存1

18 筛选最受欢迎的课程

现在有一张学生科目表course_table,这张表存储了每一位学生的id、name(姓名)、grade(年级)、course(选修课程)以及一些其他信息,现在我们想知道哪门课被学生选的人数最多?

course_table表如下所示:

idnamegradecourse
1王小凤一年级心理学
2刘诗迪二年级心理学
3李思雨三年级社会学
4张文华一年级心理学
5张青云二年级心理学
6徐文杰三年级计算机
7李智瑞一年级心理学
8徐雨秋二年级计算机
9孙皓然三年级社会学
10李春山一年级社会学

自己先想一下代码怎么写,然后再参考我的代码。

select 
    course
    ,count(id) as stu_num
from
    demo.course_table
group by
    course
order by
    count(id) desc
limit 1

解题思路:

我们是要获取被选人数最多的课程,首先需要对课程进行分组,使用的是group by;然后再对组内人数进行计数,即选择该课程的人数,使用的count;然后再对课程人数进行降序排列,使用的是order by;最后把排在第一的课程筛选出来,就是我们要的被选择人数最多的课程。最后结果如下:

coursestu_num
心理学5

想一下上面这种思路是否有问题呢?如果要是有两门或者多门课程的选择人数一样多的时候上面的这种思路得出来的结果是否还正确呢?显然是不正确的。

现在再想一下,如果有多门课程选择人数一样多时怎么办?先想一下再看我的思路。

select 
    course
    ,count(id) as stu_num
from
    demo.course_table
group by
    course
having 
    count(id) = (select 
                 max(stu_num)
                from
                 (select 
                        course
                        ,count(id) as stu_num
                    from
                        demo.course_table
                    group by
                        course
                    )a
                )

解题思路:

如果存在被选择一样多的课程,我们要把一样多的课程全部筛选出来。首先我们还是需要把每门课程以及被选择的人数获取出来,获取思路与第一种思路是一样的,也是针对课程进行group by,然后再针对组内的人数进行计数;不同点在于最多人数获取上。第一种思路是默认选择人数最多的课程只有一个,而第二种思路是假设选择人数最多的课程有多个时,我们就需要把选择人数最多的人数算出来,这里利用子查询去生成;最后再利用having对分组后的结果进行筛选,从而得到选择人数最多的课程。

19 筛选出每个年级最受欢迎的三门课程

还是前面的course_table,现在我们想知道每个年级被选择最多的三门课程,该怎么实现呢?

自己先想一下代码怎么写,然后再参考我的代码。

select 
    *
from
    (select
        grade
        ,course
        ,stu_num
        ,row_number() over(partition by grade order by stu_num desc) as course_rank
    from
        (select
            grade
            ,course
            ,count(id) as stu_num
        from
            demo.course_table
        group by
            grade
            ,course
        )a
    )b
where 
    b.course_rank < 4

解题思路:这是典型的获取组内排名的问题,我们前面的一个问题是获取报名人数最多的课程,只需要把每门课程的报名人数获取到,然后把最多的一个取出来就是我们想要的。可是现在这个问题不仅要获取最多的,还要获取第二多、第三多的。而且还是每个年级内的第一、第二、第三多。对于这种问题,我们可以使用窗口函数来实现,先生成每门课程的报名人数,然后再利用row_number()生成每个年级内每门课程的排序结果,最后再通过排序结果筛选出我们需要的排序。最后运行结果如下:

gradecoursestu_numcourse_rank
一年级心理学31
一年级社会学12
三年级社会学21
三年级计算机12
二年级心理学21
二年级计算机12

当然,我们这里可以通过where条件筛选任意排名的课程。比如如果要筛选排名第5-8的课程,只需要让where条件中的b.course_rank between 5 and 8即可。

20 求累积和

现在有一张2019年一整年的订单表consum_order_table,consum_order_table包含order_id(订单id)、uid(用户id)、amount(订单金额),现在我们想看下80%的订单金额最少是由多少用户贡献的,该怎么实现呢?

consum_order_table表如下所示:

order_iduidamount
201901110
201902220
201903315
201904315
201905420
201906420
201907525
201908525
201909630
201910630
201911735
201912735

自己先想一下代码怎么写,然后再参考我的代码。

select
    count(uid)
from
    (select
        uid
        ,amount
        ,sum(amount) over(order by amount desc) as consum_amount
        ,(sum(amount) over(order by amount desc))
        /(select sum(amount) from demo.consum_order_table) as consum_amount_rate
    from
        (select
            uid
            ,sum(amount) amount
        from
            demo.consum_order_table
        group by 
            uid
        )
    uid_table)t
where 
    t.consum_amount_rate < 0.8

解题思路:

我们要获取人80%的订单金额最少由多少用户贡献的,因为我们现在只有一个订单明细表,所以我们需要先生成一个人维度的订单金额表,然后再在这个人维度表的基础上去进行累积和,累计和的实现可以通过窗口函数来实现,这样就可以得到人维度的累积订单金额,在生成累积和的时候需要按照订单金额进行降序排列,这样就可以得到最少的人数,最后再利用一个子查询,获取到全部的订单金额,用累积订单金额去除全部订单金额,就可以得到累积的订单金额贡献情况。最后运行结果如下:

count(uid)
4

想进一步了解更多内容的同学,可以阅读《对比Excel,轻松学习SQL数据分析》一书。



▊《对比Excel,轻松学习SQL数据分析

张俊红 著


学习SQL 的主要原因是工作需要。网上关于数据相关岗位的招聘都要求有熟练使用SQL 这一条,为什么会这样呢?这是因为我们负责的是与数据相关的工作,而获取数据是我们工作的第一步,比如,你要通过数据做决策,但是现在公司的数据基本上不存储在本地Excel 表中,而是存储在数据库中,想要从数据库中获取数据就需要使用SQL,所以熟练使用SQL 成了数据相关从业者入职的必要条件。本书的所有代码和函数均以MySQL 8.0 为主

(扫码了解本书详情)





如果喜欢本文
欢迎 在看留言分享至朋友圈 三连


 热文推荐  





▼点击阅读原文,获取本书详情~
浏览 9
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报