本章精髓 数据库与文件的区别,在于能查、能改、不会乱。存进文件的数据,找一条要从头翻;存进数据库,一句话就能筛出来,而且每次改动要么全部生效,要么全部不生效。这是"真数据"与"存下来的一堆字"的分界。
学完本章后,你应该能够:
- 说清数据库与文件在查找与修改上的差别,判断一份数据该用哪一种;
- 读懂一份表结构,指出哪个字段是主键、哪个字段用来关联另一张表;
- 用增、删、改、查四类语句完成一次完整的借还登记;
- 说清提交这一步的作用,并解释漏掉它会出现什么现象;
- 在动手写代码之前先确认表结构,并说清分表的理由。
成品 · 两张表 · 先要表结构
先看成品怎么用,再打开那个文件看看里面装的是什么。
1.1本章成品:社团物资借还登记
话筒、转接头、拖线板这类东西借出去容易,收回来难。成品是一个登记工具:登记借出、登记归还、查某件物资现在在谁手上、列出所有还没还回来的。数据存在一个 items.db 文件里,跟着程序走。
"还在谁手上"这一句是数据库查出来的,不是程序自己记着的
1.2那个 .db 文件里装的是什么
items.db 双击打不开,用记事本打开是乱码。但它并不神秘,里面就是两张表。运行看表脚本,能把它原样打印出来:
两张表:一张记物资,一张记借还流水。流水表里没有物资名称,只有一个 item_id,写着 1 或者 4,指向物资表里对应的那一行。归还时间那一栏是空的,就表示这件东西还没还回来。
打开 第13章演示_两张表沙盘.html,那里的两张表可以当堂点着改,看每一次操作到底动了哪一行。
1.3为什么不用一个 txt,或者一张表格
这份数据存进文本文件也不是不行。差别在往后:
写到一半程序中断,文件就废了。两个人同时开着改,后保存的会盖掉先保存的。
一次改动要么全部生效,要么全部不生效,不会留下改了一半的状态。
数据量小的时候,两边差别不明显。决定用哪一种,看的不是现在有多少条,而是这份数据将来要不要反复查询和修改。台账、登记、记录这类数据,通常一开始就该放进数据库。
1.4本章新技巧:分层生成,先要表结构
直接说"帮我做一个借还登记工具",得到的是一份可以运行的完整代码,表结构藏在里面。跑起来之后才发现字段设计不合适,这时候麻烦就来了:改代码只是改代码,改表结构却要连已经录进去的数据一起动。数据越多,返工的代价越大。
所以本章的提问分成两步。第一步只要表结构,不要任何代码,把它当成一份设计稿来读:有几张表、每张表有哪些字段、哪个是主键、哪些允许为空、为什么这样分表。读懂并确认之后,第二步才让它按这份结构写代码。中间隔着的这一次确认,是本章最值得花的时间。
1.5可直接复制的提示词
这三段提示词里的几条要求,分别对应后面要讲的知识点:
1.6大模型的常见偏差
不等确认就直接给出完整代码。 这是本章最需要防的一种。它会把表结构和代码一起交出来,你还没来得及看结构,注意力就被代码吸走了。"不要写任何 Python 代码"这句必须单独成行写清楚。
引入需要额外安装的数据库库。 它常会用功能更全的数据库工具库。这类库在正式项目里是合理选择,在本课程里的代价是要先安装,并且把一件本来单文件就能解决的事情复杂化。
用字符串拼接拼出 SQL。 拼接写起来更省事,它有时会这样给。问题是使用者输入的内容会被当成 SQL 的一部分执行。要求里写明用问号占位符,拿到代码后也要检查一遍。
漏掉提交,或者只在部分函数里提交。 常见的情形是查询函数正常、修改函数不生效。验收的办法很简单:改一条,关掉程序,重新打开再查一次。
第13章演示_建库.py 与 第13章演示_借还登记.py,表结构与第一步得到的设计稿一致。没有安装 DB Browser 的机器,用 第13章演示_看表.py 把两张表原样打印出来。完全跑不起 Python 的机器,用两个网页演示件代替:表怎么变、漏掉 WHERE 会怎样、孤儿记录长什么样,都能在网页里当堂演一遍。SQLite · 字段类型 · 主键 · 关联
本章的核心内容集中在这一部分和下一部分。
这八个术语会原样出现在数据库文档、建表语句与报错信息里。前四个在本部分讲解,后四个在第三部分讲解。
2.1四个知识点,一个一个看
两张表都在里面
用记事本打开,开头写着 SQLite format 3
现象:文件夹里多出一个 items.db,两万来字节,双击打不开。
概念:SQLite 是一个不需要安装、不需要单独启动服务的数据库,整个数据库就是磁盘上的一个文件,并且随 Python 一同提供。用文本编辑器打开它是乱码,但开头十六个字节写着 SQLite format 3,这是它的身份标记。程序和这个文件放在一起,一起拷走就能在别的机器上继续用。
类比:一本能上锁的账本,随身带得走,不必先去开一间账房。
每个字段的类型在建表时就定好了
现象:打开 items.db,看到的是两张表格,和表格软件里的样子差不多。
概念:数据库里的数据存在表(table)里,一行一条记录,一列一个字段,这一点与表格软件相同。区别在于建表时就要为每个字段规定类型:TEXT 存文字,INTEGER 存整数。类型定死之后,往整数字段里填汉字会被拒绝,数据因此不容易乱。
类比:花名册。表格软件的花名册可以随手在"年龄"栏写"保密",数据库的不行。
名称可以重复,编号不会
现象:两张表的第一列都叫 id,1、2、3 顺着排下去。
概念:主键(primary key)是用来唯一标识一条记录的字段,同一张表里绝不重复。物资可以重名,借用人可以同名,但 id 一定不同。建表时写上 AUTOINCREMENT,新增一行时由数据库自动给号。有了主键,别的地方才能准确指认某一条记录。
类比:学号。全校可能有三个张伟,学号只有一个。
流水表里不写名称,只写编号
现象:records 表里没有物资名称,只有一个 item_id,写着 1 或者 4。
概念:数据分成多张表之后,靠一个字段互相指认,这种关系叫关联,用来指过去的那个字段称为外键(foreign key)。好处在改动时才看得出来:物资改名只需改 items 表里的一处,所有借还记录跟着对。如果把名称直接抄进每一条流水,改名就要改几十行,还容易漏掉几条。
类比:成绩表上只写学号不写姓名,姓名去花名册里查。学生改名时,花名册改一次就够了。
打开 第13章演示_一张表还是两张表.html,把同一批数据用两种设计各摆一遍,然后给一件物资改个名,看两边各要改几行。这件事说一百句不如自己点一次。
2.2表结构:两张表长什么样
动手之前先把表定下来。这份建库脚本可以直接运行,运行完就得到一个装好表结构的 items.db。
import sqlite3
from pathlib import Path
DB = Path(__file__).with_name('items.db')
# ① 已有旧库先删掉,保证每次生成的内容一致
if DB.exists():
DB.unlink()
conn = sqlite3.connect(DB)
# ② 物资表:一件物资一行。id 是主键,name 不允许重复
conn.execute('''
CREATE TABLE items (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
note TEXT
)''')
# ③ 借还流水表:借一次记一行。item_id 指向 items 表的 id
conn.execute('''
CREATE TABLE records (
id INTEGER PRIMARY KEY AUTOINCREMENT,
item_id INTEGER NOT NULL,
borrower TEXT NOT NULL,
borrow_at TEXT NOT NULL,
return_at TEXT
)''')
# ④ 写入五件示例物资。问号是占位符,实际的值由后面那个括号提供
for name, note in [('无线话筒 A', '带电池'),
('无线话筒 B', ''),
('投影转接头', 'Type-C 转 HDMI'),
('拖线板', '五米'),
('三脚架', '')]:
conn.execute('INSERT INTO items (name, note) VALUES (?, ?)', (name, note))
# ⑤ 提交:不写这一行,上面做的改动全部不算数
conn.commit()
conn.close()
print('已建立', DB.name)
input('按回车键结束')
增删改查 · WHERE · commit · 孤儿记录
这一部分讲的两件事,一件让你能干活,一件让你不出事。
3.1另外四个知识点
四个词,覆盖绝大多数需求
现象:代码里出现了几个大写的英文词:SELECT、INSERT、UPDATE、DELETE。
概念:SQL(Structured Query Language,结构化查询语言)是与数据库沟通的语言。它要做的事只有四类,行业里合称增删改查。这四句都是固定句式,把表名和条件换掉就能用在别处。本章不必记住语法,能读懂一句 SQL 在动哪张表、动哪几行就够了。
类比:点菜的固定句式。句子结构不变,换的只是菜名和份数。
筛选由数据库完成,程序拿到的已经是结果
现象:查"还没还回来的"只写了一句 WHERE return_at IS NULL,屏幕上就只剩那两条。
概念:WHERE 后面跟的是筛选条件,数据库据此只把符合条件的记录交回来。IS NULL 表示这个字段是空的。本例用空的归还时间表示还没还,因此不必另设一个"是否归还"的字段:同一件事只有一处记着,就不会出现两处对不上的情况。
类比:把条件告诉图书管理员,让他找出来,不必自己一排排书架翻过去。
这是本章最容易踩的一处
现象:程序说"已登记",关掉再打开,数据却不见了。
概念:数据库的改动先记在一次事务(transaction)里,执行 commit() 才真正写进文件。这样安排是为了让一组改动要么全部生效,要么全部不生效,中途出错不会留下改了一半的状态。代价是漏写这一行时程序不会报错,数据却没有存住。检验办法只有一步:改一条,关掉程序,重新打开再查一次。
类比:转账不会只扣不加。两边都记上,这笔才算完成。
代码丢了可以重新生成,数据丢了没有办法
现象:把 .py 拷到另一台电脑,程序能跑,但一条记录都查不到。
概念:程序和数据是两样东西:代码在 .py 里,数据在 .db 里,缺一样都不成。交付时两个文件要一起给,路径也要按脚本自身所在位置去找。数据库文件同时也是最需要备份的那一个,代码丢了还能再让大模型写一份,数据丢了没有别的办法。
类比:账本和账房先生得一起走。只带走一个,另一头就停摆。
3.2四句 SQL,一句一句看
四个功能各对应一句 SQL。下面这四句就是登记工具的全部核心,其余都是外围。
-- 增:登记借出,往流水表插一行
INSERT INTO records (item_id, borrower, borrow_at) VALUES (?, ?, ?)
-- 查:这件物资现在在谁手上
SELECT records.borrower, records.borrow_at
FROM records JOIN items ON items.id = records.item_id
WHERE items.name = ? AND records.return_at IS NULL
-- 改:登记归还,把归还时间填上
UPDATE records SET return_at = ?
WHERE item_id = ? AND return_at IS NULL
-- 删:把一件物资从台账删掉
DELETE FROM items WHERE name = ?
3.3删掉一件物资,它的记录还在,却查不出来了
把还没还回来的"拖线板"从台账删掉之后,records 表里那条未归还记录依然存在,但"列出未归还"再也查不到它,因为 JOIN 到 items 表时找不到对应的行。
这是本课程里出现的第三种故障形态。第一种会报错,停下来;第二种不报错但结果不对,比如平均分偏低两分;这一种连结果都是对的,只是有一条记录从此谁也查不到。在 第13章演示_两张表沙盘.html 里点一次"删除物资",就能看见那条流水孤零零地留在表里。
定结构 · 建库 · 四种操作 · 看表
从一张纸上的表结构,到一个能拷着走的 .db 文件。
4.1演示步骤
4.2前沿三分钟:一种被装在几十亿台设备上的数据库
每章固定栏目 · 一条与本章内容相关的行业动态
本章用的 SQLite 并不是一个教学用的简化品。按部署数量算,它很可能是世界上使用最广的数据库:手机应用的本地数据、浏览器的书签与浏览记录、许多桌面软件的配置与缓存,用的都是它。原因正是它的那个特点,一个文件就是一个库,不需要单独安装和启动,嵌进任何程序里都不添麻烦。
另一件值得知道的事与保存年限有关。SQLite 的开发者把文件格式的长期兼容当成正式承诺对外声明,美国国会图书馆也把它列入推荐的数据集长期保存格式。理由是它的格式有完整的公开文档,即便将来没有任何软件支持,也能照着文档把数据读出来。
这一点对选型有实际影响:挑存储格式时,除了现在好不好用,还要问一句十年后还打不打得开。公开有文档的格式,比某个在线服务的私有格式更经得起时间。
三选一 · 六步 · 先定结构
上机环节由此开始,当堂完成并提交。
5.1三个题目,任选其一
登记借出与归还,查某件物资在谁手上,列出未归还。
通用按书名、标签、日期检索笔记,一本书可以有多条笔记。
偏文科样品编号与批次,记录状态流转,查当前处于某一状态的样品。
偏理工三个题目都要求至少两张表、有关联字段、四类操作齐全。共同点是同一个主体会有多条记录:一件物资借还多次、一本书有多条笔记、一个样品经历多个状态。允许更换题材。
5.2上机六步
5.3共同验收标准
5.4常见错误与处理
conn.commit()5.5提交内容
提交方式:按教师提供的作业模板填写,文档开头注明所选题目。提交渠道见课堂通知,当堂提交。
5.6再进一步(选做)
让数据库自己拒绝那种会把数据搞乱的删除:在建表时为关联字段加上外键约束,删除一件仍有未归还记录的物资时,由数据库直接报错,而不是让流水变成孤儿。
做完会发现一个需要权衡的问题:约束越严,录入时越麻烦;约束越松,数据越容易对不上。报废一件确实丢失的物资时,严格的约束反而会拦住你。这条线画在哪里,取决于谁来录入、录错了由谁负责,不是一个纯技术问题。
本章五条结论
- 数据库与文件的区别,在于能查、能改、不会乱。台账、登记、记录这类要反复查询和修改的数据,一开始就该放进数据库。
- 先定表结构,再写代码。代码改错了重写一遍就好,表结构改错了,已经录进去的数据也要跟着搬。
- 增删改查四句话覆盖绝大多数需求。其中 UPDATE 与 DELETE 的 WHERE 一旦写错或漏写,影响的是整张表,而且没有撤销。
- 提交不写,改动不算数。程序不报错,数据也不在。检验只有一步:改一条,关掉,重开再查。
- 程序和数据是两样东西,交付时要一起给。代码丢了还能让大模型重写一份,数据丢了没有别的办法。