通过代码
SELECT
t20.tag,
t20.exam_cnt exam_cnt_20,
t21.exam_cnt exam_cnt_21,
concat(round((t21.exam_cnt - t20.exam_cnt)*100/t20.exam_cnt,1 ),'%') growth_rate,
t20.ranking exam_cnt_rank_20,
t21.ranking exam_cnt_rank_21,
CAST(t21.ranking AS SIGNED) - CAST(t20.ranking AS SIGNED) rank_delta
FROM
(SELECT
tag,
'2020' start_year,
count(*) exam_cnt,
rank()over(
order by count(*) desc
) ranking
FROM(select
uid,
e_r.exam_id,
if(DATE_FORMAT(submit_time,'%Y%m') BETWEEN 202001 and 202006,1,0) time1,
tag,
submit_time
FROM
exam_record e_r
RIGHT JOIN
examination_info e_i
ON
e_r.exam_id = e_i.exam_id
WHERE
DATE_FORMAT(submit_time,'%Y%m') BETWEEN 202001 and 202006
or
DATE_FORMAT(submit_time,'%Y%m') BETWEEN 202101 and 202106) tim
WHERE
time1 = 1
GROUP BY
tag) t20
JOIN(SELECT
tag,
'2021' start_year,
count(*) exam_cnt,
rank()over(
order by count(*) desc
) ranking
FROM(select
uid,
e_r.exam_id,
if(DATE_FORMAT(submit_time,'%Y%m') BETWEEN 202001 and 202006,1,0) time1,
tag,
submit_time
FROM
exam_record e_r
RIGHT JOIN
examination_info e_i
ON
e_r.exam_id = e_i.exam_id
WHERE
DATE_FORMAT(submit_time,'%Y%m') BETWEEN 202001 and 202006
or
DATE_FORMAT(submit_time,'%Y%m') BETWEEN 202101 and 202106) tim
WHERE
time1 = 0
GROUP BY
tag) as t21
ON
t20.tag = t21.tag
ORDER BY
growth_rate desc,
exam_cnt_rank_21 desc
别看这么长的代码,其中很多都是重复的,没办法判题系统不接受视图
而且,众所周知啊,同样的一道题,代码越长基本思路就越简单,理解起来就越容易
思路
又是一个乱七八糟条件一堆的题目嗷,但是题目贴心的给了个解释表,就让这个题变得清晰起来了,我再贴一下这个解释表:
然后拆解题目:计算2021年上半年各类试卷的做完次数相比2020年同期的增长率(百分比格式,保留1位小数),以及做完次数排名变化,按增长率和21年排名降序输出。
1.2021上半年、2020同期
2.总完成数,排名,增长率(百分比,1位小数),名次变化
那么 其实这道题还是很简单的对吧?
怎么分别筛选出来2020上半年跟2021上半年呢?
筛选2020 2021 上半年
select
uid,
e_r.exam_id,
if(DATE_FORMAT(submit_time,'%Y%m') BETWEEN 202001 and 202006,1,0) time1,
tag,
submit_time
FROM
exam_record e_r
RIGHT JOIN
examination_info e_i
ON
e_r.exam_id = e_i.exam_id
WHERE
DATE_FORMAT(submit_time,'%Y%m') BETWEEN 202001 and 202006
or
DATE_FORMAT(submit_time,'%Y%m') BETWEEN 202101 and 202106
有人建议我贴上结果,但是直接在网页上写的,它没有字段名,所以看着也很难看,实在想看建议直接自己复制运行/doge
话接上文:
怎么分别筛选出来2020上半年跟2021上半年呢?
啊对,就是between,直接where筛选就行了,这里有一点值得注意的是between区间是闭合的也就是[]
那么怎么分别筛选呢?我们之前做过类似的啊,直接**if(DATE_FORMAT(submit_time,'%Y%m') BETWEEN 202001 and 202006,1,0) **
打上标记就行了,打完之后2020上半年自加字段time1 是1,2021年是0
之所以可以这样来,是我已经用where筛选过上半年的了
这个时候题目上的那个解释表就可以做出来了,但是我选择代码更长但是结构可能结构清晰一点,也就是筛成两个表
题目要求其实都是根据2020、2021年分类的做题总数、分类排名算出的,所以我们下一步就是
2020、2021分类做题数目、排名
#2.1代码
SELECT
tag,
count(*) exam_cnt,
rank()over(
order by count(*) desc
) ranking
FROM(1.代码) tim
WHERE
time1 = 1
GROUP BY
tag
在上一个表的基础上进行查找,time1 是 1 那就是 2020上半年的,整理成为一个表 分类count(*)就行
这里用了窗口函数rank, rank的逻辑我们之前说过,现在再来一次加深印象:1、2、2、3,还是不知道可以看看前面的内容:题解 | #近三个月未完成试卷数为0的用户完成情况#
还有窗口函数不是一定要加上partition by的
2021的也一样,就是把time1 = 1改成0就行
#2.2代码
SELECT
tag,
count(*) exam_cnt,
rank()over(
order by count(*) desc
) ranking
FROM(1.代码) tim
WHERE
time1 = 0
GROUP BY
tag
计算题目需求、排序
SELECT
t20.tag,
t20.exam_cnt exam_cnt_20,
t21.exam_cnt exam_cnt_21,
concat(round((t21.exam_cnt - t20.exam_cnt)*100/t20.exam_cnt,1 ),'%') growth_rate,
t20.ranking exam_cnt_rank_20,
t21.ranking exam_cnt_rank_21,
CAST(t21.ranking AS SIGNED) - CAST(t20.ranking AS SIGNED) rank_delta
FROM
(2.1.代码) t20
JOIN(2.2.代码) as t21
ON
t20.tag = t21.tag
ORDER BY
growth_rate desc,
exam_cnt_rank_21 desc
题目上说,只有两个时间段都做过的类型、也就第二步我们筛选出的两个表中都有的tag才会出现在答案里所以:内连接
20、21分类做题数目、排名在第二部分我们已经完成,所以要求的只有:growth_rate \rank_delta
growth_rate要求百分数保留一位小数,我们就将除法结果放大100倍,round取1位小数,concat将结果加上%
然后就是最后的rank_delta
本来我还想这道题也不难为什么通过率这么低:
然后提交代码发现确实有问题:
这是啥呢?
就是说这窗口函数排序出来的排名相减可能会出现负数也就是2021比2020排名高了,但是上边计算rate也可能出现负数为什么没报错呢?
如果两个数有一个是UNSIGNED INT类型,他们相减的结果也是UNSIGNED INT类型,而如果这个结果是负数:
在MySQL 5.5.5之前,结果变成了最大的整数(18446744073709551615)
从MySQL 5.5.5开始,这种情况会返回一个错误:BIGINT UNSIGNED value is out of range
窗口函数排名都是UNSIGNED INT类型
所以我们强制转换
CAST(t21.ranking AS SIGNED) - CAST(t20.ranking AS SIGNED) rank_delta
最后排序、完事。