再啰嗦一下哈。我们也来详细过一遍怎么一步步搞定 MANY-TO-MANY 结构。准备好了吗?
MANY-TO-MANY 关系在学生和课程之间,没法直接用一张表搞定。一个学生可以报好几个课程,一个课程也可以被好几个学生选。
为了解决这个问题,我们建一个中间表 enrollments,用来存报名信息,也就是哪对学生-课程是存在的。这样不仅能保证数据完整性,还能很方便地扩展功能,比如加个报名日期啥的。
enrollments 表长啥样?
你已经知道,enrollments 表是 students 和 courses 两张表之间的核心连接点。结构如下:
CREATE TABLE enrollments (
enrollment_id SERIAL PRIMARY KEY, -- 唯一ID记录
student_id INT REFERENCES students(student_id), -- 指向 students 表的外键
course_id INT REFERENCES courses(course_id), -- 指向 courses 表的外键
enrollment_date DATE DEFAULT CURRENT_DATE -- 学生报名课程的日期
);
来拆解一下每一行:
enrollment_id:这是每条记录的唯一标识。每个报名课程的学生都得有唯一ID。student_id:表示哪个学生报名了。这是个外键,指向students表(student_id列)。course_id:表示学生报了哪个课程。这个字段关联courses表(course_id列)。enrollment_date:很实用的补充,显示报名日期。我们用DEFAULT CURRENT_DATE,这样插入记录时会自动写上当天日期。
创建 students 和 courses 表
在继续之前,先确保我们已经有 students 和 courses 这两张表:
CREATE TABLE students (
student_id SERIAL PRIMARY KEY, -- 学生唯一标识
name TEXT NOT NULL, -- 学生姓名
email TEXT NOT NULL UNIQUE -- 学生邮箱,不能重复
);
CREATE TABLE courses (
course_id SERIAL PRIMARY KEY, -- 课程唯一标识
title TEXT NOT NULL, -- 课程名称
description TEXT, -- 课程描述
start_date DATE -- 课程开始日期
);
注意,这里我们加了一些有用的细节,比如学生邮箱唯一、课程表里有描述字段。
把一切串起来
现在表都准备好了,来建 enrollments 表:
CREATE TABLE enrollments (
enrollment_id SERIAL PRIMARY KEY, -- 报名唯一ID
student_id INT NOT NULL REFERENCES students(student_id), -- 外键
course_id INT NOT NULL REFERENCES courses(course_id), -- 外键
enrollment_date DATE DEFAULT CURRENT_DATE -- 报名日期
);
往表里插数据
表都建好了,但没数据多无聊。来加点学生、课程和报名记录:
插入学生:
INSERT INTO students (name, email)
VALUES
('Alex Lin', 'alex.lin@example.com'),
('Maria Chi', 'maria.chi@example.com'),
('Otto Song', 'otto.song@example.com');
插入课程:
INSERT INTO courses (title, description, start_date)
VALUES
('编程基础', '给初学者的编程课程。', '2023-11-01'),
('数据库', '学习 SQL 和关系型数据库。', '2023-11-15'),
('Web开发', '做网站和 Web 应用。', '2023-12-01');
插入报名记录:
INSERT INTO enrollments (student_id, course_id)
VALUES
(1, 1), -- Alex Lin 报了“编程基础”
(1, 2), -- Alex Lin 报了“数据库”
(2, 2), -- Maria Chi 报了“数据库”
(3, 3); -- Otto Song 报了“Web开发”
这里 student_id 和 course_id 跟对应表里的ID一致。
用查询检查关联
查所有报名:
SELECT e.enrollment_id, s.name AS student_name, c.title AS course_title, e.enrollment_date
FROM enrollments e
JOIN students s ON e.student_id = s.student_id
JOIN courses c ON e.course_id = c.course_id;
结果:
| enrollment_id | student_name | course_title | enrollment_date |
|---|---|---|---|
| 1 | Alex Lin | 编程基础 | 2023-11-01 |
| 2 | Alex Lin | 数据库 | 2023-11-01 |
| 3 | Maria Chi | 数据库 | 2023-11-01 |
| 4 | Otto Song | Web开发 | 2023-11-01 |
动手练习题
你可以再加几个学生和课程,然后把他们报名到 enrollments 表里。比如加个课程“机器学习”,让1-2个学生报名。用上面的查询查查结果。
常见的坑
用外键和中间表时有几个常见的坑,大家经常踩:
父表里没记录: 如果你往
enrollments插student_id或course_id,但students或courses表里没有对应的ID,就会报错。外键会死死盯着这个。删数据导致数据不完整: 如果你删了某个学生或课程,而他们还在
enrollments表里被用着,又没设置ON DELETE CASCADE,也会报错。重复记录: 注意别让同一个学生重复报名同一个课程,除非业务逻辑允许。
现在你已经有了一个能用的模型,来表示 PostgreSQL 里学生和课程的 MANY-TO-MANY 关系。这种结构在实际项目里超常见,比如学习管理系统、CRM 等等。冲下节课吧!
GO TO FULL VERSION