🐬 第21章 创建和操纵表
📄 一句话概括

建表就是给数据立规矩:哪列不许空、哪列有默认值、谁是主键。 这一章讲 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 秒实验

  1. 给 vendors2 加一列 vend_memo TEXT DEFAULT 'n/a',再插一行看它的值。
  2. 把 vend_rating 的默认值改成 5 需要重建表吗?为什么?
  3. (先想再跑)直接 DROP TABLE vendors2,再查 pragma_table_info 会怎样?

常见误区

  1. 以为默认值能兜住显式 NULL。写了 NULL 就是 NULL,默认值不生效。
  2. 在 SQLite 上写 MODIFY COLUMN。它没有这个语法,改类型得重建表搬数据。
  3. 随手 DROP TABLE。连结构带数据一起没,多数引擎不给撤销。
自测三问(点开对答案) 1. 建表时 NOT NULL 和 DEFAULT 各管什么? —— NOT NULL 禁止空值,插入时不给值就报错;DEFAULT 在列没出现在 INSERT 里时提供值。 2. DROP TABLE 和 DELETE FROM 的区别? —— DROP 连表结构和数据一起删;DELETE 只删数据,表还在,还能继续插。 3. 为什么说建表和删表要讲顺序? —— 有外键的表必须后建(先有被引用的表),删除时要先删引用别人的表,否则外键报错。

表会建了、数据会改了, 但每次都要写一长串联结太累。 下一章把常用的查询存成「视图」,起个名字反复用。

相关笔记