SQL-多对多关系
一、场景背景:多对多关系的业务逻辑
在实际业务中,“学生” 和 “课程” 存在典型的多对多关系:
- 一个学生可以选择多门课程(1 个学生 → N 门课程);
- 一门课程可以被多个学生选择(1 门课程 → N 个学生)。
这种关系无法直接通过两个表的字段关联实现(会导致数据冗余或关联混乱),因此需要引入中间表来维护两者的关联关系。上述代码中,student
(学生表)和course
(课程表)是两个主体表,student_course
(学生 - 课程中间表)是关联表。
二、表结构设计与核心语法解析
1. 学生表(student
)
sql
create table student(id int auto_increment primary key comment '主键ID',name varchar(10) not null unique comment '名字',no varchar(10) comment '学号' -- 注:原代码注释有误,应为“学号”
)comment '学生表';
- 核心字段解析:
id
:整数类型(int
),作为主键(primary key
),且设置auto_increment
(自增)。- 主键(
primary key
):唯一标识表中的每条记录,确保记录不重复,且加速查询(主键会自动创建索引)。 - 自增(
auto_increment
):插入数据时无需手动指定id
值,MySQL 会自动生成唯一的递增数值(默认从 1 开始),简化插入操作。
- 主键(
name
:字符串类型(varchar(10)
,最长 10 个字符),设置not null
(非空,必须填写)和unique
(唯一,不允许重复的学生姓名)。no
:字符串类型,注释为 “学号”(原代码注释笔误),未设置非空约束,允许为空。
comment
:为表和字段添加注释,提高代码可读性(可通过show full columns from 表名;
查看)。
2. 课程表(course
)
sql
create table course(id int auto_increment primary key comment '主键ID',name varchar(10) comment '课程名字'
)comment '课程表';
- 结构与
student
表类似,核心字段为id
(自增主键)和name
(课程名称,未设置唯一约束,允许同名课程,具体根据业务需求调整)。
3. 中间表(student_course
)
中间表是多对多关系的核心,用于记录学生和课程的关联关系:
sql
create table student_course(id int auto_increment primary key comment '主键',student_id int not null comment '学生ID',course_id int not null comment '课程ID',constraint fk_student_id foreign key (student_id) references student(id),constraint fk_course_id foreign key (course_id) references course(id)
)comment '学生课程中间表';
核心设计逻辑:
- 包含两个外键字段:
student_id
(关联学生表的id
)和course_id
(关联课程表的id
),通过这两个字段建立学生和课程的关联。 - 自身主键
id
:中间表也可以用student_id
和course_id
作为联合主键(primary key(student_id, course_id)
),但此处用自增id
作为主键,更便于后续对关联记录的单独操作(如删除某条选课记录)。
- 包含两个外键字段:
外键约束(
foreign key
)详解:外键是维护数据完整性的核心约束,用于确保关联字段的值必须在被关联表中存在。constraint fk_student_id
:为外键约束命名(便于后续删除或修改约束)。foreign key (student_id) references student(id)
:表示student_id
的值必须是student
表中已存在的id
(否则插入 / 更新会失败)。- 同理,
course_id
必须是course
表中已存在的id
。 - 作用:避免 “无效关联”(如关联一个不存在的学生或课程),保证数据一致性。
三、数据插入(insert
)操作解析
通过insert
语句向表中插入数据,验证多对多关系的实际存储:
1. 向学生表插入数据
sql
insert into student values (null, '带一丝','2000100109'),(null, '谢谢从','2000100107'),(null, '带二四','2000100105'),(null, '带三丝','2000100101');
values
后的null
对应自增主键id
,MySQL 会自动生成1、2、3、4
(按插入顺序)。- 插入后学生表数据:
id | name | no |
---|---|---|
1 | 带一丝 | 2000100109 |
2 | 谢谢从 | 2000100107 |
3 | 带二四 | 2000100105 |
4 | 带三丝 | 2000100101 |
2. 向课程表插入数据
sql
insert into course values (null, 'mysql'),(null,'PHP'),(null, 'Java'),(null, 'Hadoop');
- 课程表
id
自动生成1、2、3、4
,对应课程名:
id | name |
---|---|
1 | mysql |
2 | PHP |
3 | Java |
4 | Hadoop |
3. 向中间表插入关联数据
sql
insert into student_course values (null,1,1 ),(null,1,2),(null, 2,2),(null, 2,3),(null,3,4);
每条记录代表 “某个学生选了某门课”:
(null,1,1)
:学生 1(带一丝)选了课程 1(mysql);(null,1,2)
:学生 1 选了课程 2(PHP);(null,2,2)
:学生 2(谢谢从)选了课程 2(PHP);(null,2,3)
:学生 2 选了课程 3(Java);(null,3,4)
:学生 3(带二四)选了课程 4(Hadoop)。
插入后中间表数据:
id | student_id | course_id |
---|---|---|
1 | 1 | 1 |
2 | 1 | 2 |
3 | 2 | 2 |
4 | 2 | 3 |
5 | 3 | 4 |
- 注意:学生 4(带三丝)未选课,课程 1(mysql)仅学生 1 选,课程 4(Hadoop)仅学生 3 选,体现了多对多关系的灵活性。
四、多对多关系的查询场景(延伸)
通过中间表可以查询多对多关系的具体数据,例如:
- 查询 “带一丝” 选了哪些课程:
sql
select s.name, c.name
from student s
join student_course sc on s.id = sc.student_id
join course c on sc.course_id = c.id
where s.name = '带一丝';
结果:
name | name |
---|---|
带一丝 | mysql |
带一丝 | PHP |
- 查询 “PHP” 课程被哪些学生选了:
sql
select c.name, s.name
from course c
join student_course sc on c.id = sc.course_id
join student s on sc.student_id = s.id
where c.name = 'PHP';
结果:
name | name |
---|---|
PHP | 带一丝 |
PHP | 谢谢从 |
五、总结
- 多对多关系设计核心:通过 “主体表 A + 主体表 B + 中间表(含 A 和 B 的外键)” 实现,避免数据冗余。
- 约束的作用:
- 主键(
primary key
):唯一标识记录,加速查询。 - 自增(
auto_increment
):简化主键值的生成。 - 非空(
not null
)和唯一(unique
):保证字段数据的有效性。 - 外键(
foreign key
):维护关联数据的一致性,防止无效关联。
- 主键(
- 中间表的意义:作为多对多关系的 “桥梁”,存储两个主体表的关联记录,是查询关联数据的核心。
通过上述设计,MySQL 能高效维护学生与课程的多对多关系,并支持灵活的关联查询。