# 练习-综合性练习一

现有三张表

  • 学生信息表
  • 课程信息表
  • 学生成绩表

学生表 student

  • 学号:student_no
  • 姓名:student_name
  • 性别:student_sex
  • 籍贯:student_hometown
student_no student_name student_sex student_hometown
1 张三 北京
2 李四 上海
3 王五 广州
4 赵六 深圳
5 孙七 成都
6 周八 重庆

课程表 course

  • 课程号:course_no
  • 课程名:course_name
  • 总分:course_total_score

生成3门课程

course_no course_name course_total_score
1 数学 100
2 英语 100
3 语文 100

学生成绩表 score

  • 学生成绩表id:id
  • 学号:student_no
  • 课程号:course_no
  • 成绩:score
id student_no course_no score
1 1 1 52
2 1 2 62
3 1 3 74
4 2 1 88
5 2 2 95
6 2 3 100
7 3 1 57
8 3 2 69
9 3 3 81
10 4 1 83
11 4 2 95
12 4 3 99
13 5 1 50
14 5 2 60
15 5 3 72
16 6 1 89
17 6 2 98
18 6 3 100

# 每个学生总成绩

查询每个学生的总成绩和学生信息

  select 
    student.student_no student_no,
    student_name, 
    student_sex, 
    student_hometown,
    SUM(score) as total_score
  from student 
  left join score on student.student_no = score.student_no
  group by score.student_no

结果

student_no student_name student_sex student_hometown total_score
1 张三 北京 188
2 李四 上海 283
3 王五 广州 207
4 赵六 深圳 277
5 孙七 成都 182
6 周八 重庆 287

解析

  • 使用left join 连接学生表和成绩表
  • 使用group by 按照学号分组
  • 使用SUM(score) 计算每个学生的总成绩

# 查询英语成绩排名

相同分数排名相同,下一名次+1

  select 
      st.student_no,
      st.student_name,
      sc.score,
      dense_rank() over (order by score desc) as rank
  from score sc
  join student st on sc.student_no = st.student_no
  where sc.course_no = 2

结果

student_no student_name student_sex student_hometown score rank
6 周八 重庆 98 1
2 李四 上海 95 2
4 赵六 深圳 95 2
3 王五 广州 69 3
1 张三 北京 62 4
5 孙七 成都 60 5

解析

  • 使用dense_rank() 计算排名, dense_rank()窗口函数的作用是计算每个学生的排名,相同分数排名相同,下一名次+1
  • 使用where 过滤课程号为2的英语成绩

# 查询每个课程的第一名

查询每个课程的第一名,存在并列的返回多个。并返回学生姓名(student_name)、课程名(course_name)、成绩(score)

正确答案

student_name course_name score
孙七 数学 89
周八 数学 89
周八 英语 98
李四 语文 100
周八 语文 100

# 方法一

SELECT student_name, course_name, score
FROM (
    SELECT student_name, course_name, score,
       dense_rank() OVER (PARTITION BY course_name ORDER BY score DESC) as rank
    FROM (
      select *
      from student st
      join score sc on sc.student_no = st.student_no
      join course co on sc.course_no = co.course_no
    )
) as ranked
WHERE rank = 1;

解析

  • 使用dense_rank() 计算排名, dense_rank()窗口函数的作用是计算每个学生的排名,相同分数排名相同,下一名次+1
  • 使用where 过滤排名为1的记录

# 方法二

SELECT 
    st.student_name,
    co.course_name,
    sc.score
FROM score sc
JOIN student st ON sc.student_no = st.student_no
JOIN course co ON sc.course_no = co.course_no
WHERE sc.score = (
    SELECT MAX(score)
    FROM score
    WHERE course_no = sc.course_no
);

# 解析

该函数主要有三部分

  • 主查询的FROMJOIN
  • 子查询
  • WHERE条件的过滤
  1. 主查询的FROMJOIN
SELECT 
    st.student_name,
    co.course_name,
    sc.score
FROM score sc
JOIN student st ON sc.student_no = st.student_no
JOIN course co ON sc.course_no = co.course_no

首先,score表与student表和course表进行连接,生成一个包含所有学生成绩、学生信息和课程信息的临时结果集。

  1. 子查询:
SELECT MAX(score)
FROM score
WHERE course_no = sc.course_no
  • 对于主查询中的每一行,子查询都会执行一次,计算当前课程的最高分。
  • 例如,如果主查询中当前行的course_no是1,子查询会计算课程1的最高分。
  1. WHERE条件的过滤:
WHERE sc.score = (
    ...
);

主查询中的每一行都会与子查询的结果进行比较,如sc.score等于子查询返回的最高分,则该行会被保留。

# 方法三

SELECT 
    st.student_name,
    co.course_name,
    sc.score
FROM score sc
JOIN student st ON sc.student_no = st.student_no
JOIN course co ON sc.course_no = co.course_no
JOIN (
    SELECT 
        course_no,
        MAX(score) AS max_score
    FROM score
    GROUP BY course_no
) AS max_scores ON sc.course_no = max_scores.course_no AND sc.score = max_scores.max_score;

# 解析

方法三和方法二的主要区别在于方法三使用了子查询,将计算最高分的逻辑单独抽取出来。将主查询的临时结果集与子查询的max_scores表进行连接,筛选出成绩等于课程最高分的学生记录。

# 查询每个课程的第二名

# 方法一

使用窗口函数dense_rank()计算排名

SELECT 
    student_name,
    course_name,
    score
FROM (
    SELECT 
        st.student_name,
        co.course_name,
        sc.score,
        dense_rank() OVER (PARTITION BY sc.course_no ORDER BY sc.score DESC) AS rank
    FROM score sc
    JOIN student st ON sc.student_no = st.student_no
    JOIN course co ON sc.course_no = co.course_no
) AS ranked
WHERE rank = 2;

# 方法二

SELECT 
    st.student_name,
    co.course_name,
    sc.score
FROM score sc
JOIN student st ON sc.student_no = st.student_no
JOIN course co ON sc.course_no = co.course_no
WHERE sc.score = (
    SELECT MIN(score)
    FROM (
        SELECT DISTINCT score
        FROM score  
        WHERE course_no = sc.course_no
        ORDER BY score DESC
        LIMIT 2
    ) AS top_two
);

# 解析

  1. 主查询的FROM和JOIN:
  • score表与student表和course表进行连接,生成一个包含所有学生成绩、学生信息和课程信息的临时结果集。
  1. 子查询的执行:
  • 对于主查询中的每一行,子查询都会执行一次,计算当前课程的第二名成绩。
  • 内层子查询获取当前课程的前2个不同成绩,外层子查询取其中的最小值(即第二名成绩)。

内层子查询先在相同课程下,进行DESC降序排序,然后使用LIMIT 2取前2个成绩,外层子查询取其中的最小值(即第二名成绩)。

  1. WHERE条件的过滤:
  • 主查询中的每一行都会与子查询的结果进行比较,如果sc.score等于子查询返回的第二名成绩,则该行会被保留。

# 查询各科目第一名次数最多的学生

# 方法一

select student_name, max(time) time
from (
  SELECT student_name, count(student_no) as time
  FROM (
      SELECT student_name, course_name, student_no,
         dense_rank() OVER (PARTITION BY course_name ORDER BY score DESC) as rank
      FROM (
        select *
        from student st
        join score sc on sc.student_no = st.student_no
        join course co on sc.course_no = co.course_no
      )
  ) as ranked
  WHERE rank = 1
  group by student_no
)

结果

student_name time
周八 3

# 解析

  • 通过子查询,我们先筛选出一名的学生,并使用group by进行分组算出 出现第一名学生(同一人)的次数count
  • 然后使用max(time) 获取出现次数最多的学生

# 查询每个课程中成绩大于李四的学生

  select 
    st.student_name,
    co.course_name,
    sc.score
  from student st
  join score sc on sc.student_no = st.student_no
  join course co on sc.course_no = co.course_no
  where sc.score > (
    select score
    from score
    where student_no = 2 and course_no = sc.course_no
  )

结果

student_name course_name score
孙七 数学 89
周八 数学 89
周八 英语 98

# 解析

  • 通过子查询,我们先筛选出李四的成绩,然后使用where过滤出大于李四的成绩
  • 然后使用join连接学生表和成绩表,并使用join连接课程表和成绩表
  • 最后使用where过滤出大于李四的成绩