查找至少参加过一次考试但未达到最高或最低分数的学生

Find the students who attended at least one exam but not the Max nor the Min score

我的解决方案通过了第一个测试用例,但在最终提交后得到了错误的答案。我感谢任何愿意指出我错误的人。谢谢!

问题如下:

Table:学生

+---------------------+---------+
| Column Name         | Type    |
+---------------------+---------+
| student_id          | int     |
| student_name        | varchar |
+---------------------+---------+

student_id 是此 table 的主键。 student_name是学生的名字。

Table:考试

+---------------+---------+
| Column Name   | Type    |
+---------------+---------+
| exam_id       | int     |
| student_id    | int     |
| score         | int     |
+---------------+---------+

(exam_id, student_id) 是这个 table 的主键。 student_id 的学生在 ID 为 exam_id 的考试中获得了分数。

“相当”的学生是指至少参加过一次考试并且既没有获得高分也没有获得低分的学生。

编写一个 SQL 查询来报告学生(student_id、student_name)在所有考试中都“安静”。

不要return从未参加过任何考试的学生。 Return 结果 table 按 student_id 排序。

查询结果格式如下例

学生table:

+-------------+---------------+
| student_id  | student_name  |
+-------------+---------------+
| 1           | Daniel        |
| 2           | Jade          |
| 3           | Stella        |
| 4           | Jonathan      |
| 5           | Will          |
+-------------+---------------+

考试table:

+------------+--------------+-----------+
| exam_id    | student_id   | score     |
+------------+--------------+-----------+
| 10         |     1        |    70     |
| 10         |     2        |    80     |
| 10         |     3        |    90     |
| 20         |     1        |    80     |
| 30         |     1        |    70     |
| 30         |     3        |    80     |
| 30         |     4        |    90     |
| 40         |     1        |    60     |
| 40         |     2        |    70     |
| 40         |     4        |    80     |
+------------+--------------+-----------+

结果table:

+-------------+---------------+
| student_id  | student_name  |
+-------------+---------------+
| 2           | Jade          |
+-------------+---------------+

题中解释:

对于考试 1:学生 1 和 3 分别持有最低和最高分。 对于考试 2:学生 1 同时持有最高分和最低分。 对于考试 3 和 4:Studnet 1 和 4 分别持有最低和最高分。 学生 2 和 5 在任何一次考试中都没有获得最高或最低的成绩。 由于学生 5 没有参加任何考试,因此他被排除在结果之外。 所以,我们只return学生2的信息

我的答案是创建两个 table,一个是列出符合条件的学生,至少有一次考试。 另一种是求考试的max(score)和min(score)table。 并使用<>定位安静学生的id,然后与Studenttable联合查找 这个student_id的名字,如下:

-- get eligible student

with eligible_student as
(
select distinct student_id as eligible_id from Exam
    group by 1
    order by 1
),

-- get the high and low score
high_low as
(select student_id, max(score) as high_score, min(score) as low_score
from Exam), 

result as 
 (select eligible_student.eligible_id as student_id
 from eligible_student inner join
 high_low 
 on eligible_student.eligible_id <> high_low.student_id
-- left join Student
-- on eligible_student.eligible_id = Student.student_id 
 group by student_id
 order by student_id
 )
 
 select result.student_id, s.student_name as student_name
 from result left join Student s
 on result.student_id = s.student_id
 order by student_id;


我会使用 window 函数和聚合:

select s.*
from student s
inner join (
    select e.*, 
        rank() over(partition by exam_id order by score) as rn_asc,
        rank() over(partition by exam_id order by score desc) as rn_desc
    from exam e
) e on e.student_id = s.student_id
group by s.student_id
having min(rn_asc) > 1 and min(rn_desc) > 1

子查询通过升序和降序分数对具有相同考试的记录进行排名,允许并列。然后,我们可以将其与学生 table 合并(这将消除根本没有考试的学生),按学生分组,并过滤​​那些两个排名都未达到 1.

的学生

这个查询:

SELECT *,
     (MIN(score) OVER (PARTITION BY exam_id) = score) +
     (MAX(score) OVER (PARTITION BY exam_id) = score) flag
FROM exam

returns 当学生的分数既不是考试的最低分也不是最高分时,flag 列的值为 0

您可以聚合上述查询的结果,以获取所有没有 flag 且值不同于 0 的学生:

WITH cte AS (
  SELECT *,
         (MIN(score) OVER (PARTITION BY exam_id) = score) +
         (MAX(score) OVER (PARTITION BY exam_id) = score) flag
  FROM exam
)
SELECT s.student_id, s.student_name
FROM student s INNER JOIN cte c
ON c.student_id = s.student_id
GROUP BY s.student_id, s.student_name
HAVING SUM(c.flag) = 0

或:

WITH cte AS (
  SELECT student_id
  FROM (
    SELECT *,
         (MIN(score) OVER (PARTITION BY exam_id) = score) +
         (MAX(score) OVER (PARTITION BY exam_id) = score) flag
    FROM exam
  ) t
  GROUP BY student_id
  HAVING SUM(flag) = 0
)
SELECT * 
FROM student
WHERE student_id IN (SELECT student_id FROM cte)

参见demo
结果:

> student_id | student_name
> ---------: | :-----------
>          2 | Jade        

这部分错误:

with high_low as
(select student_id, max(score) as high_score, min(score) as low_score
from Exam)

因为它输出:

+------------+------------+-----------+
| student_id | high_score | low_score |
+------------+------------+-----------+
|          1 |         90 |        60 |
+------------+------------+-----------+

和student_id=1与找到的high_score或low_score没有关系。

此后找到的(但不正确)student_id 用于 cte result 的选择。

解决方案:

with high_low as
(select max(score) as high_score, min(score) as low_score
from Exam) 
select student.student_id, student_name
from (
  select exam.student_id 
   from exam
   group by exam.student_id
   having max(score) <> (select high_score from high_low)
      and min(score) <> (select low_score from high_low)) x
inner join student on student.student_id=x.student_id;

或:

select 
   student.*
from exam 
inner join student on student.student_id=exam.student_id
group by student_id 
having max(score) not in (select max(score) from exam) 
   and min(score) not in (select min(score) from exam);