建表就是给数据立规矩:哪列不许空、哪列有默认值、谁是主键。 这一章讲 CREATE TABLE、ALTER TABLE、DROP TABLE 三件事。 Demo 从零建了一张供应商表,NOT NULL 当场挡住一次插入,默认值给没填的列兜底; 然后加列、改列名、删列各来一次,再删表一次、故意再删一次看它报错。
前两章改的是「表里的数据」, 这一章改的是「表本身」——结构一变,前面所有查询都可能要跟着改。 建表时的约束是后面所有章节的地基:第 1 章演示过约束拦人, 这一章终于自己动手把约束立起来。
一、创建表:每一列都要交代清楚
CREATE TABLE 表名 (列名 类型 约束, ...):每列三个部分,
类型与约束都可以省,但省了就没人替你把关。
三种常用约束:PRIMARY KEY 唯一标识一行;NOT NULL 不许空;DEFAULT 值 没给的时候用什么。
它们都在「写」的那一刻起作用。
主键在 SQLite 里有个特点:INTEGER PRIMARY KEY 就是自增,不用写 AUTOINCREMENT 也会自己加;
而 TEXT 主键允许 NULL(第 3 章验证过)。MySQL 的主键一律非空,这点两边不一样。
表建好后先看一眼结构:MySQL 用 DESCRIBE 表,SQLite 用 pragma_table_info('表')。
别凭记忆写 SQL,先看列名和顺序。
二、默认值:不填的时候给你什么
默认值只在「这一列没出现在 INSERT 里」时生效。
显式写 NULL 就是 NULL,不会退回默认值——这条最容易误会。
默认值能省掉大量重复写法:建单日期、状态位、创建时间,
这些列九成九的行都是同一个值,交给默认值最省事。
但默认值不能写「当前时间」以外的动态计算(多数引擎限制),
要动态就写 CURRENT_TIMESTAMP 这类内建值,别指望默认值里能跑函数。
三、更新表:ALTER TABLE
加列最安全:ALTER TABLE 表 ADD COLUMN 列 类型,老数据那一格自动是 NULL 或你给的默认值。
改列名:SQLite 用 ALTER TABLE 表 RENAME COLUMN 旧 TO 新;
MySQL 用 CHANGE COLUMN 旧 新 类型(改名和改类型一句话)。
改类型:MySQL 用 MODIFY COLUMN;
SQLite 没有这个语法,因为它是「弱类型」,列的类型只是个建议值。这是两边差得最远的一处。
删列:ALTER TABLE 表 DROP COLUMN 列,SQLite 3.35 起才支持,老版本要「建新表、搬数据、
删旧表、改名」四步。教材那版 MySQL 早就支持,所以书里只是一句话带过。
四、删除表与重命名表
DROP TABLE 表 连结构带数据一起删,而且多数引擎不给撤销。
它比 DELETE 彻底得多:DELETE 之后表还在,DROP 之后什么都没有了。
改名:SQLite 用 ALTER TABLE 旧 RENAME TO 新;MySQL 用 RENAME TABLE 旧 TO 新。
改名会牵连所有引用它的查询、视图、外键,别在别人正在用的库上随手改。
建表与删表都有顺序:建的时候先建被引用的表(先 vendors 后 products),
删的时候先删引用别人的表。顺序错了就是外键报错。
五、小结
教材小结: CREATE TABLE 建表要写清类型与约束,ALTER TABLE 可加列删列改列,DROP TABLE 连结构一起删, RENAME 改名。 本课补三条:一是默认值只在没出现在 INSERT 里时生效; 二是 SQLite 没有 MODIFY/CHANGE,改类型基本免谈;三是建表删表都有先后顺序,外键决定它。
六、动手 Demo:从建表到删表,一条龙
Demo 七段:建一张带约束的表、看结构、试默认值、让 NOT NULL 拦一次,然后加列改名列、删列、
改名删表,最后讲顺序。
注意 notnull 那列的 1 与 0,以及 dflt_value 里那个 'USA'。
import sqlite3
con = sqlite3.connect(":memory:")
# 从零建表:NOT NULL、DEFAULT、主键都交代清楚
con.execute("CREATE TABLE vendors2 ("
"vend_id INTEGER PRIMARY KEY, vend_name TEXT NOT NULL, "
"vend_city TEXT, vend_country TEXT NOT NULL DEFAULT 'USA', "
"vend_rating INTEGER DEFAULT 3)")
# SQLite 版的 DESCRIBE
con.execute("SELECT name, type, \"notnull\", dflt_value "
"FROM pragma_table_info('vendors2')")
# 不填国家就拿默认值,显式给 NULL 不会退回默认值
con.execute("INSERT INTO vendors2 (vend_id, vend_name) VALUES (1, 'Anvils R Us')")
# 加列、改列名、删列
con.execute("ALTER TABLE vendors2 ADD COLUMN vend_phone TEXT")
con.execute("ALTER TABLE vendors2 RENAME COLUMN vend_phone TO vend_tel")
con.execute("ALTER TABLE vendors2 DROP COLUMN vend_tel")
# 改名、删表
con.execute("ALTER TABLE tmp_demo RENAME TO tmp_demo2")
con.execute("DROP TABLE tmp_demo2")
实跑输出:
--------------------------------------------------------------------
第二十一课:创建和操纵表
--------------------------------------------------------------------
【一】从零建表:每一列都要交代清楚
SQL> CREATE TABLE vendors2 ( vend_id INTEGER PRIMARY KEY, vend_name TEXT NOT NULL,
vend_city TEXT, vend_country TEXT NOT NULL DEFAULT 'USA', vend_rating INTEGER DEFAULT 3)
完成
SQL> SELECT name, type, "notnull", dflt_value FROM pragma_table_info('vendors2')
name type notnull dflt_value
----------------------------------------------------
vend_id INTEGER 0 NULL
vend_name TEXT 1 NULL
vend_city TEXT 0 NULL
vend_country TEXT 1 'USA'
vend_rating INTEGER 0 3
(5 行)
**`pragma_table_info('表名')` 就是 SQLite 版的 `DESCRIBE 表名`**,
MySQL 直接写 `DESCRIBE vendors2`。
**notnull 那列 1 表示不许空**,dflt_value 是默认值。
【二】默认值:不填的时候给你什么
SQL> INSERT INTO vendors2 (vend_id, vend_name) VALUES (1, 'Anvils R Us')
影响 1 行
SQL> INSERT INTO vendors2 (vend_id, vend_name, vend_country) VALUES (2, 'Jet Set', 'England')
影响 1 行
SQL> SELECT * FROM vendors2
vend_id vend_name vend_city vend_country vend_rating
------------------------------------------------------------------------
1 Anvils R Us NULL USA 3
2 Jet Set NULL England 3
(2 行)
**第一家没给国家,拿到默认值 USA;第二家给了 England,默认值就不生效。**
**默认值只在「这一列没出现在 INSERT 里」时才起作用**——
**显式写 NULL 就是 NULL,不会退回默认值。**
【三】NOT NULL 是当场拦,不是事后提醒
**报错:NOT NULL constraint failed: vendors2.vend_name**
**vend_name 是 NOT NULL 又没默认值,不给就插不进去。**
**这正是约束的价值:错误发生在写的那一刻,而不是三个月后对账时。**
【四】改表:加列、删列、改名
SQL> ALTER TABLE vendors2 ADD COLUMN vend_phone TEXT
完成
SQL> SELECT name FROM pragma_table_info('vendors2')
name
-------------
vend_id
vend_name
vend_city
vend_country
vend_rating
vend_phone
(6 行)
**加列很安全**,老数据那一格自动是 NULL(或你给的默认值)。
SQL> ALTER TABLE vendors2 RENAME COLUMN vend_phone TO vend_tel
完成
SQL> SELECT name FROM pragma_table_info('vendors2')
name
-------------
vend_id
vend_name
vend_city
vend_country
vend_rating
vend_tel
(6 行)
**改列名用 `RENAME COLUMN`**,数据不动。
SQL> ALTER TABLE vendors2 DROP COLUMN vend_tel
完成
SQL> SELECT name FROM pragma_table_info('vendors2')
name
-------------
vend_id
vend_name
vend_city
vend_country
vend_rating
(5 行)
**删列 SQLite 从 3.35 起才支持**,本课这套 3.53 没问题;老版本得建新表搬数据。
【五】MySQL 那边怎么改(教材原码,不执行)
`ALTER TABLE vendors2 ADD vend_phone CHAR(20);`
`ALTER TABLE vendors2 DROP COLUMN vend_phone;`
`ALTER TABLE vendors2 MODIFY vend_name CHAR(50) NOT NULL;` **改类型用 MODIFY**
`ALTER TABLE vendors2 CHANGE vend_phone vend_tel CHAR(20);` **改名加改类型用 CHANGE**
**SQLite 没有 MODIFY 与 CHANGE**,类型是「弱类型」,改类型基本没必要;
**这一点两个引擎差得很远。**
【六】删表与改名
SQL> CREATE TABLE tmp_demo (id INTEGER PRIMARY KEY, note TEXT)
完成
SQL> ALTER TABLE tmp_demo RENAME TO tmp_demo2
完成
SQL> SELECT name FROM sqlite_master WHERE type = 'table' AND name LIKE 'tmp%'
name
----------
tmp_demo2
(1 行)
**`ALTER TABLE 旧名 RENAME TO 新名`** 就是改名;MySQL 写 `RENAME TABLE 旧 TO 新`。
SQL> DROP TABLE tmp_demo2
完成
SQL> SELECT name FROM sqlite_master WHERE type = 'table' AND name LIKE 'tmp%'
name
-----
(0 行)
**DROP TABLE 连结构一起删**,数据当然也没了,**而且多数引擎不给撤销。**
**再删一次会报错:no such table: tmp_demo2**
**这算好事**:说明引擎在提醒你「这张表已经不存在了」,而不是默默当成功。
【七】建表的顺序:先被引用的表
**有外键的表要后建。** 建 orderitems 之前 orders 与 products 必须已经存在,
**否则外键指向一张不存在的表。**
**删表反过来:先删引用别人的表。** 这条规则在真实项目里天天用得上。
第一段建表。 五列:主键、非空的名字、可空的城市、非空带默认值的国家、带默认值的评分。
第二段看结构。 notnull 给 1/0,默认值给 USA 与 3,这就是 DESCRIBE 的活儿。
第三段默认值。 第一家没给国家,拿到 USA;第二家给了 England,默认值不生效。
第四段 NOT NULL 拦人。 只给主键,名字那列不让空,插入当场失败。
第五段加列改名列。 列数从 5 变 6,再变回 5,改名的数据一点没动。
第六段改名删表。 tmp_demo 变 tmp_demo2,再被 DROP,查不到了。
第七段再删一次。 报 no such table,引擎在提醒你它已经不在了。
30 秒实验
- 给 vendors2 加一列
vend_memo TEXT DEFAULT 'n/a',再插一行看它的值。 - 把
vend_rating的默认值改成 5 需要重建表吗?为什么? - (先想再跑)直接 DROP TABLE vendors2,再查 pragma_table_info 会怎样?
常见误区
- 以为默认值能兜住显式 NULL。写了 NULL 就是 NULL,默认值不生效。
- 在 SQLite 上写 MODIFY COLUMN。它没有这个语法,改类型得重建表搬数据。
- 随手 DROP TABLE。连结构带数据一起没,多数引擎不给撤销。
自测三问(点开对答案)
1. 建表时 NOT NULL 和 DEFAULT 各管什么? —— NOT NULL 禁止空值,插入时不给值就报错;DEFAULT 在列没出现在 INSERT 里时提供值。 2. DROP TABLE 和 DELETE FROM 的区别? —— DROP 连表结构和数据一起删;DELETE 只删数据,表还在,还能继续插。 3. 为什么说建表和删表要讲顺序? —— 有外键的表必须后建(先有被引用的表),删除时要先删引用别人的表,否则外键报错。表会建了、数据会改了, 但每次都要写一长串联结太累。 下一章把常用的查询存成「视图」,起个名字反复用。
- ☐ (本章完,下一章 第22章 使用视图)