SQL常见面试题(四)

专用窗口函数

MySQL 8.0 版本引入了窗口函数的支持,下面是 MySQL 中常见的窗口函数及其用法:

ROW_NUMBER(): 为查询结果集中的每一行分配一个唯一的整数值。

SELECT col1, col2, ROW_NUMBER() OVER (ORDER BY col1) AS row_num
FROM table;

RANK(): 计算每一行在排序结果中的排名。

SELECT col1, col2, RANK() OVER (ORDER BY col1 DESC) AS ranking
FROM table;

DENSE_RANK(): 计算每一行在排序结果中的排名,保留相同的排名。

SELECT col1, col2, DENSE_RANK() OVER (ORDER BY col1 DESC) AS ranking
FROM table;

NTILE(n): 将结果分成 n 个基本均匀的桶,并为每个桶分配一个标识号。

SELECT col1, col2, NTILE(4) OVER (ORDER BY col1) AS bucket
FROM table;

SUM(), AVG(),COUNT(), MIN(), MAX(): 这些聚合函数也可以与窗口函数结合使用,计算窗口内指定列的汇总、平均值、计数、最小值和最大值。

SELECT col1, col2, SUM(col1) OVER () AS sum_col
FROM table;

LEAD()LAG(): LEAD 函数用于获取当前行之后的某个偏移量的行的值,而 LAG 函数用于获取当前行之前的某个偏移量的行的值。

SELECT col1, col2, LEAD(col1, 1) OVER (ORDER BY col1) AS next_col1,
                 LAG(col1, 1) OVER (ORDER BY col1) AS prev_col1
FROM table;

FIRST_VALUE()LAST_VALUE(): FIRST_VALUE 函数用于获取窗口内指定列的第一个值,LAST_VALUE 函数用于获取窗口内指定列的最后一个值。

SELECT col1, col2, FIRST_VALUE(col2) OVER (PARTITION BY col1 ORDER BY col2) AS first_val,
                 LAST_VALUE(col2) OVER (PARTITION BY col1 ORDER BY col2) AS last_val
FROM table;

窗口函数通常需要配合 OVER 子句一起使用,用于定义窗口的大小、排序规则和分组方式。

每类试卷得分前三名

描述

现有试卷信息表 examination_infoexam_id 试卷 ID, tag 试卷类别, difficulty 试卷难度, duration 考试时长, release_time 发布时间):

id exam_id tag difficulty duration release_time
1 9001 SQL hard 60 2021-09-01 06:00:00
2 9002 SQL hard 60 2021-09-01 06:00:00
3 9003 算法 medium 80 2021-09-01 10:00:00

试卷作答记录表 exam_recorduid 用户 ID, exam_id 试卷 ID, start_time 开始作答时间, submit_time 交卷时间, score 得分):

id uid exam_id start_time submit_time score
1 1001 9001 2021-09-01 09:01:01 2021-09-01 09:31:00 78
2 1002 9001 2021-09-01 09:01:01 2021-09-01 09:31:00 81
3 1002 9002 2021-09-01 12:01:01 2021-09-01 12:31:01 81
4 1003 9001 2021-09-01 19:01:01 2021-09-01 19:40:01 86
5 1003 9002 2021-09-01 12:01:01 2021-09-01 12:31:51 89
6 1004 9001 2021-09-01 19:01:01 2021-09-01 19:30:01 85
7 1005 9003 2021-09-01 12:01:01 2021-09-01 12:31:02 85
8 1006 9003 2021-09-07 10:01:01 2021-09-07 10:21:01 84
9 1003 9003 2021-09-08 12:01:01 2021-09-08 12:11:01 40
10 1003 9002 2021-09-01 14:01:01 (NULL) (NULL)

找到每类试卷得分的前 3 名,如果两人最大分数相同,选择最小分数大者,如果还相同,选择 uid 大者。由示例数据结果输出如下:

tid uid ranking
SQL 1003 1
SQL 1004 2
SQL 1002 3
算法 1005 1
算法 1006 2
算法 1003 3

解释:有作答得分记录的试卷 tag 有 SQL 和算法,SQL 试卷用户 1001、1002、1003、1004 有作答得分,最高得分分别为 81、81、89、85,最低得分分别为 78、81、86、40,因此先按最高得分排名再按最低得分排名取前三为 1003、1004、1002。

答案

SELECT tag,
       UID,
       ranking
FROM
  (SELECT b.tag AS tag,
          a.uid AS UID,
          ROW_NUMBER() OVER (PARTITION BY b.tag
                             ORDER BY b.tag,
                                      max(a.score) DESC,
                                      min(a.score) DESC,
                                      a.uid DESC) AS ranking
   FROM exam_record a
   LEFT JOIN examination_info b ON a.exam_id = b.exam_id
   GROUP BY b.tag,
            a.uid) t
WHERE ranking <= 3

第二快/慢用时之差大于试卷时长一半的试卷(较难)

描述

现有试卷信息表 examination_infoexam_id 试卷 ID, tag 试卷类别, difficulty 试卷难度, duration 考试时长, release_time 发布时间):

id exam_id tag difficulty duration release_time
1 9001 SQL hard 60 2021-09-01 06:00:00
2 9002 C++ hard 60 2021-09-01 06:00:00
3 9003 算法 medium 80 2021-09-01 10:00:00

试卷作答记录表 exam_recorduid 用户 ID, exam_id 试卷 ID, start_time 开始作答时间, submit_time 交卷时间, score 得分):

id uid exam_id start_time submit_time score
1 1001 9001 2021-09-01 09:01:01 2021-09-01 09:51:01 78
2 1001 9002 2021-09-01 09:01:01 2021-09-01 09:31:00 81
3 1002 9002 2021-09-01 12:01:01 2021-09-01 12:31:01 81
4 1003 9001 2021-09-01 19:01:01 2021-09-01 19:59:01 86
5 1003 9002 2021-09-01 12:01:01 2021-09-01 12:31:51 89
6 1004 9002 2021-09-01 19:01:01 2021-09-01 19:30:01 85
7 1005 9001 2021-09-01 12:01:01 2021-09-01 12:31:02 85
8 1006 9001 2021-09-07 10:01:01 2021-09-07 10:21:01 84
9 1003 9001 2021-09-08 12:01:01 2021-09-08 12:11:01 40
10 1003 9002 2021-09-01 14:01:01 (NULL) (NULL)
11 1005 9001 2021-09-01 14:01:01 (NULL) (NULL)
12 1003 9003 2021-09-08 15:01:01 (NULL) (NULL)

找到第二快和第二慢用时之差大于试卷时长的一半的试卷信息,按试卷 ID 降序排序。由示例数据结果输出如下:

exam_id duration release_time
9001 60 2021-09-01 06:00:00

解释:试卷 9001 被作答用时有 50 分钟、50 分钟、30 分 1 秒、11 分钟、10 分钟,第二快和第二慢用时之差为 50 分钟-11 分钟=39 分钟,试卷时长为 60 分钟,因此满足大于试卷时长一半的条件,输出试卷 ID、时长、发布时间。

思路:

第一步,找到每张试卷完成时间的顺序排名和倒序排名 也就是表 a;

第二步,与通过试卷信息表 b 建立内连接,并根据试卷 id 分组,利用having筛选排名为第二个数据,将秒转化为分钟并进行比较,最后再根据试卷 id 倒序排序就行

连续两次作答试卷的最大时间窗(较难)

描述

现有试卷作答记录表 exam_recorduid 用户 ID, exam_id 试卷 ID, start_time 开始作答时间, submit_time 交卷时间, score 得分):

id uid exam_id start_time submit_time score
1 1006 9003 2021-09-07 10:01:01 2021-09-07 10:21:02 84
2 1006 9001 2021-09-01 12:11:01 2021-09-01 12:31:01 89
3 1006 9002 2021-09-06 10:01:01 2021-09-06 10:21:01 81
4 1005 9002 2021-09-05 10:01:01 2021-09-05 10:21:01 81
5 1005 9001 2021-09-05 10:31:01 2021-09-05 10:51:01 81

请计算在 2021 年至少有两天作答过试卷的人中,计算该年连续两次作答试卷的最大时间窗 days_window,那么根据该年的历史规律他在 days_window 天里平均会做多少套试卷,按最大时间窗和平均做答试卷套数倒序排序。由示例数据结果输出如下:

uid days_window avg_exam_cnt
1006 6 2.57

解释:用户 1006 分别在 20210901、20210906、20210907 作答过 3 次试卷,连续两次作答最大时间窗为 6 天(1 号到 6 号),他 1 号到 7 号这 7 天里共做了 3 张试卷,平均每天 3/7=0.428571 张,那么 6 天里平均会做 0.428571*6=2.57 张试卷(保留两位小数);用户 1005 在 20210905 做了两张试卷,但是只有一天的作答记录,过滤掉。

思路:

上面这个解释中提示要对作答记录去重,千万别被骗了,不要去重!去重就通不过测试用例。注意限制时间是 2021 年;

而且要注意时间差要+1 天;还要注意==没交卷也算在内==!!!! (反正感觉这题描述不清,出的不是很好)

答案

SELECT UID,
       max(datediff(next_time, start_time)) + 1 AS days_window,
       round(count(start_time)/(datediff(max(start_time), min(start_time))+ 1) * (max(datediff(next_time, start_time))+ 1), 2) AS avg_exam_cnt
FROM
  (SELECT UID,
          start_time,
          lead(start_time, 1) OVER (PARTITION BY UID
                                    ORDER BY start_time) AS next_time
   FROM exam_record
   WHERE YEAR (start_time) = '2021' ) a
GROUP BY UID
HAVING count(DISTINCT date(start_time)) > 1
ORDER BY days_window DESC,
         avg_exam_cnt DESC

[近三个月未完成为 0 的用户完成情况](#近三个月未完成为 0 的用户完成情况)

描述

现有试卷作答记录表 exam_recorduid:用户 ID, exam_id:试卷 ID, start_time:开始作答时间, submit_time:交卷时间,为空的话则代表未完成, score:得分):

id uid exam_id start_time submit_time score
1 1006 9003 2021-09-06 10:01:01 2021-09-06 10:21:02 84
2 1006 9001 2021-08-02 12:11:01 2021-08-02 12:31:01 89
3 1006 9002 2021-06-06 10:01:01 2021-06-06 10:21:01 81
4 1006 9002 2021-05-06 10:01:01 2021-05-06 10:21:01 81
5 1006 9001 2021-05-01 12:01:01 (NULL) (NULL)
6 1001 9001 2021-09-05 10:31:01 2021-09-05 10:51:01 81
7 1001 9003 2021-08-01 09:01:01 2021-08-01 09:51:11 78
8 1001 9002 2021-07-01 09:01:01 2021-07-01 09:31:00 81
9 1001 9002 2021-07-01 12:01:01 2021-07-01 12:31:01 81
10 1001 9002 2021-07-01 12:01:01 (NULL) (NULL)

找到每个人近三个有试卷作答记录的月份中没有试卷是未完成状态的用户的试卷作答完成数,按试卷完成数和用户 ID 降序排名。由示例数据结果输出如下:

uid exam_complete_cnt
1006 3

解释:用户 1006 近三个有作答试卷的月份为 202109、202108、202106,作答试卷数为 3,全部完成;用户 1001 近三个有作答试卷的月份为 202109、202108、202107,作答试卷数为 5,完成试卷数为 4,因为有未完成试卷,故过滤掉。

思路:

  1. 找到每个人近三个有试卷作答记录的月份中没有试卷是未完成状态的用户的试卷作答完成数首先看这句话,肯定要先根据人进行分组
  2. 最近三个月,可以采用连续重复排名,倒序排列,排名<=3
  3. 统计作答数
  4. 拼装剩余条件
  5. 排序

答案

SELECT UID,
       count(score) exam_complete_cnt
FROM
  (SELECT *, DENSE_RANK() OVER (PARTITION BY UID
                             ORDER BY date_format(start_time, '%Y%m') DESC) dr
   FROM exam_record) t1
WHERE dr <= 3
GROUP BY UID
HAVING count(dr)= count(score)
ORDER BY exam_complete_cnt DESC,
         UID DESC

[未完成率较高的 50%用户近三个月答卷情况(困难)](#未完成率较高的 50%用户近三个月答卷情况(困难))

描述

现有用户信息表 user_infouid 用户 ID,nick_name 昵称, achievement 成就值, level 等级, job 职业方向, register_time 注册时间):

id uid nick_name achievement level job register_time
1 1001 牛客 1 号 3200 7 算法 2020-01-01 10:00:00
2 1002 牛客 2 号 2500 6 算法 2020-01-01 10:00:00
3 1003 牛客 3 号 ♂ 2200 5 算法 2020-01-01 10:00:00

试卷信息表 examination_infoexam_id 试卷 ID, tag 试卷类别, difficulty 试卷难度, duration 考试时长, release_time 发布时间):

id exam_id tag difficulty duration release_time
1 9001 SQL hard 60 2020-01-01 10:00:00
2 9002 SQL hard 80 2020-01-01 10:00:00
3 9003 算法 hard 80 2020-01-01 10:00:00
4 9004 PYTHON medium 70 2020-01-01 10:00:00

试卷作答记录表 exam_recorduid 用户 ID, exam_id 试卷 ID, start_time 开始作答时间, submit_time 交卷时间, score 得分):

id uid exam_id start_time submit_time score
1 1001 9001 2020-01-01 09:01:01 2020-01-01 09:21:59 90
15 1002 9001 2020-01-01 18:01:01 2020-01-01 18:59:02 90
13 1001 9001 2020-01-02 10:01:01 2020-01-02 10:31:01 89
2 1002 9001 2020-01-20 10:01:01
3 1002 9001 2020-02-01 12:11:01
5 1001 9001 2020-03-01 12:01:01
6 1002 9001 2020-03-01 12:01:01 2020-03-01 12:41:01 90
4 1003 9001 2020-03-01 19:01:01
7 1002 9001 2020-05-02 19:01:01 2020-05-02 19:32:00 90
14 1001 9002 2020-01-01 12:11:01
8 1001 9002 2020-01-02 19:01:01 2020-01-02 19:59:01 69
9 1001 9002 2020-02-02 12:01:01 2020-02-02 12:20:01 99
10 1002 9002 2020-02-02 12:01:01
11 1002 9002 2020-02-02 12:01:01 2020-02-02 12:43:01 81
12 1002 9002 2020-03-02 12:11:01
17 1001 9002 2020-05-05 18:01:01
16 1002 9003 2020-05-06 12:01:01

请统计 SQL 试卷上未完成率较高的 50%用户中,6 级和 7 级用户在有试卷作答记录的近三个月中,每个月的答卷数目和完成数目。按用户 ID、月份升序排序。

由示例数据结果输出如下:

uid start_month total_cnt complete_cnt
1002 202002 3 1
1002 202003 2 1
1002 202005 2 1

解释:各个用户对 SQL 试卷的未完成数、作答总数、未完成率如下:

uid incomplete_cnt total_cnt incomplete_rate
1001 3 7 0.4286
1002 4 8 0.5000
1003 1 1 1.0000

1001、1002、1003 分别排在 1.0、0.5、0.0 的位置,因此较高的 50%用户(排位<=0.5)为 1002、1003;

1003 不是 6 级或 7 级;

有试卷作答记录的近三个月为 202005、202003、202002;

这三个月里 1002 的作答题数分别为 3、2、2,完成数目分别为 1、1、1。

思路:

注意点:这题注意求的是所有的答题次数和完成次数,而 sql 类别的试卷是限制未完成率排名,6, 7 级用户限制的是做题记录。

先求出未完成率的排名

SELECT UID,
       count(submit_time IS NULL
             OR NULL)/ count(start_time) AS num,
       PERCENT_RANK() OVER (
                            ORDER BY count(submit_time IS NULL
                                           OR NULL)/ count(start_time)) AS ranking
FROM exam_record
LEFT JOIN examination_info USING (exam_id)
WHERE tag = 'SQL'
GROUP BY UID

再求出最近三个月的练习记录

SELECT UID,
       date_format(start_time, '%Y%m') AS month_d,
       submit_time,
       exam_id,
       dense_rank() OVER (PARTITION BY UID
                          ORDER BY date_format(start_time, '%Y%m') DESC) AS ranking
FROM exam_record
LEFT JOIN user_info USING (UID)
WHERE LEVEL IN (6,7)

[试卷完成数同比 2020 年的增长率及排名变化(困难)](#试卷完成数同比 2020 年的增长率及排名变化(困难))

描述

现有试卷信息表 examination_infoexam_id 试卷 ID, tag 试卷类别, difficulty 试卷难度, duration 考试时长, release_time 发布时间):

id exam_id tag difficulty duration release_time
1 9001 SQL hard 60 2021-01-01 10:00:00
2 9002 C++ hard 80 2021-01-01 10:00:00
3 9003 算法 hard 80 2021-01-01 10:00:00
4 9004 PYTHON medium 70 2021-01-01 10:00:00

试卷作答记录表 exam_recorduid 用户 ID, exam_id 试卷 ID, start_time 开始作答时间, submit_time 交卷时间, score 得分):

请计算 2021 年上半年各类试卷的做完次数相比 2020 年上半年同期的增长率(百分比格式,保留 1 位小数),以及做完次数排名变化,按增长率和 21 年排名降序输出。

由示例数据结果输出如下:

tag exam_cnt_20 exam_cnt_21 growth_rate exam_cnt_rank_20 exam_cnt_rank_21 rank_delta
SQL 3 2 -33.3% 1 2 1

解释:2020 年上半年有 3 个 tag 有作答完成的记录,分别是 C++、SQL、PYTHON,它们被做完的次数分别是 3、3、2,做完次数排名为 1、1(并列)、3;

2021 年上半年有 2 个 tag 有作答完成的记录,分别是算法、SQL,它们被做完的次数分别是 3、2,做完次数排名为 1、2;具体如下:

tag start_year exam_cnt exam_cnt_rank
C++ 2020 3 1
SQL 2020 3 1
PYTHON 2020 2 3
算法 2021 3 1
SQL 2021 2 2

因此能输出同比结果的 tag 只有 SQL,从 2020 到 2021 年,做完次数 3=>2,减少 33.3%(保留 1 位小数);排名 1=>2,后退 1 名。

思路:

本题难点在于长整型的数据类型要求不能有负号产生,用 cast 函数转换数据类型为 signed。

以及用到的增长率计算公式:(exam_cnt_21-exam_cnt_20)/exam_cnt_20

做完次数排名变化(2021 年和 2020 年比排名升了或者降了多少)

计算公式:exam_cnt_rank_21 - exam_cnt_rank_20

在 MySQL 中,CAST() 函数用于将一个表达式的数据类型转换为另一个数据类型。它的基本语法如下:

CAST(expression AS data_type)

-- 将一个字符串转换成整数
SELECT CAST('123' AS INT);

示例就不一一举例了,这个函数很简单

答案

聚合窗口函数

[对试卷得分做 min-max 归一化](#对试卷得分做 min-max 归一化)

描述

现有试卷信息表 examination_infoexam_id 试卷 ID, tag 试卷类别, difficulty 试卷难度, duration 考试时长, release_time 发布时间):

id exam_id tag difficulty duration release_time
1 9001 SQL hard 60 2020-01-01 10:00:00
2 9002 C++ hard 80 2020-01-01 10:00:00
3 9003 算法 hard 80 2020-01-01 10:00:00
4 9004 PYTHON medium 70 2020-01-01 10:00:00

试卷作答记录表 exam_recorduid 用户 ID, exam_id 试卷 ID, start_time 开始作答时间, submit_time 交卷时间, score 得分):

id uid exam_id start_time submit_time score
6 1003 9001 2020-01-02 12:01:01 2020-01-02 12:31:01 68
9 1001 9001 2020-01-02 10:01:01 2020-01-02 10:31:01 89
1 1001 9001 2020-01-01 09:01:01 2020-01-01 09:21:59 90
12 1002 9002 2021-05-05 18:01:01 (NULL) (NULL)
3 1004 9002 2020-01-01 12:01:01 2020-01-01 12:11:01 60
2 1003 9002 2020-01-01 19:01:01 2020-01-01 19:30:01 75
7 1001 9002 2020-01-02 12:01:01 2020-01-02 12:43:01 81
10 1002 9002 2020-01-01 12:11:01 2020-01-01 12:31:01 83
4 1003 9002 2020-01-01 12:01:01 2020-01-01 12:41:01 90
5 1002 9002 2020-01-02 19:01:01 2020-01-02 19:32:00 90
11 1002 9004 2021-09-06 12:01:01 (NULL) (NULL)
8 1001 9005 2020-01-02 12:11:01 (NULL) (NULL)

在物理学及统计学数据计算时,有个概念叫 min-max 标准化,也被称为离差标准化,是对原始数据的线性变换,使结果值映射到[0 - 1]之间。

转换函数为:

请你将用户作答高难度试卷的得分在每份试卷作答记录内执行 min-max 归一化后缩放到[0,100]区间,并输出用户 ID、试卷 ID、归一化后分数平均值;最后按照试卷 ID 升序、归一化分数降序输出。(注:得分区间默认为[0,100],如果某个试卷作答记录中只有一个得分,那么无需使用公式,归一化并缩放后分数仍为原分数)。

由示例数据结果输出如下:

uid exam_id avg_new_score
1001 9001 98
1003 9001 0
1002 9002 88
1003 9002 75
1001 9002 70
1004 9002 0

解释:高难度试卷有 9001、9002、9003;

作答了 9001 的记录有 3 条,分数分别为 68、89、90,按给定公式归一化后分数为:0、95、100,而后两个得分都是用户 1001 作答的,因此用户 1001 对试卷 9001 的新得分为(95+100)/2≈98(只保留整数部分),用户 1003 对于试卷 9001 的新得分为 0。最后结果按照试卷 ID 升序、归一化分数降序输出。

思路:

注意点:

  1. 将高难度的试卷,按每类试卷的得分,利用 max/min (col) over()窗口函数求得各组内最大最小值,然后进行归一化公式计算,缩放区间为[0,100],即 min_max*100
  2. 若某类试卷只有一个得分,则无需使用归一化公式,因只有一个分 max_score=min_score,score,公式后结果可能会变成 0。
  3. 最后结果按 uid、exam_id 分组求归一化后均值,score 为 NULL 的要过滤掉。

最后就是仔细看上面公式 (说实话,这题看起来就很绕)

答案

每份试卷每月作答数和截止当月的作答总数

描述:

现有试卷作答记录表 exam_record(uid 用户 ID, exam_id 试卷 ID, start_time 开始作答时间, submit_time 交卷时间, score 得分):

id uid exam_id start_time submit_time score
1 1001 9001 2020-01-01 09:01:01 2020-01-01 09:21:59 90
2 1002 9001 2020-01-20 10:01:01 2020-01-20 10:10:01 89
3 1002 9001 2020-02-01 12:11:01 2020-02-01 12:31:01 83
4 1003 9001 2020-03-01 19:01:01 2020-03-01 19:30:01 75
5 1004 9001 2020-03-01 12:01:01 2020-03-01 12:11:01 60
6 1003 9001 2020-03-01 12:01:01 2020-03-01 12:41:01 90
7 1002 9001 2020-05-02 19:01:01 2020-05-02 19:32:00 90
8 1001 9002 2020-01-02 19:01:01 2020-01-02 19:59:01 69
9 1004 9002 2020-02-02 12:01:01 2020-02-02 12:20:01 99
10 1003 9002 2020-02-02 12:01:01 2020-02-02 12:31:01 68
11 1001 9002 2020-02-02 12:01:01 2020-02-02 12:43:01 81
12 1001 9002 2020-03-02 12:11:01 (NULL) (NULL)

请输出每份试卷每月作答数和截止当月的作答总数。

由示例数据结果输出如下:

exam_id start_month month_cnt cum_exam_cnt
9001 202001 2 2
9001 202002 1 3
9001 202003 3 6
9001 202005 1 7
9002 202001 1 1
9002 202002 3 4
9002 202003 1 5

解释:试卷 9001 在 202001、202002、202003、202005 共 4 个月有被作答记录,每个月被作答数分别为 2、1、3、1,截止当月累积作答总数为 2、3、6、7。

思路:

这题就两个关键点:统计截止当月的作答总数、输出每份试卷每月作答数和截止当月的作答总数

这个是关键**sum(count(*)) over(partition by exam_id order by date_format(start_time,'%Y%m'))**

答案

SELECT exam_id,
       date_format(start_time, '%Y%m') AS start_month,
       count(*) AS month_cnt,
       sum(count(*)) OVER (PARTITION BY exam_id
                           ORDER BY date_format(start_time, '%Y%m')) AS cum_exam_cnt
FROM exam_record
GROUP BY exam_id,
         start_month

每月及截止当月的答题情况(较难)

描述 :现有试卷作答记录表 exam_recorduid 用户 ID, exam_id 试卷 ID, start_time 开始作答时间, submit_time 交卷时间, score 得分):

id uid exam_id start_time submit_time score
1 1001 9001 2020-01-01 09:01:01 2020-01-01 09:21:59 90
2 1002 9001 2020-01-20 10:01:01 2020-01-20 10:10:01 89
3 1002 9001 2020-02-01 12:11:01 2020-02-01 12:31:01 83
4 1003 9001 2020-03-01 19:01:01 2020-03-01 19:30:01 75
5 1004 9001 2020-03-01 12:01:01 2020-03-01 12:11:01 60
6 1003 9001 2020-03-01 12:01:01 2020-03-01 12:41:01 90
7 1002 9001 2020-05-02 19:01:01 2020-05-02 19:32:00 90
8 1001 9002 2020-01-02 19:01:01 2020-01-02 19:59:01 69
9 1004 9002 2020-02-02 12:01:01 2020-02-02 12:20:01 99
10 1003 9002 2020-02-02 12:01:01 2020-02-02 12:31:01 68
11 1001 9002 2020-01-02 19:01:01 2020-02-02 12:43:01 81
12 1001 9002 2020-03-02 12:11:01 (NULL) (NULL)

请输出自从有用户作答记录以来,每月的试卷作答记录中月活用户数、新增用户数、截止当月的单月最大新增用户数、截止当月的累积用户数。结果按月份升序输出。

由示例数据结果输出如下:

start_month mau month_add_uv max_month_add_uv cum_sum_uv
202001 2 2 2 2
202002 4 2 2 4
202003 3 0 2 4
202005 1 0 2 4
month 1001 1002 1003 1004
202001 1 1
202002 1 1 1 1
202003 1 1 1
202005 1

由上述矩阵可以看出,2020 年 1 月有 2 个用户活跃(mau=2),当月新增用户数为 2;

2020 年 2 月有 4 个用户活跃,当月新增用户数为 2,最大单月新增用户数为 2,当前累积用户数为 4。

思路:

难点:

1.如何求每月新增用户

2.截至当月的答题情况

大致流程:

(1)统计每个人的首次登陆月份 min()

(2)统计每月的月活和新增用户数:先得到每个人的首次登陆月份,再对首次登陆月份分组求和是该月份的新增人数

(3)统计截止当月的单月最大新增用户数、截止当月的累积用户数 ,最终按照按月份升序输出

答案

-- 截止当月的单月最大新增用户数、截止当月的累积用户数,按月份升序输出
SELECT
	start_month,
	mau,
	month_add_uv,
	max( month_add_uv ) over ( ORDER BY start_month ),
	sum( month_add_uv ) over ( ORDER BY start_month )
FROM
	(
	-- 统计每月的月活和新增用户数
	SELECT
		date_format( a.start_time, '%Y%m' ) AS start_month,
		count( DISTINCT a.uid ) AS mau,
		count( DISTINCT b.uid ) AS month_add_uv
	FROM
		exam_record a
		LEFT JOIN (
         -- 统计每个人的首次登陆月份
		SELECT uid, min( date_format( start_time, '%Y%m' )) AS first_month FROM exam_record GROUP BY uid ) b ON date_format( a.start_time, '%Y%m' ) = b.first_month
	GROUP BY
		start_month
	) main
ORDER BY
	start_month
相关推荐
o(╥﹏╥)8 分钟前
linux(ubuntu )卡死怎么强制重启
linux·数据库·ubuntu·系统安全
阿里嘎多学长23 分钟前
docker怎么部署高斯数据库
运维·数据库·docker·容器
Yuan_o_25 分钟前
Linux 基本使用和程序部署
java·linux·运维·服务器·数据库·后端
Sunyanhui130 分钟前
牛客网 SQL36查找后排序
数据库·sql·mysql
老王笔记43 分钟前
MHA binlog server
数据库·mysql
lovelin+v175030409661 小时前
安全性升级:API接口在零信任架构下的安全防护策略
大数据·数据库·人工智能·爬虫·数据分析
DT辰白2 小时前
基于Redis的网关鉴权方案与性能优化
数据库·redis·缓存
2401_871213302 小时前
mysql高阶语句
数据库·mysql
Mitch3112 小时前
【漏洞复现】CVE-2021-45788 SQL Injection
sql·web安全·docker·prometheus·metersphere