🦏 第8章 表的操作
📄 一句话概括

四种动作:rename to 改表名、add column 加列、rename column 改列名、 drop column 删列。 加列时已有行会补上默认值(Demo 里补 18),没给默认值就是 NULL; 删列连数据一起没,没有撤销。 MySQL 的 modify/change 本沙箱没有, change 必须把类型再写一遍——忘了写就会把类型改掉,这是老版本的经典事故。

📝 本章和前面几章的关系

上一篇会看结构了,这一篇开始改结构——全书风险最高的一类语句。 建表时想清楚,就是为了少走到这一篇。 真要改, 每个动作后面都跟着一句「什么时候会出事」。

一、改表名:rename to

alter table 旧名 rename to 新名,一个动作、不改数据。 Demo 里 demo_alter 变成 demo_renamed,再按 demo% 查只剩这一张。 MySQL 也支持这个写法(8.0 起更推荐 RENAME TABLE)。 改名不动数据、不动列,所以很快,几乎是瞬时的。 改名之后,原来指向它的视图、外键会怎样:多数数据库会自动更新引用, 但程序里硬编码的表名不会。 所以生产环境给表改名,等于一次「全代码搜索 + 改 + 发版」。

二、加一列:add column

alter table 表 add column 列名 类型 属性, 新列默认加在最后。 Demo 里加了 age INTEGER DEFAULT 18。 已有行的那一列会填上默认值——三行都拿到 18。 没给默认值就是 NULL,这一列对老数据全是空的。 MySQL 可以 ADD COLUMN … AFTER 某列 指定位置,本沙箱只能加在最后。 这一处差别不大: 列的顺序对查询没影响,select 时自己排就行。 靠列顺序取值的写法(select * 后按下标取)本来就是坑。

三、改列名:rename column

alter table 表 rename column 旧列名 to 新列名, 只改名、不动类型和属性。 Demo 里 name 变成 title,notnull 那个 1 还在。 MySQL 8 也支持这个写法。 MySQL 5.7 及以前没有这个语法, 改列名得用 ALTER TABLE … CHANGE 旧名 新名 类型。 而 change 必须把类型再写一遍—— 忘了写、或者写错,就会把类型一起改掉,数据可能被截断。 这是老版本的经典事故, 也是 MySQL 8 专门加 RENAME COLUMN 的原因:改名字就该只改名字。

四、删一列:drop column

alter table 表 drop column 列名, 那一列连同里面的数据一起消失,没有撤销。 Demo 里删掉 age,PRAGMA 回到两行。 MySQL 8 后面也支持这个写法(8.0.29 起正式支持,之前很受限)。 老版本只能靠重建表来删列。 删列之前一定要确认没有代码在用—— 尤其是那些 select * 的地方:列没了,程序按下标取值就会错位。 第 3 章讲过「别写星号」,这里就是它的真实代价。

五、改类型与属性:modify 与 change 这里没有

MySQL 写 ALTER TABLE 表 MODIFY 列 新类型 新属性;——只改属性。 ALTER TABLE 表 CHANGE 旧名 新名 新类型;——连名字一起改。 本沙箱(SQLite)不支持这两个, 只能用 rename column 改名,类型改不了。 这是这一篇唯一「连替代写法都没有」的地方。 SQLite 的做法是「重建表」:建一张新表、把数据 insert 过去、删旧表、改名, 官方文档就是这么建议的。 真 MySQL 上改大表的类型也要重建(ALTER 内部就是干这个), 所以「改类型很贵」这件事两边一样。

六、动手 Demo:一张两列的表,四次动手,每次回来看结构

Demo 六段:先建一张两列的表,然后改名、加列、改列名、删列,每动一刀就 PRAGMA 看一次。 最后一段撞一次 MODIFY,说明改类型这件事在这里根本做不到。

import _q46 as Q
c = Q.con()
Q.run(c, "CREATE TABLE demo_alter (id INTEGER PRIMARY KEY, name TEXT NOT NULL)")

# 改名
Q.run(c, "ALTER TABLE demo_alter RENAME TO demo_renamed")
# 加列(旧行补默认值 18)
Q.run(c, "ALTER TABLE demo_renamed ADD COLUMN age INTEGER DEFAULT 18")
# 改列名
Q.run(c, "ALTER TABLE demo_renamed RENAME COLUMN name TO title")
# 删列
Q.run(c, "ALTER TABLE demo_renamed DROP COLUMN age")

# MySQL 的 modify/change,这里没有
c.execute("ALTER TABLE demo_renamed MODIFY name VARCHAR(20)")

实跑输出:

--------------------------------------------------------------------
第三十九课:改表——改名、加列、改列、删列
--------------------------------------------------------------------

  建一张用来折腾的表
  SQL> CREATE TABLE demo_alter (id INTEGER PRIMARY KEY, name TEXT NOT NULL)
  完成

  SQL> PRAGMA table_info(demo_alter)
  cid     name     type        notnull     dflt_value     pk
  -------------------------------------------------------------------
  0       id       INTEGER     0           NULL           1
  1       name     TEXT        1           NULL           0
  (2 行)

  **两列,`id` 是主键、`name` 非空。** **下面每一段都在这张表上动一刀,随时回来看它的变化。**

  改表名
  SQL> ALTER TABLE demo_alter RENAME TO demo_renamed
  完成

  SQL> SELECT name FROM sqlite_master WHERE type = 'table' AND name LIKE 'demo%' ORDER BY name
  name
  -------------
  demo_renamed
  (1 行)

  **`alter table 旧名 rename to 新名`**,一个动作、不改数据。
  **MySQL 也支持这个写法**(8.0 起推荐 `RENAME TABLE`)。
  **改名之后,原来指向它的视图、外键会怎样**:**多数数据库会自动更新引用**,
  **但程序里硬编码的表名不会**。 **所以生产环境给表改名,等于一次「全代码搜索 + 改 + 发版」。**

  加一列
  SQL> ALTER TABLE demo_renamed ADD COLUMN age INTEGER DEFAULT 18
  完成

  SQL> PRAGMA table_info(demo_renamed)
  cid     name     type        notnull     dflt_value     pk
  -------------------------------------------------------------------
  0       id       INTEGER     0           NULL           1
  1       name     TEXT        1           NULL           0
  2       age      INTEGER     0           18             0
  (3 行)

  **`add column 列名 类型 属性`**,新列默认加在最后。
  **已有行的那一列会填上默认值**(这里 18),没给默认值就是 NULL。
  **MySQL 可以 `ADD COLUMN … AFTER 某列` 指定位置**,
  **本沙箱只能加在最后。** **这一处差别不大**——**列的顺序对查询没影响,`select` 时自己排就行。**

  改列名
  SQL> ALTER TABLE demo_renamed RENAME COLUMN name TO title
  完成

  SQL> PRAGMA table_info(demo_renamed)
  cid     name      type        notnull     dflt_value     pk
  --------------------------------------------------------------------
  0       id        INTEGER     0           NULL           1
  1       title     TEXT        1           NULL           0
  2       age       INTEGER     0           18             0
  (3 行)

  **`rename column 旧列名 to 新列名`**,只改名、不动类型和属性。 **MySQL 8 也支持这个写法。**
  **MySQL 5.7 及以前没有这个语法**,改列名得用 `ALTER TABLE … CHANGE 旧名 新名 类型`,
  **而 `change` 必须把类型再写一遍**——**忘了写就会把类型改掉,这是老版本的经典事故。**

  删一列
  SQL> ALTER TABLE demo_renamed DROP COLUMN age
  完成

  SQL> PRAGMA table_info(demo_renamed)
  cid     name      type        notnull     dflt_value     pk
  --------------------------------------------------------------------
  0       id        INTEGER     0           NULL           1
  1       title     TEXT        1           NULL           0
  (2 行)

  **`drop column 列名`**,那一列连同里面的数据一起消失,
  **没有撤销。** **MySQL 8 后面也支持这个写法**(8.0.29 起正式支持,之前很受限)。
  **删列之前一定要确认没有代码在用**——
  **尤其是那些 `select *` 的地方:列没了,程序按下标取值就会错位。**
  **第 3 章讲过「别写星号」,这里就是它的真实代价。**

  改列的类型和属性:MySQL 的 modify,这里没有
  **MySQL 写 `ALTER TABLE 表 MODIFY 列 新类型 新属性;`**——只改属性。
  **`ALTER TABLE 表 CHANGE 旧名 新名 新类型;`**——连名字一起改。
  **本沙箱(SQLite)不支持 `modify` 和 `change`**,**只能用 `rename column` 改名,类型改不了。**
  **SQLite 的做法是「重建表」**:建一张新表、把数据 `insert` 过去、删旧表、改名。
  **官方文档就是这么建议的。** **真 MySQL 上改大表的类型也要重建**(`ALTER` 内部就是干这个),
  **所以「改类型很贵」这件事两边一样。**

第一段建表,两列:id 主键、name 非空。 第二段改名,demo_alter 变 demo_renamed,按 demo% 查只剩这一张。 第三段加列,age 带默认值 18,PRAGMA 里 dflt_value 就是 18。 第四段改列名,name 变 title,非空那个 1 还在——只改了名字。 第五段删列,age 没了,PRAGMA 回到两行。 第六段撞 MODIFY,报语法错——SQLite 没有这个动作。

30 秒实验

  1. 给一张有 10 万行的表加一列 status INTEGER DEFAULT 0,那 10 万行的 status 是什么?
  2. RENAME COLUMN name TO title 之后,name 上的非空属性还在吗?在哪一列上?
  3. 为什么说 CHANGE 比 RENAME COLUMN 危险?

常见误区

  1. 加列不给默认值,以为老数据会「自动合理」。 老数据那一列全是 NULL,业务代码一读就崩。
  2. 用 CHANGE 改列名却不重写类型。 类型被一起改掉,数据可能被截断,而且不报错。
  3. 删列前不看代码。 程序里 select * 按下标取值的地方,列一没就整体错位。
自测三问(点开对答案) 1. 四种改表动作分别是什么?改完怎么确认? —— `rename to` 改表名、`add column` 加列、`rename column` 改列名、`drop column` 删列。 改完用 `PRAGMA table_info` 或 `DESCRIBE` 再看一遍。 2. 加列时老数据那一列是什么? —— 给了 `DEFAULT` 就是默认值,没给就是 NULL。Demo 里 `age` 补的是 18。 3. 本沙箱能改列的类型吗? —— 不能,`MODIFY`/`CHANGE` 都不支持,只能靠「建新表、搬数据、删旧表、改名」重建。

改表已经够麻烦了,下一篇讲一个更省事的动作:8.6 复制表——只复制结构,或者连数据一起。

相关笔记