数据库MySQL-(进阶)数据库约束 (Constraints)、表关系设计(Table Relations)和复杂查询技术(Query)
本文深入讲解了MySQL表的增删改查进阶操作,重点介绍了数据库约束、表关系设计和复杂查询技术。主要内容包括:1. 数据库约束(NOT NULL、UNIQUE、PRIMARY KEY、FOREIGN KEY等)及其应用场景;2. 表关系的三种类型(一对一、一对多、多对多)及实现方法;3. 聚合查询(GROUP BY、HAVING)和函数使用;4. 联合查询(INNER JOIN、LEFT JOIN等
好的,我们来对您提供的“MySQL表的增删改查(进阶)”内容进行一次全面、深入的解析。我将严格遵循图片中的大纲结构,对每一个知识点进行详尽的解释,为所有SQL代码提供逐行注释,并进行大量的扩展说明,以确保内容详实、透彻,并达到您对篇幅的要求。
引言:从基础到进阶
在掌握了基础的CRUD(增删改查)之后,“进阶”的核心在于两个方面:
-
保证数据的正确性和完整性:我们如何确保存入数据库的数据是有效、无误、且符合业务规则的?这就引出了数据库约束 (Constraints)。
-
实现复杂的数据关联查询:当数据分散在多张表中时,我们如何将它们有效地关联起来,从中提取出有价值的信息?这就需要学习表的关系设计以及高级查询技巧,如联合查询 (Joins)、聚合查询 (Aggregate Queries) 和 子查询 (Subqueries)。
本节内容将围绕这两个核心,带你构建更健壮、更强大的数据库应用能力。
准备工作:创建示例数据表
为了让后续的示例更具象化,我们先创建几张符合“一对多”关系的表:一个班级(classes
)表和一个学生(students
)表。一个班级可以有多个学生,一个学生只属于一个班级。
SQL
-- 创建一个用于演示的数据库
CREATE DATABASE IF NOT EXISTS advanced_db CHARACTER SET utf8mb4;
-- 切换到该数据库
USE advanced_db;
-- 创建班级表 (父表)
CREATE TABLE classes (
id INT PRIMARY KEY AUTO_INCREMENT COMMENT '班级ID',
name VARCHAR(100) NOT NULL UNIQUE COMMENT '班级名称'
);
-- 创建学生表 (子表)
CREATE TABLE students (
id INT PRIMARY KEY AUTO_INCREMENT COMMENT '学生ID',
sn VARCHAR(20) NOT NULL UNIQUE COMMENT '学号',
name VARCHAR(50) NOT NULL COMMENT '姓名',
score DECIMAL(5, 2) DEFAULT 0.00 COMMENT '成绩',
class_id INT COMMENT '所属班级ID' -- 这个列将作为外键关联到 classes 表
);
-- 插入一些初始数据
INSERT INTO classes (name) VALUES ('计算机科学1班'), ('软件工程2班'), ('网络工程3班'), ('未分班');
INSERT INTO students (sn, name, score, class_id) VALUES
('SN001', '张三', 90.50, 1),
('SN002', '李四', 88.00, 1),
('SN003', '王五', 92.50, 2),
('SN004', '赵六', 76.00, 2),
('SN005', '孙七', 85.00, 3),
('SN006', '周八', 60.00, NULL); -- 周八同学暂时未分配班级
一、 数据库约束 (Database Constraints)
数据库约束是在表的数据列上强制执行的规则,用于限制可以插入、更新或删除的数据类型,从而确保数据库中数据的准确性、完整性和可靠性。
1.1 & 1.2: 约束类型 与 NULL 约束
-
NULL 约束:默认情况下,表的列是允许存入
NULL
值的。NULL
是一个特殊的值,代表“未知”、“不适用”或“不存在”。 -
NOT NULL
: 如果我们不希望某一列的值为空,就可以在定义该列时添加NOT NULL
约束。
示例: 在上面的 students
表中,name
列被定义为 NOT NULL
,这意味着你在 INSERT
或 UPDATE
时必须为 name
提供一个非空的值,否则数据库会报错。而 score
列没有这个约束,理论上可以为 NULL
(尽管我们给了它 DEFAULT
值)。
1.3 UNIQUE: 唯一约束
作用:保证某一列(或某几列组合)的所有值都是唯一的,不能有重复。
示例: 在 students
表中,sn
(学号)对于每个学生来说都应该是独一无二的,所以我们给它添加了 UNIQUE
约束。
SQL
-- 在创建表时添加唯一约束
CREATE TABLE students (
id INT PRIMARY KEY AUTO_INCREMENT,
sn VARCHAR(20) NOT NULL UNIQUE, -- 列级定义唯一约束
...
);
-- 或者在表定义最后添加唯一约束(适用于单列或多列组合)
CREATE TABLE example_table (
part1 VARCHAR(20),
part2 VARCHAR(20),
...
UNIQUE(part1, part2) -- 表级定义组合唯一约束
);
深入扩展与注意事项:
-
NULL
值的处理:UNIQUE
约束允许列中包含多个NULL
值。因为NULL
被认为是“未知”的,所以两个NULL
值并不被视为相等。 -
UNIQUE
vsPRIMARY KEY
:-
一个表只能有一个主键(
PRIMARY KEY
),但可以有多个唯一约束(UNIQUE
)。 -
主键列的值绝对不能为
NULL
,而唯一约束列可以。
-
1.4 DEFAULT: 默认值约束
作用:如果在插入一条新记录时没有为某个列提供值,数据库会自动为该列赋予防定的默认值。
示例: 在 students
表中,score
列有 DEFAULT 0.00
约束。
SQL
-- 插入一个新学生,但不指定分数
INSERT INTO students (sn, name, class_id) VALUES ('SN007', '吴九', 3);
-- 查询该学生,会发现他的 score 自动变为了 0.00
SELECT * FROM students WHERE sn = 'SN007';
1.5 PRIMARY KEY: 主键约束
作用:主键是表中每一行数据的唯一标识符。主键列的值必须是唯一的,且绝对不能为 NULL
。一个表只能有一个主键。
特性总结:PRIMARY KEY
= UNIQUE
+ NOT NULL
示例: 在 classes
和 students
表中,id
列都被定义为 PRIMARY KEY
。我们通常还会搭配 AUTO_INCREMENT
让数据库自动生成唯一ID。
深入扩展:自然主键 vs 代理主键
-
自然主键 (Natural Key):使用业务数据中本身就具有唯一性的字段作为主键,例如
sn
(学号) 或身份证号。 -
代理主键 (Surrogate Key):创建一个与业务无关的、纯粹用于唯一标识的字段,通常是
INT
或BIGINT
类型的id
,并设置其为AUTO_INCREMENT
。
为什么推荐使用代理主键?
-
稳定性:业务数据(自然主键)可能会有变更的需求(例如,学号编排规则变了),修改主键是一项成本极高且危险的操作。而代理主键一旦生成,就永远不变。
-
性能:整数类型的代理主键通常比字符串类型的自然主键在索引和连接查询时性能更好。
1.6 FOREIGN KEY: 外键约束
作用:外键是数据库约束的核心,是实现表之间关联和保证参照完整性的关键。它确保一个表(子表)中的某个列的值,必须在另一个表(父表)的主键列中存在。
示例: students
表中的 class_id
就是一个外键,它参照了 classes
表中的 id
。这确保了我们不能为一个学生随意编造一个不存在的班级ID。
语法:
SQL
-- 我们来重建 students 表,并正式添加外键约束
DROP TABLE students; -- 先删除旧表
CREATE TABLE students (
id INT PRIMARY KEY AUTO_INCREMENT COMMENT '学生ID',
sn VARCHAR(20) NOT NULL UNIQUE COMMENT '学号',
name VARCHAR(50) NOT NULL COMMENT '姓名',
score DECIMAL(5, 2) DEFAULT 0.00 COMMENT '成绩',
class_id INT COMMENT '所属班级ID',
-- 添加外键约束
-- CONSTRAINT fk_students_classes -- 给约束起一个名字(可选,但推荐)
FOREIGN KEY (class_id) -- 指定当前表(子表)的哪一列是外键
REFERENCES classes(id) -- 指定该外键参照哪个父表的哪个主键列
);
有了外键约束后:
-
INSERT
: 你不能插入一个class_id
为 99 的学生记录,因为classes
表中不存在id
为 99 的班级。 -
DELETE
: 你不能删除classes
表中id
为 1 的班级,因为它下面还关联着学生(张三、李四)。直接删除会导致这些学生数据参照失效。
深入扩展:外键的级联操作 (ON DELETE / ON UPDATE)
为了解决上面不能删除父表记录的问题,外键提供了级联操作策略:
-
ON DELETE RESTRICT
(默认):禁止删除父表记录。 -
ON DELETE CASCADE
(级联删除):当父表记录被删除时,自动删除所有关联的子表记录。这是一个高风险操作,例如删除一个班级,该班所有学生记录都会被永久删除。 -
ON DELETE SET NULL
: 当父表记录被删除时,自动将所有关联子表记录的外键列值设为NULL
。这要求子表的外键列必须允许为NULL
。例如,删除一个班级,该班学生的class_id
会变为NULL
(表示未分班)。
SQL
-- 定义了级联操作的外键
FOREIGN KEY (class_id) REFERENCES classes(id) ON DELETE SET NULL ON UPDATE CASCADE
1.7 CHECK 约束 (了解)
作用:为一个列的值设置一个必须满足的布尔表达式。
示例:
SQL
-- 要求 score 必须在 0 到 100 之间
CREATE TABLE students (
...
score DECIMAL(5, 2),
CHECK (score >= 0 AND score <= 100)
);
历史注意:在 MySQL 8.0.16 版本之前,MySQL虽然支持 CHECK
的语法,但并不会实际去强制执行这个约束。从 8.0.16 开始,CHECK
约束才被真正实现。因此在旧版本的MySQL中,这个约束是不起作用的。
二、 表的设计 (Table Relations)
数据库的威力在于管理相互关联的数据。表之间的关系主要有三种:
2.1 一对一 (One-to-One)
概念:表A中的一条记录,最多只能与表B中的一条记录相对应,反之亦然。
场景:不常用。通常用于拆分一张非常宽的表。例如,users 表存放基本登录信息(用户名、密码),user_profiles 表存放详细信息(昵称、头像、简介等)。
实现方式:
-
共享主键:让
user_profiles
表的主键既是自己的主键,也是参照users
表主键的外键。
2.2 一对多 (One-to-Many)
概念:表A中的一条记录可以对应表B中的多条记录,但表B中的一条记录只能对应表A中的一条记录。
场景:最常见的关系。例如,一个班级对应多个学生;一个用户发表多篇文章。
实现方式:在“多”的一方(students表)添加一个外键列(class_id),指向“一”的一方(classes表)的主键。
2.3 多对多 (Many-to-Many)
概念:表A中的一条记录可以对应表B中的多条记录,反之亦然。
场景:也非常常见。例如,一个学生可以选修多门课程,一门课程可以被多个学生选修。
实现方式:必须通过**第三张中间表(或称为连接表、关系表)**来实现。这张中间表至少包含两个外键,分别指向原来两张表的主键。
示例:学生选课
SQL
-- 课程表
CREATE TABLE courses (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(100) NOT NULL
);
-- 学生-课程关系表 (中间表)
CREATE TABLE student_courses (
id INT PRIMARY KEY AUTO_INCREMENT,
student_id INT NOT NULL, -- 外键, 指向 students 表
course_id INT NOT NULL, -- 外键, 指向 courses 表
FOREIGN KEY (student_id) REFERENCES students(id),
FOREIGN KEY (course_id) REFERENCES courses(id),
UNIQUE (student_id, course_id) -- 保证一个学生不能重复选同一门课
);
这样,通过在 student_courses
表中插入记录,就可以表示学生和课程之间的选修关系。
三、 新增 (Insert - 进阶)
在定义了各种约束之后,INSERT
语句的行为会受到影响。
-
你必须为所有
NOT NULL
且没有DEFAULT
值的列提供数据。 -
你为主键列和唯一约束列提供的值不能与已有数据重复。
-
你为外键列提供的值必须在父表中存在。
-
你提供的值必须满足
CHECK
约束的条件。
任何违反约束的 INSERT
操作都会被数据库拒绝并报错。
四、 查询 (Query - 进阶)
这是进阶部分的核心和难点。
4.1 聚合查询 (Aggregate Query)
聚合查询用于对一组值进行计算,并返回单个值。
4.1.1 聚合函数 (Aggregate Functions)
-
COUNT(列名 or *)
: 统计行数。-
COUNT(*)
或COUNT(1)
: 统计所有行数。 -
COUNT(列名)
: 统计指定列中非NULL值的行数。
-
-
SUM(列名)
: 计算指定列的数值总和。 -
AVG(列名)
: 计算指定列的数值平均值。 -
MAX(列名)
: 找出指定列的最大值。 -
MIN(列名)
: 找出指定列的最小值。
示例:
SQL
-- 统计学生总人数
SELECT COUNT(*) FROM students;
-- 统计1班的学生人数
SELECT COUNT(*) FROM students WHERE class_id = 1;
-- 计算1班的总分
SELECT SUM(score) FROM students WHERE class_id = 1;
-- 找出2班的最高分
SELECT MAX(score) FROM students WHERE class_id = 2;
4.1.2 GROUP BY 子句
作用:GROUP BY
子句通常与聚合函数一起使用,它将结果集中的行按照一个或多个列的值进行分组,然后对每个分组分别执行聚合函数。
示例:
SQL
-- 需求:查询每个班级各有多少名学生
SELECT
class_id, -- 按哪个字段分组,通常就查询哪个字段
COUNT(*) AS student_count -- 对每个分组进行聚合计算,并用别名显示
FROM
students
GROUP BY
class_id; -- 指定按照 class_id 进行分组
执行流程:MySQL首先遍历 students
表,根据 class_id
的值(1, 2, 3, NULL)将所有学生分成不同的组。然后,对每个组分别执行 COUNT(*)
聚合函数,最后返回每个 class_id
及其对应的计数值。
4.1.3 HAVING 子句
作用:HAVING
子句用于对 GROUP BY
分组后的结果 进行过滤。
核心区别:WHERE
vs HAVING
-
WHERE
: 在分组之前对原始表中的行进行过滤。 -
HAVING
: 在分组之后对聚合出的组进行过滤。 -
一句话总结:
WHERE
不能使用聚合函数,HAVING
可以。
示例:
SQL
-- 需求:查询学生人数超过1人的班级ID及其人数
SELECT
class_id,
COUNT(*) AS student_count
FROM
students
GROUP BY
class_id
HAVING
student_count > 1; -- 对分组后的结果(每组的学生人数)进行过滤
-- 或者直接写聚合函数
-- HAVING COUNT(*) > 1;
错误示例分析:SELECT class_id, COUNT(*) FROM students WHERE COUNT(*) > 1 GROUP BY class_id;
-- 这是错误的! 因为 WHERE
子句在 GROUP BY
之前执行,此时聚合函数 COUNT(*)
还没计算出来,所以不能在 WHERE
中使用。
4.2 联合查询 (Join Query)
当需要的数据存在于多张表中时,就需要使用 JOIN
将这些表按某种关联条件连接起来。
4.2.1 内连接 (INNER JOIN)
作用:返回两张表中能够通过连接条件匹配上的行。可以想象成取两张表的交集部分。
示例:
SQL
-- 需求:查询所有学生的名字及其所在的班级名称
SELECT
s.name AS student_name, -- 从 students 表取学生姓名
c.name AS class_name -- 从 classes 表取班级名称
FROM
students AS s -- students 表,并使用别名 s
INNER JOIN -- 使用内连接
classes AS c -- classes 表,并使用别名 c
ON
s.class_id = c.id; -- 连接条件:学生的 class_id 等于班级的 id
结果:只会显示张三、李四、王五、赵六、孙七这些已分班的学生。未分班的周八(class_id
为NULL)因为在 classes
表中找不到匹配的 id
,所以不会出现在结果中。INNER
关键字可以省略,直接写 JOIN
默认就是内连接。
4.2.2 外连接 (OUTER JOIN)
作用:除了返回匹配的行之外,还返回某一张表中不匹配的行。
-
LEFT JOIN
(左外连接): 以左边的表 (FROM
后面的第一张表) 为主,返回左表的所有行。如果右表有匹配的行,则显示;如果没有,则右表的列显示为NULL
。
示例:
SQL
-- 需求:查询所有学生的信息,无论他们是否已分班,都要显示其班级名称
SELECT
s.name AS student_name,
c.name AS class_name
FROM
students AS s -- 左表 students
LEFT JOIN -- 使用左连接
classes AS c -- 右表 classes
ON
s.class_id = c.id;
结果:会包含所有学生,包括周八。因为周八在左表 students
中存在,即使在右表 classes
中找不到匹配,他依然会被列出,只是他的 class_name
列会显示为 NULL
。
-
RIGHT JOIN
(右外连接): 与LEFT JOIN
相反,以右边的表为主。
示例:
SQL
-- 需求:查询所有班级,以及这些班级里的学生。如果某个班级没有学生,也要列出该班级。
SELECT
s.name AS student_name,
c.name AS class_name
FROM
students AS s -- 左表 students
RIGHT JOIN -- 使用右连接
classes AS c -- 右表 classes
ON
s.class_id = c.id;
结果:会包含所有班级,包括 “未分班” 这个班级,即使没有任何学生关联到它。它的 student_name
列会显示为 NULL
。
4.2.3 自连接 (Self Join)
作用:一张表与它自己进行连接。
场景:当表中的数据存在层级或递归关系时。经典例子是员工表,其中有一个 manager_id 列指向同一个表中的 employee_id。
示例: 假设我们有一个地区表,包含 id
, name
, parent_id
。
SQL
-- 查找每个地区及其上级地区的名称
SELECT
child.name AS '地区名称',
parent.name AS '上级地区'
FROM
area AS child -- 将 area 表当作“孩子”表
JOIN
area AS parent -- 将同一张 area 表当作“父母”表
ON
child.parent_id = parent.id; -- 连接条件是孩子的 parent_id 等于父母的 id
4.2.4 子查询 (Subquery)
作用:将一个 SELECT
查询语句的结果作为另一个查询语句的一部分。
-
标量子查询 (Scalar Subquery): 子查询返回单个值(一行一列)。
示例:
SQL
-- 需求:查询分数高于平均分的学生
SELECT name, score FROM students
WHERE score > (SELECT AVG(score) FROM students); -- 子查询计算出平均分这个单一值
-
列子查询 (Column Subquery): 子查询返回一列多行。通常与
IN
,ANY
,ALL
搭配使用。
示例:
SQL
-- 需求:查询“计算机科学1班”和“软件工程2班”的所有学生
SELECT name FROM students
WHERE class_id IN (SELECT id FROM classes WHERE name = '计算机科学1班' OR name = '软件工程2班');
-- 子查询返回了班级ID 1 和 2 这一列数据
-
表子查询 (Table Subquery): 子查询返回多行多列,像一张临时表。必须在
FROM
子句中使用,并且必须为其指定一个别名。
示例:
SQL
-- 需求:找出每个班级的最高分,并显示对应的学生信息
-- (这是一个复杂需求,用子查询是一种解法,虽然有更优的窗口函数解法)
SELECT s.name, temp.max_score FROM students s
JOIN (
-- 这个子查询作为一张临时表 temp
SELECT class_id, MAX(score) AS max_score FROM students GROUP BY class_id
) AS temp
ON s.class_id = temp.class_id AND s.score = temp.max_score;
4.2.5 合并查询 (UNION)
作用:将两个或多个 SELECT
语句的结果集合并成一个单一的结果集。
-
UNION
: 合并结果并自动去重。 -
UNION ALL
: 合并结果但保留所有重复行。
要求:所有 SELECT
语句必须拥有相同数量的列,且对应列的数据类型必须兼容。
示例:
SQL
-- 需求:查询1班的学生 和 分数低于80分的学生,将他们合并在一起
(SELECT name, score, class_id FROM students WHERE class_id = 1)
UNION
(SELECT name, score, class_id FROM students WHERE score < 80);
深入扩展:因为 UNION
需要进行排序和去重操作,所以它的性能通常低于 UNION ALL
。如果业务逻辑确定合并的结果不会有重复,或者允许重复,应优先使用 UNION ALL
。
五、 内容重点总结
-
约束是保证数据质量的基石,包括
NOT NULL
,UNIQUE
,DEFAULT
,PRIMARY KEY
,FOREIGN KEY
等。 -
外键 (
FOREIGN KEY
) 是构建表与表之间关系的桥梁,用于强制实现参照完整性。 -
表关系分为一对一、一对多(最常见)、多对多(需要中间表)。
-
聚合查询通过聚合函数 (
COUNT
,SUM
等) 和GROUP BY
对数据进行分组统计。 -
使用
HAVING
过滤分组后的聚合结果,WHERE
过滤分组前的原始行。 -
JOIN
是多表查询的核心:-
INNER JOIN
取交集。 -
LEFT/RIGHT JOIN
以某一张表为基准,返回其所有行。
-
-
子查询允许嵌套查询,实现更复杂的过滤逻辑。
-
UNION
/UNION ALL
用于合并多个查询的结果集。
六、 课后作业 (综合实践)
这个作业是一个非常经典的数据库设计与查询练习,涵盖了本节课的大部分核心知识点。
1. 设计表
需求分析:
-
学生 (student): 学号, 姓名, 性别
-
课程 (course): 课程编号, 课程名称
-
成绩 (score): 包含了哪个学生选了哪门课,得了多少分。这是一个典型的多对多关系的中间表。
建表SQL语句:
SQL
-- 创建作业用的数据库
CREATE DATABASE IF NOT EXISTS homework_db CHARACTER SET utf8mb4;
USE homework_db;
-- 1. 创建学生表 (student)
CREATE TABLE student (
-- 学号:整数类型,作为主键
sno INT PRIMARY KEY COMMENT '学号',
-- 姓名:可变字符串,最大长度20,不能为空
sname VARCHAR(20) NOT NULL COMMENT '姓名',
-- 性别:固定长度1的字符串,不能为空,并使用CHECK约束限制只能是'男'或'女'
ssex CHAR(1) NOT NULL COMMENT '性别',
CHECK(ssex IN ('男', '女'))
) COMMENT '学生表';
-- 2. 创建课程表 (course)
CREATE TABLE course (
-- 课程号:整数类型,作为主键
cno INT PRIMARY KEY COMMENT '课程号',
-- 课程名:可变字符串,最大长度20,不能为空
cname VARCHAR(20) NOT NULL COMMENT '课程名'
) COMMENT '课程表';
-- 3. 创建成绩表 (score) - 这是学生和课程的多对多关系中间表
CREATE TABLE score (
-- 成绩记录ID:整数,主键,自增
id INT PRIMARY KEY AUTO_INCREMENT,
-- 学号:整数,不能为空,是参照 student 表 sno 的外键
sno INT NOT NULL COMMENT '学号',
-- 课程号:整数,不能为空,是参照 course 表 cno 的外键
cno INT NOT NULL COMMENT '课程号',
-- 成绩:整数类型
degree INT COMMENT '成绩',
-- 添加外键约束
FOREIGN KEY (sno) REFERENCES student(sno),
FOREIGN KEY (cno) REFERENCES course(cno),
-- 添加组合唯一约束,确保一个学生对一门课程只能有一条成绩记录
UNIQUE(sno, cno)
) COMMENT '成绩表';
-- 插入一些测试数据
INSERT INTO student VALUES(101, '张三', '男'), (102, '李四', '女'), (103, '王五', '男');
INSERT INTO course VALUES(1, '语文'), (2, '数学'), (3, '计算机');
INSERT INTO score (sno, cno, degree) VALUES
(101, 1, 85), (101, 2, 90),
(102, 1, 92), (102, 3, 88),
(103, 1, 70);
2. 编写查询语句
a. 查询出成绩大于80分的学生的学号、姓名。
SQL
-- 分析:需要学生姓名(student表)和成绩(score表),两张表需要通过 sno 连接
SELECT
s.sno, -- 从 student 表获取学号
s.sname -- 从 student 表获取姓名
FROM
student AS s
JOIN -- 使用连接
score AS sc
ON
s.sno = sc.sno -- 连接条件是学号相等
WHERE
sc.degree > 80; -- 对成绩进行过滤
b. 查询出没有选修任何课程的学生的学号,姓名。
这个题目在给定的数据中无法实现,因为所有学生都选了课。我们先增加一个没选课的学生。
SQL
INSERT INTO student VALUES(104, '赵六', '男');
SQL
-- 分析:需要找出在 student 表中存在,但在 score 表中不存在记录的学生。
-- 这是一个典型的外连接应用场景。
SELECT
s.sno, -- 学生表的学号
s.sname -- 学生表的姓名
FROM
student AS s -- 左表 student
LEFT JOIN -- 使用左连接,保证所有学生都被列出
score AS sc
ON
s.sno = sc.sno -- 连接条件
WHERE
sc.sno IS NULL; -- 关键!如果一个学生没选课,那么在左连接后,右表(score)对应的所有列都会是NULL。
-- 我们通过判断 sc.sno 是否为 NULL 来找出这些学生。
c. 查询出每门课程的平均成绩。
SQL
-- 分析:需要按课程分组(GROUP BY),然后对每个组使用 AVG() 聚合函数。
-- 还需要课程名称(course表)和成绩(score表)。
SELECT
c.cname, -- 课程表的课程名称
AVG(sc.degree) AS average_degree -- 对每个分组的成绩求平均值
FROM
course AS c
JOIN
score AS sc
ON
c.cno = sc.cno -- 通过课程号连接
GROUP BY
c.cno, c.cname; -- 按照课程号和课程名进行分组
d. 查询出选修了“计算机”课程的学生的学号和姓名。
SQL
-- 分析:这是一个三表连接查询,需要 student, score, course。
-- student(姓名) -> score(关系) -> course(课程名)
SELECT
s.sno, -- 学生表的学号
s.sname -- 学生表的姓名
FROM
student AS s
JOIN
score AS sc ON s.sno = sc.sno -- 第一步:连接 student 和 score
JOIN
course AS c ON sc.cno = c.cno -- 第二步:连接中间结果和 course
WHERE
c.cname = '计算机'; -- 最后根据课程名进行过滤
更多推荐
所有评论(0)