选课系统中存在这样的三种表
学生表(Student): 学号(S#)、学生姓名(Sname)、学生年龄(Sage)、学生性别(Ssex);
课程表(Course):课程编号(C#)、课程名称(Cname)、教师编号(T#);
成绩表(SC):学号(S#)、课程编号(C#)、成绩(score);
教师表(Teacher):教师编号(T#)、教师名字(Tname)。
请用SQL完成以下需求
a查询"001"课程比"002"课程成绩高的所有学生的学号
b查询平均成绩大于60分的同学的学号和平均成绩
c查询所有同学的学号、姓名、选课数、总成绩
d查询没有学过"叶平"老师的同学的学号、姓名 e查询学过"001"并且也学过编号"002"课程的同学的学号、姓名
首先,我会针对每个需求分别给出SQL查询语句。
a) 查询"001"课程比"002"课程成绩高的所有学生的学号
sql
SELECT s1.S#
FROM SC s1, SC s2
WHERE s1.S# = s2.S#
AND s1.C# = '001'
AND s2.C# = '002'
AND s1.score > s2.score;
-- 当你执行这个 SQL 查询时,数据库管理系统(DBMS)会遵循一定的逻辑和执行顺序来处理查询。虽然实际的物理执行顺序可能因数据库的查询优化器而异,但我们可以按照逻辑执行顺序来解释这个查询。以下是该查询的逻辑执行顺序和讲解:
-- FROM 子句:
-- 首先,DBMS 会确定要从哪些表中检索数据。在这个查询中,我们有两个表实例 SC s1 和 SC s2,它们实际上是同一个表 SC 的两个别名。使用别名允许我们在查询中多次引用同一个表,这在进行比较或连接表的不同行时非常有用。
-- WHERE 子句(包括连接条件):
-- 接下来,DBMS 会应用 WHERE 子句中的条件来过滤记录。这个查询的 WHERE 子句包含几个条件,它们将一起应用:
-- s1.S# = s2.S#:这是一个连接条件,用于将 s1 和 s2 两个表实例中的记录匹配起来。具体来说,它会找出那些在两个表实例中具有相同学号 (S#) 的记录。这实际上是一种自连接,因为我们正在比较同一个表的不同行。
-- s1.C# = '001' 和 s2.C# = '002':这两个条件分别过滤出 s1 中课程编号为 '001' 的记录和 s2 中课程编号为 '002' 的记录。
-- s1.score > s2.score:这个条件比较了匹配学号在两个不同课程中的分数,只选择那些在 '001' 课程中分数高于 '002' 课程的记录。
-- SELECT 子句:
-- 最后,一旦所有符合条件的记录都被确定,DBMS 就会根据 SELECT 子句中的指示选择并返回特定的列。在这个查询中,我们仅选择并返回了 s1.S#,即满足所有上述条件的学生的学号。
-- 总结来说,这个查询的逻辑是找出那些在 '001' 课程中得分高于 '002' 课程的所有学生的学号。它通过比较同一个学生(由学号 S# 标识)在两门不同课程中的分数来实现这一点。这种查询在处理诸如成绩比较、趋势分析或识别特定表现模式的学生等任务时非常有用。
b) 查询平均成绩大于60分的同学的学号和平均成绩
sql
SELECT S#, AVG(score) AS avg_score
FROM SC
GROUP BY S#
HAVING AVG(score) > 60;
c) 查询所有同学的学号、姓名、选课数、总成绩
sql
SELECT Student.S#, Student.Sname, COUNT(SC.C#) AS course_count, SUM(SC.score) AS total_score
FROM Student
LEFT JOIN SC ON Student.S# = SC.S#
GROUP BY Student.S#, Student.Sname;
d) 查询没有学过"叶平"老师的同学的学号、姓名
sql
SELECT Student.S#, Student.Sname
FROM Student
WHERE S# NOT IN (
SELECT DISTINCT SC.S#
FROM SC
JOIN Course ON SC.C# = Course.C#
JOIN Teacher ON Course.T# = Teacher.T#
WHERE Teacher.Tname = '叶平'
);
-- 这条SQL查询的目的是从Student表中选择那些没有选修过'叶平'老师课程的学生的学号和姓名。下面我将从执行顺序的角度详细解释这条查询。
-- 查询解释
-- 内层子查询:
-- sql
-- SELECT DISTINCT SC.S#
-- FROM SC
-- JOIN Course ON SC.C# = Course.C#
-- JOIN Teacher ON Course.T# = Teacher.T#
-- WHERE Teacher.Tname = '叶平'
-- 这个子查询首先通过JOIN操作连接了三个表:SC(学生课程表)、Course(课程表)和Teacher(教师表)。连接条件是课程编号(C#)和教师编号(T#)。然后,它筛选出教师名字为'叶平'的课程所对应的学生编号(S#),并确保学生编号是唯一的(通过DISTINCT)。
-- 外层查询:
-- sql
-- SELECT Student.S#, Student.Sname
-- FROM Student
-- WHERE S# NOT IN (子查询结果)
-- 外层查询从Student表中选择学号(S#)和姓名(Sname)。WHERE子句中的条件是,学生的学号不能出现在内层子查询的结果中。换句话说,这个查询选择了所有没有选修过'叶平'老师课程的学生。
-- 执行顺序(逻辑上)
-- FROM 子句:首先确定查询的主体表,这里是Student表。
-- 内层子查询:在执行外层查询之前,数据库会先执行内层子查询。
-- JOIN操作:先连接SC、Course和Teacher三个表,基于课程编号和教师编号的匹配。
-- WHERE子句:从连接后的结果中筛选出教师名字为'叶平'的记录。
-- DISTINCT:确保返回的学生编号是唯一的。
-- WHERE 子句(外层查询):使用内层子查询的结果来过滤Student表中的记录。具体来说,它会排除那些学号出现在内层子查询结果中的学生。
-- SELECT 子句:最后,从经过过滤的Student表中选择学号和姓名。
-- 这条查询的逻辑执行顺序大致如上所述,但实际的物理执行顺序可能会因数据库的查询优化器而有所调整,以提高查询效率。不过,从逻辑上理解,这个顺序有助于我们更好地掌握查询的工作原理。
sql
SELECT s.S#, s.Sname
FROM Student s
LEFT JOIN (
SELECT DISTINCT sc.S#
FROM SC sc
JOIN Course c ON sc.C# = c.C#
JOIN Teacher t ON c.T# = t.T#
WHERE t.Tname = '叶平'
) AS taught_by_yeping ON s.S# = taught_by_yeping.S#
WHERE taught_by_yeping.S# IS NULL;
-- 在这个查询中,我们首先创建了一个子查询(别名为 taught_by_yeping),该子查询选出了所有学过"叶平"老师课程的学生的学号。然后,我们使用 LEFT JOIN 将这个子查询的结果连接到 Student 表上。最后,通过 WHERE 子句筛选出那些在子查询结果中没有对应学号的学生,即没有学过"叶平"老师课程的学生。
-- 这种方法通常比使用 NOT IN 更高效,特别是在处理大量数据时,因为 LEFT JOIN 通常能够更好地利用数据库索引,从而减少查询的执行时间。
-- 子查询执行:
-- 在执行主查询之前,MySQL会首先处理子查询部分。这个子查询用于创建一个临时表(在本查询中命名为taught_by_yeping),其中包含选修了'叶平'老师课程的学生的学号(S#)。
-- sql
-- SELECT DISTINCT sc.S#
-- FROM SC sc
-- JOIN Course c ON sc.C# = c.C#
-- JOIN Teacher t ON c.T# = t.T#
-- WHERE t.Tname = '叶平'
-- 首先,SC表(别名sc)与Course表(别名c)通过课程编号(C#)进行连接。
-- 接着,连接后的结果与Teacher表(别名t)通过教师编号(T#)进行连接。
-- 然后,WHERE子句筛选出教师名字为'叶平'的记录。
-- 最后,使用DISTINCT关键字确保返回的学生学号(S#)是唯一的。
-- 这个子查询的结果集将作为一个临时表taught_by_yeping,包含所有选修了'叶平'老师课程的学生的学号。
-- 主查询执行:
-- 在子查询执行完毕后,主查询开始执行。主查询的目的是从Student表中选择那些没有选修过'叶平'老师课程的学生的学号和姓名。
-- sql
-- SELECT s.S#, s.Sname
-- FROM Student s
-- LEFT JOIN taught_by_yeping ON s.S# = taught_by_yeping.S#
-- WHERE taught_by_yeping.S# IS NULL
-- 首先,Student表(别名s)与前面创建的临时表taught_by_yeping进行左连接(LEFT JOIN),连接条件是学生学号(S#)。
-- 左连接会返回Student表中的所有记录,以及与之匹配的taught_by_yeping表中的记录(如果有的话)。如果taught_by_yeping表中没有匹配的记录,则对应的字段将为NULL。
-- 接着,WHERE子句筛选出taught_by_yeping.S#为NULL的记录,即那些在taught_by_yeping临时表中没有匹配学号的学生,这表示他们没有选修过'叶平'老师的课程。
-- 最后,SELECT子句选择这些学生的学号(S#)和姓名(Sname)。
-- 总结来说,这个查询通过子查询找出选修了'叶平'老师课程的学生学号,然后通过左连接和筛选条件找出没有选修过这些课程的学生,最终返回他们的学号和姓名。
sql
SELECT s.S#, s.Sname
FROM Student s
LEFT JOIN SC sc ON s.S# = sc.S#
LEFT JOIN Course c ON sc.C# = c.C#
LEFT JOIN Teacher t ON c.T# = t.T# AND t.Tname = '叶平'
GROUP BY s.S#, s.Sname
HAVING COUNT(DISTINCT t.T#) = 0;
-- 在这个查询中:
-- 我们首先对 Student 表进行左连接,通过 SC 表连接到 Course 表,再通过 Course 表连接到 Teacher 表。
-- 在连接 Teacher 表时,我们在 ON 子句中加入了条件 t.Tname = '叶平',这样只有当教师名字是'叶平'时,才会进行连接。
-- 使用 GROUP BY 对学生学号和姓名进行分组,因为我们想要得到每个学生的结果。
-- HAVING COUNT(DISTINCT t.T#) = 0 确保我们只选择那些没有与'叶平'老师相关联的记录的学生。如果一个学生与'叶平'老师有关联,那么 COUNT(DISTINCT t.T#) 的值会大于0。
-- 请注意,这种方法可能在性能上不如使用子查询的方法,特别是当数据库中的记录数量非常大时。在实际应用中,建议测试不同的查询方法,以确定哪种方法在您的特定数据库和数据量下最有效。
-- 另外,如果数据库中存在大量数据,确保相关的字段(如学号、课程号和教师号)已经被正确地索引,以提高查询性能。
e) 查询学过"001"并且也学过编号"002"课程的同学的学号、姓名
sql
SELECT Student.S#, Student.Sname
FROM Student
WHERE S# IN (
SELECT S# FROM SC WHERE C# = '001'
) AND S# IN (
SELECT S# FROM SC WHERE C# = '002'
);
或者使用EXISTS子句来实现:
sql
SELECT s.S#, s.Sname
FROM Student s
WHERE EXISTS (SELECT 1 FROM SC WHERE S# = s.S# AND C# = '001')
AND EXISTS (SELECT 1 FROM SC WHERE S# = s.S# AND C# = '002');
请注意,这些查询假设数据库中的表和字段都严格遵循您提供的结构,并且所有的字段名和表名都是准确无误的。在实际应用中,您可能需要根据实际的数据库结构和命名来调整这些查询。