四种动作: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 秒实验
- 给一张有 10 万行的表加一列
status INTEGER DEFAULT 0,那 10 万行的status是什么? RENAME COLUMN name TO title之后,name上的非空属性还在吗?在哪一列上?- 为什么说
CHANGE比RENAME COLUMN危险?
常见误区
- 加列不给默认值,以为老数据会「自动合理」。 老数据那一列全是 NULL,业务代码一读就崩。
- 用
CHANGE改列名却不重写类型。 类型被一起改掉,数据可能被截断,而且不报错。 - 删列前不看代码。 程序里
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 复制表——只复制结构,或者连数据一起。
- ☐ (本篇完,下一篇 8.6 复制表)