7. 多表查询与外键
这是什么:JOIN = 查询时把两张表按条件拼起来,一次拿到「文章 + 作者名」这样的组合信息;外键(FOREIGN KEY)= 声明「这一列的值必须在另一张表里存在」。
为什么用:文章表里只存了 user_id,可页面上要显示作者名——数据分家了,查询时就得把它们重新拼上。而 SQLite 有个新手第一大坑:外键默认是关的,不打开就形同虚设。
如何用:先 JOIN 连表查询,再专门开一节收拾外键这个坑。
7.1 JOIN:文章带作者名
SELECT u.username, a.title, a.views
FROM article a
JOIN user u ON a.user_id = u.id
ORDER BY a.views DESC;
预期输出(沿用前几章的数据):
frog|从零学前端:HTML 入门|130
frog|CSS 才是网页的灵魂|85
article a 给表起短别名;ON a.user_id = u.id 说明两表怎么对上号。写法与 MySQL 的 JOIN 完全相同,LEFT JOIN 也一样可用(上一章已经用过了)。
7.2 大坑预警:外键默认关闭
第 3 章建 article 表时写了 REFERENCES user(id),但现在试试往里塞一个不存在的作者:
INSERT INTO article (title, views, user_id) VALUES ('幽灵文章', 1, 999);
SELECT COUNT(*) FROM article WHERE user_id = 999;
预期输出(居然插进去了!):
1
MySQL 里外键默认生效,SQLite 里外键默认关闭——REFERENCES 只是写了句注释,不拦任何数据。必须每个连接先执行:
PRAGMA foreign_keys = ON;
再插同样的数据,就会被拒:
Runtime error: FOREIGN KEY constraint failed
口诀:SQLite 开库两件事——打开文件,打开外键。用 Python 连接时,
connect之后第一句就执行这条 PRAGMA。
先清掉刚才的幽灵数据:
DELETE FROM article WHERE user_id = 999;
7.3 级联删除:ON DELETE CASCADE
外键打开后,还能声明「删作者时他的文章跟着删」:
CREATE TABLE comment (
id INTEGER PRIMARY KEY,
text TEXT NOT NULL,
user_id INTEGER NOT NULL REFERENCES user(id) ON DELETE CASCADE
);
这样删除一个 user,该用户的 comment 会被数据库自动清掉,不留孤儿数据。注意:建表时就得声明,事后改不了(SQLite 的 ALTER 能力很弱,改表结构通常是「建新表 → 搬数据 → 换名」)。
7.4 动手改
- 打开外键后,给 comment 插一条 user_id = 888 的评论,看报错
- 建带
ON DELETE CASCADE的 comment 表,删掉一个用户,验证他的评论也没了 - 用 LEFT JOIN 查「没有发表过任何文章的用户」
7.5 小结
- JOIN 连表查询与 MySQL 写法相同,别名 + ON 条件
- SQLite 外键默认关闭,每个连接都要
PRAGMA foreign_keys = ON - 级联规则建表时声明;SQLite 改表结构靠「建新表换名」
下一章:综合练习:用 Python 操作 SQLite 🐸