题意
- 统计出每个题目(question_id)和每份试卷(exam_id),被作答的人数(uv)和次数(pv)
- 分别按照“试卷”和“题目”的uv和pv降序 —— 根据示例答案可见,“试卷”的返还结果在上,随后再是“题目”的结果,并且,“试卷”和“题目”的排序是独立进行的,而非对最终结果一并排序
思路
1. 先算出每份试卷、每个题目对应的UV和PV。其中,UV是指答过题的唯一用户(即,DISTINCT uid),PV则是用户所有的答题次数(无论最终是否有提交,只要发生过答题,都计入一次,通过COUNT(uid)即可):
# 计算每份试卷的uv和pv
SELECT exam_id AS tid, COUNT(DISTINCT uid) AS uv, COUNT(uid) AS pv
FROM exam_record
GROUP BY exam_id
# 计算每道题目的uv和pv
SELECT question_id AS tid, COUNT(DISTINCT uid) AS uv, COUNT(uid) AS pv
FROM practice_record
GROUP BY question_id
2. 分别对“试卷”和“题目”的结果,按照uv、pv降序 —— 这一步之后会比较tricky,引出了SQL UNION中需要谨记的注意事项。先看对两者分别进行的排序语句:
# 对试卷进行排序
SELECT exam_id AS tid, COUNT(DISTINCT uid) AS uv, COUNT(uid) AS pv
FROM exam_record
GROUP BY exam_id
ORDER BY uv DESC, pv DESC
# 对题目进行排序
SELECT question_id AS tid, COUNT(DISTINCT uid) AS uv, COUNT(uid) AS pv
FROM practice_record
GROUP BY question_id
ORDER BY uv DESC, pv DESC
3. 一半同学会认为直接将两个排序结果UNION即可,但其实会发现过不了实际的案例:
# 错误代码示范
(SELECT exam_id AS tid, COUNT(DISTINCT uid) AS uv, COUNT(uid) AS pv
FROM exam_record
GROUP BY exam_id
ORDER BY uv DESC, pv DESC)
UNION
(SELECT question_id AS tid, COUNT(DISTINCT uid) AS uv, COUNT(uid) AS pv
FROM practice_record
GROUP BY question_id
ORDER BY uv DESC, pv DESC)
会发现在题目给予的其他案例里,final result(最终结果)并没有按照我们所想的一样,将“试卷”按照uv、pv降序的结果,以及“题目”按照uv、pv降序的结果拼接在一起,形成一个有序的最终结果。
这是因为,在MySQL 8.0中,只要我们使用了UNION关键字来合并结果,最终返还的final result一定是无序的(unordered,并不是指“乱序”的意思) —— 即是说,无论我们是否事先对“试卷”和“题目”单独进行了排序,只要我们直接对两个排序的结果进行UNION操作,最终呈现出来的结果还是会抵消原本的排序操作,不会按照我们原本既定的排序来显示结果。
(ps:即使最终结果看起来貌似还是按照tid呈现的,但实际只是一个巧合,我们所看到的最终结果就是一个无序的结果)
Mysql 8.0 使用手册中,关于UNION的原文(链接)如下:
Use of ORDER BY for individual SELECT statements implies nothing about the order in which the rows appear in the final result because UNION by default produces an **unordered ** set of rows.
4. 那么,我们要如何解决该问题呢?换句话说,我们是否能将两次排序的结果保留下来呢?
答案是肯定的,既然上面说到,不能直接对排序的结果进行UNION,那么,我们只需要对两次排序结果再嵌套一层SELECT,将两次结果保存下来,再将新的结果直接UNION即可 —— 既然UNION是无序的,那么,我们就让需要UNION的两个结果从一开始就已经是排序过后的样子就好了。最终代码如下:
SELECT t1.*
FROM
(SELECT exam_id AS tid, COUNT(DISTINCT uid) AS uv, COUNT(uid) AS pv
FROM exam_record
GROUP BY exam_id
ORDER BY uv DESC, pv DESC) AS t1
UNION
SELECT t2.*
FROM
(SELECT question_id AS tid, COUNT(DISTINCT uid) AS uv, COUNT(uid) AS pv
FROM practice_record
GROUP BY question_id
ORDER BY uv DESC, pv DESC) AS t2;