暂无图片
暂无图片
4
暂无图片
暂无图片
暂无图片

交卷!阿里云瑶池数据库SQL挑战赛!

原创 严少安 2023-06-15
769

SQL 挑战赛

本次墨天轮社区的 SQL 挑战赛已接近尾声(before 6/18),趁着活动尚未结束抓紧时间上车。

knowledge_1685094554310.jpg

总共三道题目,需要先前往阿里云领取资源 – 云数据库 RDS MySQL Serverless

这里我选择的是 阿里云 RDS MySQL Serverless (MySQL 8)

图片.png
图片.png

导入数据集

在墨天轮文档下载数据集。

并导入阿里云 RDS,这里 DMS 提供了数据文件导入功能,可快速导入 csv 格式的数据文件。

图片.png

赛题 1: 找出各项考试中的佼佼者

一个学生可以参加任意考试,不限次数。

现在我们关注的是每门考试有哪些顶尖的学生。一门考试的 顶尖学生 是指一个学生的分数在参加该考试的 不同 学生中 得分排名前三 。

编写一个SQL查询,找出每个考试中 得分最高的的考生 。

若同一个考生有多条考试记录,则取最高分。

如果存在并列,将并列的考生都列举出来,直到列举的考生达到或超过三人。

以 每门考试考试分数从高到低的顺序 返回结果表

使用窗口函数 ROW_NUMBER() 来为每个学生的得分排名,使用 PARTITION BY 将学生的得分排名限制为每个考试内部,然后使用 ORDER BY 按照得分从高到低进行排序。
使用 COUNT(*) OVER 窗口函数,用于计算每个考试中每个得分的出现次数。
使用 MAX(tt.score) OVER 窗口函数,用于获取每个考生在该考试中的最高分数。

如果使用 DENSE_RANK 则会出现75条结果,不符合预期,如下结果:

| Test 1   | Student 32 |   400 |
| Test 1   | Student 80 |   400 |
| Test 1   | Student 19 |   400 |
| Test 1   | Student 24 |   400 |
| Test 1   | Student 36 |   400 |
| Test 1   | Student 61 |   400 |
| Test 1   | Student 3  |   400 |
| Test 1   | Student 8  |   399 |
| Test 1   | Student 29 |   398 |
| Test 1   | Student 26 |   398 |

题目要求:

如果存在并列,将并列的考生都列举出来,直到列举的考生达到或超过三人。

所以 75 条记录的结果是错误的,
这里应该使用 RANK,得到的结果集为 44 条记录。

  • 方案一(118ms)
SELECT t.name AS testName, t2.StuName, t2.score FROM ( SELECT t1.testId, t1.StuName, t1.score, t1.rn, t1.cnt , t1.max_score, RANK() OVER (PARTITION BY t1.testId ORDER BY t1.score DESC) AS rank_num FROM ( SELECT tt.testId, tt.studentId, tt.score, s.name AS StuName , ROW_NUMBER() OVER (PARTITION BY tt.testId ORDER BY tt.score DESC) AS rn , COUNT(*) OVER (PARTITION BY tt.studentId, tt.score ) AS cnt , MAX(tt.score) OVER (PARTITION BY tt.studentId, tt.testId ) AS max_score FROM TestAttempt tt JOIN Student s ON tt.studentId = s.id ) t1 WHERE t1.score = t1.max_score ORDER BY t1.testId, t1.score DESC ) t2 JOIN Test t ON t2.testId = t.id WHERE t2.rank_num <= 3;
  • 方案二(71ms)
WITH t1 AS ( SELECT tt.testId, tt.studentId, tt.score , MAX(tt.score) OVER (PARTITION BY tt.studentId, tt.testId ) AS max_score , RANK() OVER (PARTITION BY tt.testId ORDER BY tt.score DESC) AS rank_num FROM TestAttempt tt ), t2 AS ( SELECT t1.testId, t1.studentId, t1.score FROM t1 WHERE t1.score = t1.max_score AND t1.rank_num <= 3 ) SELECT t.name AS testName, s.name AS StuName, t2.score FROM t2 JOIN Test t ON t2.testId = t.id JOIN Student s ON t2.studentId = s.id ;

截图

  • 方案一(118ms)
    20230615_103526.png

  • 方案二(71ms)
    图片.png

赛题 2: 游戏游玩情况

编写一个 SQL 查询,报告在首次游玩后的一周内至少再有一次游玩的玩家的比例,也就是注册首周内至少有两次登录的玩家占总玩家的比例,四舍五入到小数点后两位。

这张表 Activity 包含四个字段,每一行是一个玩家 (player_id) 在指定日期 (event_date) 的游玩记录,包含了设备信息 (device_id),以及总共玩了多少款游戏 (games_played)。这套题目的关键在于如何利用 (player_id, event_date) 这两个字段计算出题目所需的结果。

首次游玩后的一周内至少再有一次游玩的玩家

需要先找到有哪些玩家在注册首周内再次登录,假设注册日期为 first_played_date ,则在 [first_played_date, first_played_date + 6 day] 的区间内还有游玩记录。这里用到了窗口函数 over (partition by .. order by .. frame) 的高级语法。具体参考 Window Function Frame Specification

player_id 分组,确认玩家首次游玩时间,即 event_date 的最小值。

按玩家分组,按游玩时间排序,并标记序号,每位玩家只取前两天游玩记录。

依据此中间结果,计算每位玩家的第二条记录的日期是否在首次游玩日期的一周内。

依据上面的统计结果,即可计算出题目要求的玩家比例。

  • 方法一
WITH t AS (
    SELECT player_id, event_date, 
	  MIN(event_date) OVER (PARTITION BY player_id ) AS first_played_date,
      ROW_NUMBER() OVER (PARTITION BY player_id ORDER BY event_date) AS rn
    FROM Activity
  ), 
  t2 AS (
    SELECT t.player_id, t.event_date, t.first_played_date, t.rn,
	  COUNT(t.rn) OVER (PARTITION BY t.first_played_date ORDER BY t.event_date) AS cnt
    FROM t
    WHERE t.rn <= 2 
	     AND t.event_date BETWEEN t.first_played_date AND DATE_ADD(t.first_played_date, INTERVAL 6 DAY)
       AND t.event_date != t.first_played_date
  )
SELECT ROUND(COUNT(t2.player_id) / (SELECT COUNT(DISTINCT player_id) FROM Activity), 2) AS pct
FROM t2
WHERE t2.cnt > 0
;
  • 方法二:
SELECT ROUND(COUNT(t.player_id) / (
    SELECT COUNT(DISTINCT player_id)
    FROM Activity
  ), 2) AS pct
FROM (
  SELECT player_id, event_date, MIN(event_date) OVER (PARTITION BY player_id ) AS 1st_date
    , nth_value(event_date, 2) OVER (PARTITION BY player_id ORDER BY event_date) AS 2nd_date
    , ROW_NUMBER() OVER (PARTITION BY player_id ORDER BY event_date) AS rn
  FROM Activity
) t
WHERE t.2nd_date IS NOT NULL
  AND t.rn = 2
  AND datediff(2nd_date, 1st_date) < 7;

共计14个玩家,在首次游玩后的一周内至少再有一次游玩的记录,百分比为 0.01 。

截图

  • 方法一(80ms)
    图片.png

  • 方法二(116ms)
    图片.png

RDS MySQL bug?

在测试过程中,因为用到了高级语法 RANGE BETWEEN CURRENT ROW AND INTERVAL 6 DAY FOLLOWING 导致 RDS 遇到了展示不出结果的情况,并且返回结果:【影响行数为-1】,在本地 MySQL 8.0.28 环境中,这个 SQL 是没有问题的。

WITH t AS ( SELECT player_id, event_date, MIN(event_date) OVER (PARTITION BY player_id ) AS first_played_date, ROW_NUMBER() OVER (PARTITION BY player_id ORDER BY event_date) AS rn FROM Activity ), t2 AS ( SELECT t.player_id, t.event_date, t.first_played_date, t.rn, COUNT(t.rn) OVER (PARTITION BY t.first_played_date ORDER BY t.event_date RANGE BETWEEN CURRENT ROW AND INTERVAL 6 DAY FOLLOWING) AS cnt FROM t WHERE t.rn <= 2 ) SELECT ROUND(COUNT(t2.player_id) / (SELECT COUNT(DISTINCT player_id) FROM Activity), 2) AS pct FROM t2 WHERE t2.cnt > 0 ;

20230615_133023.png

20230615_133153.png

赛题 3: 计算三角形面积

写一个SQL查询,计算每个三角形的面积,保留两位小数。

空间三角形的面积解法较多,比如用两条边的长度和夹角进行计算,或者海伦公式,或者Bretschneider公式,或者高斯斯特林公式。
这里用到的是向量计算法。

假设三角形的三个顶点坐标分别为 A(x1, y1, z1)、B(x2, y2, z2) 和 C(x3, y3, z3)。则该三角形的面积为:

微信图片_20230614205916.png

经过初步验算,得到如下公式。

微信图片_20230615010350.jpg

SELECT t.id, ROUND(0.5 * SQRT(pow(a, 2) + pow(b, 2) + pow(c, 2)), 2) AS area FROM ( SELECT triangle.id , (p2.y - p1.y) * (p3.z - p1.z) - (p3.y - p1.y) * (p2.z - p1.z) AS a , (p2.z - p1.z) * (p3.x - p1.x) - (p2.x - p1.x) * (p3.z - p1.z) AS b , (p2.x - p1.x) * (p3.y - p1.y) - (p3.x - p1.x) * (p2.y - p1.y) AS c FROM triangle JOIN point p1 ON pointid1 = p1.id JOIN point p2 ON pointid2 = p2.id JOIN point p3 ON pointid3 = p3.id ) t ORDER BY t.id;

这里用到了 MySQL 中的数学函数 Mathematical Functions
SQRT: 返回非负数X的平方根。如果X为NULL,则该函数返回NULL。
POW: 返回X的Y次方的值,如果X或Y为NULL则返回NULL。

截图

图片.png

总结

三道题目各有侧重,题目都很有趣,对于不常写 SQL 的人来说很有难度,对于 MySQL 或者 AliSQL 不熟的人来说,也有一定的挑战。

参考

最后修改时间:2023-06-16 09:48:20
「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

文章被以下合辑收录

评论