SQL-多对多关系

一、场景背景:多对多关系的业务逻辑

在实际业务中,"学生" 和 "课程" 存在典型的多对多关系:

  • 一个学生可以选择多门课程(1 个学生 → N 门课程);
  • 一门课程可以被多个学生选择(1 门课程 → N 个学生)。

这种关系无法直接通过两个表的字段关联实现(会导致数据冗余或关联混乱),因此需要引入中间表 来维护两者的关联关系。上述代码中,student(学生表)和course(课程表)是两个主体表,student_course(学生 - 课程中间表)是关联表。

二、表结构设计与核心语法解析

1. 学生表(student)

sql

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

sql 复制代码
create table course(
    id int auto_increment primary key comment '主键ID',
    name varchar(10) comment '课程名字'
)comment '课程表';
  • 结构与student表类似,核心字段为id(自增主键)和name(课程名称,未设置唯一约束,允许同名课程,具体根据业务需求调整)。
3. 中间表(student_course)

中间表是多对多关系的核心,用于记录学生和课程的关联关系:

sql

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

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

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 选,体现了多对多关系的灵活性。

四、多对多关系的查询场景(延伸)

通过中间表可以查询多对多关系的具体数据,例如:

  1. 查询 "带一丝" 选了哪些课程:

sql

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
  1. 查询 "PHP" 课程被哪些学生选了:

sql

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 谢谢从

五、总结

  1. 多对多关系设计核心:通过 "主体表 A + 主体表 B + 中间表(含 A 和 B 的外键)" 实现,避免数据冗余。
  2. 约束的作用 :
    • 主键(primary key):唯一标识记录,加速查询。
    • 自增(auto_increment):简化主键值的生成。
    • 非空(not null)和唯一(unique):保证字段数据的有效性。
    • 外键(foreign key):维护关联数据的一致性,防止无效关联。
  3. 中间表的意义:作为多对多关系的 "桥梁",存储两个主体表的关联记录,是查询关联数据的核心。

通过上述设计,MySQL 能高效维护学生与课程的多对多关系,并支持灵活的关联查询。

相关推荐
Android打工仔13 分钟前
Kotlin 协程源码解析:DispatchedContinuation 里的 Dispatcher 从哪里来?
android·kotlin
打工仔折腾 AI25 分钟前
从BPE到SentencePiece:Transformer分词原理与Python实战对比
android·人工智能·python·深度学习·langchain·transformer·ai agent 实战
布莱克6051 小时前
MySQL 增删改查(CRUD)语法详解
数据库·mysql·增删改查
Dovis(誓平步青云)1 小时前
几个方案来回选不定?做一个随时切换的候选推荐页
android·java·服务器·开发语言·javascript·数据库·智能化
rm -rf * && haha.sh3 小时前
【Linux】Rocky 9.8 操作系统磁盘分区与MySQL数据目录迁移
linux·运维·mysql
对空六课4 小时前
动态按钮ID变化时的稳定埋点标识设计
前端·数据分析
benchmark_cc4 小时前
批量行情返回后,怎样把请求失败的股票和正常数据分开?
python·数据分析·pandas·量化交易·股票数据·quantdash
ao-weilai5 小时前
MySQL数据库:表操作
数据库·mysql·adb
宵时待雨5 小时前
MySQL数据库1:数据库基础
服务器·数据库·mysql
恋猫de小郭5 小时前
KMP 又改了编译流程, Separate Compilation 禁止了 `commonMain` 的依赖穿透
android·前端·flutter