2.2 PRIMARY KEY、FOREIGN KEY 与 JOIN
关系型数据库强大的地方,不只是能存多张表,而是能让表之间的关系保持清楚、可检查、可查询。
这一节用三张表贯穿:
students:学生资料。courses:课程资料。enrollments:报名记录,连接学生和课程。
PRIMARY KEY:先让每一行有身份
主键唯一标识表中的一行:
sql
CREATE TABLE students (
student_id INTEGER PRIMARY KEY,
name VARCHAR(100) NOT NULL
);主键应该稳定、唯一、非空。不要用容易变化的业务字段做主键,例如昵称。
text
students
+------------+------+
| student_id | name |
+------------+------+
| 1 | Ada |
| 2 | Bo |
+------------+------+
^
|
primary key正在加载概念检查...
FOREIGN KEY:写入时检查父表是否存在
外键引用另一张表的主键。报名表里的 student_id 必须能在 students.student_id 里找到,course_id 也必须能在 courses.course_id 里找到。
text
students.student_id <--- enrollments.student_id
courses.course_id <--- enrollments.course_idsql
CREATE TABLE enrollments (
student_id INTEGER,
course_id INTEGER,
enrolled_on DATE,
FOREIGN KEY (student_id) REFERENCES students(student_id),
FOREIGN KEY (course_id) REFERENCES courses(course_id)
);如果你插入 student_id = 42,但 students 表里没有 42,外键检查会拒绝这条报名记录。这样可以减少孤立数据。
产品差异提醒:外键约束在不同产品中的默认启用方式、级联删除语法、延迟检查能力不同。SQLite 需要确认 PRAGMA foreign_keys = ON;MySQL 需要注意存储引擎;PostgreSQL 外键能力较完整。
正在加载交互实验...
JOIN:查询时把匹配的行拼成结果
外键负责“写入时能不能指向存在的行”。JOIN 负责“查询时如何把相关行放到同一张结果表里”。二者经常一起出现,但不是同一件事。
先看一条报名记录:
text
enrollments row
+------------+-----------+------------+
| student_id | course_id | enrolled_on|
+------------+-----------+------------+
| 1 | 101 | 2026-01-12 |
+------------+-----------+------------+
| |
| +--> courses.course_id = 101 -> SQL Basics
+----------------> students.student_id = 1 -> Ada第一步,只把报名记录连到学生:
sql
SELECT e.enrolled_on, s.name
FROM enrollments AS e
JOIN students AS s
ON s.student_id = e.student_id;ON 条件说明两张表如何匹配。不是“列名一样就自动懂”,而是你明确告诉数据库:e.student_id 的值要等于 s.student_id。
第二步,再把同一条报名记录连到课程:
sql
SELECT s.name, c.title, e.enrolled_on
FROM enrollments AS e
JOIN students AS s
ON s.student_id = e.student_id
JOIN courses AS c
ON c.course_id = e.course_id;这条查询从 enrollments 这张桥表出发。每一条报名记录先找学生,再找课程,最后输出一行“学生 + 课程 + 报名日期”。
text
students enrollments courses
Ada <--- student_id=1
course_id=101 ---> SQL Basics
Bo <--- student_id=2
course_id=102 ---> Data ModelingINNER JOIN 与 LEFT JOIN
JOIN 默认通常表示 INNER JOIN:只保留能匹配上的行。
如果你从 courses 出发,想列出所有课程,包括还没有人报名的课程,就用 LEFT JOIN:
sql
SELECT c.course_id, c.title, s.name, e.enrolled_on
FROM courses AS c
LEFT JOIN enrollments AS e
ON e.course_id = c.course_id
LEFT JOIN students AS s
ON s.student_id = e.student_id;结果会像这样:
text
+-----------+---------------+------+
| course_id | title | name |
+-----------+---------------+------+
| 101 | SQL Basics | Ada |
| 105 | Warehousing | NULL |
+-----------+---------------+------+NULL 不是字符串“NULL”,而是表示右侧没有匹配行。
正在加载交互实验...
正在加载概念检查...
JOIN 的常见错误
- 忘记
ON条件,导致行数爆炸。 - 用了错误的列连接,例如把
student_id连到course_id。 - 以为外键会自动替你写 JOIN;外键是约束,JOIN 是查询。
- 不理解
NULL,误读 LEFT JOIN 结果。 - 在
WHERE里过滤右表列,把 LEFT JOIN 变成类似 INNER JOIN 的效果。
正在加载本节练习...