题外笔记

在解题过程中也发现一个细节问题,不难,但是对我个人很有帮助,大家也可以在日后额外留意一下:

若在SELECT子句中为特定字段创建了别名(alias),同时还需要对该字段再SELECT并且进行操作,请不要在第二次(或任意一次)SELECT中使用该字段的别名——这么说很拗口晦涩,直接上示例:

# 只是一个sample
SELECT exam_id,
       DATE_FORMAT(start_time, '%Y%m') AS start_month,
       
       COUNT(start_time) AS month_cnt,   # 以此行为示例
       month_cnt + 1                      # 直接使用别名"month_cnt"来进行创建新的计算字段
       
FROM exam_record
GROUP BY exam_id, DATE_FORMAT(start_time, '%Y%m')

上述操作会得到报错:SQL_ERROR_INFO: "Unknown column 'month_cnt' in 'field list'"——第六行找不到字段“month_cnt”,要使其可行,只能够完完整整再写一次month_cnt原本的代码,即

SELECT exam_id,
       DATE_FORMAT(start_time, '%Y%m') AS start_month,
       
       COUNT(start_time) AS month_cnt,
       COUNT(start_time) + 1
       
FROM exam_record
GROUP BY exam_id, DATE_FORMAT(start_time, '%Y%m')

正式解题思路

本题主要考核使用开窗函数来计算累计数值

我一开始的思路也比较常规:

1)首先是查询出每一个科目在每一个月份的作答次数(注意:只要字段start_time存在,无论最终有没有提交——即,submit_time是否为NULL——都无关紧要;因此直接对start_time进行COUNT())

SELECT exam_id,
       DATE_FORMAT(start_time, '%Y%m') AS start_month,
       COUNT(start_time) AS month_cnt
FROM exam_record
GROUP BY exam_id, DATE_FORMAT(start_time, '%Y%m') #这里没有使用别名start_month而是完整代码

2)基于上述结果表,再进行一次聚合窗口函数,记住,在这里是分考试(或考试id)且by月份去计算累计值,因此必须是PARTITION BY 和 ORDER BY 一同使用:

SELECT t.exam_id, t.start_month, t.month_cnt,
       SUM(t.month_cnt) OVER (PARTITION BY t.exam_id ORDER BY t.start_month ASC) AS cum_exam_cnt
FROM
(
    SELECT exam_id,
           DATE_FORMAT(start_time, '%Y%m') AS start_month,
           COUNT(start_time) AS month_cnt
    FROM exam_record
    GROUP BY exam_id, DATE_FORMAT(start_time, '%Y%m')
) AS t;

但是,又发现了更加便捷的解题方法,有以下思路:

1. 首先是优化我的原答案——减少嵌套层数/子查询层数

方法很简单,但是我认为也很精妙,很需要熟练度,参考@盐咸咸的答案,能够发现,“SUM(t.month_cnt)”其实就是对子查询中的“COUNT(start_time) AS month_cnt”进行加和SUM。

因此,可以优化我的最终答案——直接使用SUM(COUNT(start_time)),在第一层子查询中就直接创建一个新的计算字段cum_exam_cnt,直接上代码:

SELECT exam_id,
       DATE_FORMAT(start_time, '%Y%m') AS start_month,
       COUNT(start_time) AS month_cnt,
       
       # 直接对原来的month_cnt进行嵌套,可以简单理解为,对month_cnt这个字段进行操作
       SUM(COUNT(start_time)) OVER (PARTITION BY exam_id ORDER BY DATE_FORMAT(start_time, '%Y%m') ASC) AS cum_exam_cnt
       
FROM exam_record
GROUP BY exam_id, DATE_FORMAT(start_time, '%Y%m')

该解题思路和另外一道题目“每类试卷得分前3名”(链接)有异曲同工之妙。

2. 第二个解题思路更加清晰简洁,但是我认为很需要熟练度才能过一气呵成

待更新...

先上代码,随后再进行解析

SELECT DISTINCT 
    exam_id,
    DATE_FORMAT(start_time,'%Y%m') AS start_month,
    COUNT(start_time) OVER(PARTITION BY exam_id,DATE_FORMAT(start_time,'%Y%m')) AS month_cnt,#每份试卷每月作答数
    COUNT(start_time) OVER(PARTITION BY exam_id ORDER BY DATE_FORMAT(start_time,'%Y%m')) AS cum_exam_cnt#截止当月的作答总数
FROM exam_record
ORDER BY exam_id,start_month

不得不说,真的牛