(十二)从记录列表到关系表
1. 把重复信息分到对应的表
现在希望查询“每本书读了多久”“有基础标签的书有哪些”。JSON 中可以保存嵌套列表,但每次查找都要重新读入并遍历。可以把不同记录放进关系表,再让数据库按条件取得需要的结果。
books:一本书一行 |
标题 | 完成状态 |
|---|---|---|
id=1 |
Python | 0 |
id=2 |
NumPy | 1 |
id=3 |
Pygame | 0 |
reading_log:一次阅读一行 |
所属书籍 book_id |
分钟数 |
|---|---|---|
id=1 |
1 | 20 |
id=2 |
1 | 35 |
id=3 |
2 | 45 |
books.id 是主键,标识一行书籍;reading_log.book_id 是外键,指向对应的书籍。一次阅读不再重复保存完整书名,改标题后也不用逐条修改历史日志。
一本书有多次阅读,是一对多关系。一本书有多个标签,同一个标签也可以出现在多本书上,项目用 book_tags(book_id, tag) 保存对应关系,并用两个字段组成联合主键,阻止同一本书重复加入相同标签。
2. 用表结构表达记录规则
SQL 是描述表结构与数据操作的语言。先看建表时写下的规则:
-- 一本书一个稳定编号,本例把标题作为不可重复的书目名称。
CREATE TABLE books (
id INTEGER PRIMARY KEY,
title TEXT NOT NULL UNIQUE,
finished INTEGER NOT NULL DEFAULT 0 CHECK (finished IN (0, 1))
);
-- 一次阅读对应已有书籍,分钟数保存为非负整数。
CREATE TABLE reading_log (
id INTEGER PRIMARY KEY,
book_id INTEGER NOT NULL REFERENCES books(id) ON DELETE CASCADE,
day TEXT NOT NULL,
-- typeof 检查实际存储类型,再与非负判断一起执行。
minutes INTEGER NOT NULL CHECK (typeof(minutes) = 'integer' AND minutes >= 0)
);NOT NULL 表示这一列要有值;UNIQUE 表示不同记录的该值不重复;DEFAULT 在插入省略该列时给出初值;CHECK 检查写入值。ON DELETE CASCADE 表示删除书籍时同时删除它的日志。本例的标题唯一是这份小书目程序采用的识别规则,实际区分同名不同版本时,可以另加版本或书目编号。
SQLite 常用存储类型有 INTEGER、REAL、TEXT、BLOB 和 NULL。布尔状态在这里存成 0/1,日期按约定存 ISO 文本;Python 侧再恢复成相应对象。声明、约束与具体转换一起构成记录结构。