📄 一句话概括
主键唯一标识表里的每一行,外键存着别家表的主键——拆开的表靠这一对键随时拉手拼回来。
📝 本节和前面几节的关系
上一节把表拆开了,本节回答拆开之后的第一个问题:两张表靠什么互相指认?
一、主键:表里的身份证
主键是能唯一标识一行的那一列(或几列):学号之于学生、订单号之于订单。 两条铁律:唯一(不许重复)、非空(不许缺)——DBMS 亲自把关,想插重复的学号会被当场拒绝。 主键一旦定下,就是这张表对外的【身份证号】——别的表认它,人也可以认它。
二、外键:抄一份身份证号去拉手
外键是 A 表里存着 B 表主键的那一列:借书记录表存学生号,就指向了学生表。 外键表达了【这条记录属于谁】——记录 3 属于学生 2,一读就懂; DBMS 还能用外键约束拦住孤儿数据:指向不存在的学生号的记录,插不进去。
三、关系拼回来
拆表时用编号指过去,查询时按编号拼回来——这个动作就是连接(JOIN); 借书记录表 JOIN 学生表,出来的是【借书记录+对应姓名】的完整视图; 拆得开、拼得回,是关系型数据库名字里【关系】二字的来历。
四、动手 Demo:两表靠主键外键拉手
建学生表与借书记录表,用外键关联,JOIN 查出带姓名的借书记录。
import sqlite3
con = sqlite3.connect(":memory:")
con.execute("CREATE TABLE student (id INTEGER PRIMARY KEY, name TEXT)")
con.execute("CREATE TABLE loan (id INTEGER PRIMARY KEY, "
"student_id INTEGER REFERENCES student(id), book TEXT)")
con.executemany("INSERT INTO student VALUES (?, ?)", [(1, "波仔"), (2, "小墨")])
con.executemany("INSERT INTO loan VALUES (?, ?, ?)",
[(1, 1, "三国演义"), (2, 2, "红楼梦")])
sql = ("SELECT student.name, loan.book FROM loan "
"JOIN student ON loan.student_id = student.id")
for row in con.execute(sql):
print(row)
实跑输出:
--------------------------------------------------------------------
用主键和外键在表间建立关系
--------------------------------------------------------------------
拆完表, 两张表靠什么拉手? 靠主键和外键。真建两张表看看:
查一次【连带姓名的借书记录】(两表按外键拉手):
借书记录1 波仔 《三国演义》
借书记录2 波仔 《数据结构》
借书记录3 小墨 《红楼梦》
键 在哪张表 作用
---------------------------------------------------------
主键 student.id 唯一标识一名学生, 不许重复
外键 loan.student_id 指向 student.id, 记录属于谁
主键是【身份证号】, 外键是【户口本上写的身份证号】——
有了它俩, 拆开的表还能随时拼回来查询。
JOIN 的条件 loan.student_id = student.id 就是外键指向主键的那根线——关系就是这样拼回的。
外键让【拆表】不再有代价:平时各存各的,要拼时一句 JOIN 就行。
30 秒实验
- 给【班级-学生】设计两张表,标出哪列是主键、哪列是外键。
- 故意插一条外键指向不存在学生的记录,看看 DBMS 拦不拦。
常见误区
- 以为外键就是又存了一遍学生号——存的是【引用】,学生信息仍只在学生表里一份。
- 以为主键可以随便选一列——它要唯一且稳定,会变的列当主键后患无穷。
自测三问(点开对答案)
1. 主键的两条铁律? —— 唯一、非空——DBMS 亲自把关。 2. 外键存在哪里、指向哪里? —— 存在 B 表里,存的是 A 表主键的值,指向 A 表。 3. 拆开的表靠什么拼回来? —— JOIN——按外键指向主键的条件连接两表。- ☐ 下一节(8.6 索引能够提升数据的检索速度)
相关笔记
- LX_CH8_84 — 上一节拆开的表,本节靠键拼回来
- LX_CH6_64 — 表间关系与结构体的打包思路一脉
- 💻 计算机是怎样跑起来的 · 索引