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

只要结构:MySQL 写 CREATE TABLE 新表 LIKE 原表;, 结构一模一样,一行数据都不带。 本沙箱报 near "LIKE"。 本沙箱的替代是 CREATE TABLE 新表 AS SELECT * FROM 原表 WHERE 1 = 0—— 1 = 0 永远不成立,所以只搬列、不搬行。 但 AS SELECT 只搬「列名和大致类型」:Demo 里 id 从 INTEGER 变 INT, 主键、非空、默认值、索引全丢了。 这是它和 like 最大的区别。

📝 本章和前面几章的关系

改表贵、删表险,复制表是这两件事之间的缓冲:先在副本上试、先拿快照再动手。 这一篇的重点不是「怎么复制」,是「复制过来丢了什么」。 很多人拿 AS SELECT 当正式备份,恢复回去才发现表是「裸」的。

一、只复制结构:create table ... like

CREATE TABLE 新表 LIKE 原表;—— 结构一模一样,包括主键、非空、默认值、索引,但一行数据都不带。 本沙箱没有这个语法,报 near "LIKE": syntax error。 教材原码照原样记。 这个写法最典型的用途是「造一张空壳做试验」: 结构和原表完全一致,往里插数据不会踩到「列类型不对」这种问题。 第 8.1 篇说的「先在副本上试一遍」,用它造副本最干净。

二、本沙箱的替代:as select 加个假条件

CREATE TABLE copy_struct AS SELECT * FROM classes WHERE 1 = 0—— where 1 = 0 让 select 一行都不返回,于是新表只有结构、没有数据。 Demo 里 copy_struct 是 0 行。 这一招在 SQLite 里就是 like 的替代品, 几乎所有「我要一张空表但不想写一遍列定义」的场合都能用。 它和 like 有一个关键差别,看 PRAGMA 的输出就明白——下一段专门讲。

三、as select 丢了什么:这一篇最该记住的

看 copy_struct 的 PRAGMA 输出:id 的类型变成 INT(原来是 INTEGER), pk 是 0,notnull 也全是 0。 也就是说:AS SELECT 只复制「列名和大致类型」,主键、非空、默认值、索引全丢了。 这是它和 create table … like 最大的区别。 后果是具体的:副本上可以插重复的 id(没主键了),可以插 NULL 进 name(非空没了), 查起来也慢(索引没了)。 拿它当「临时快照」够用,当「正式备份」不够——恢复回去时表是「裸」的。

四、结构加数据:直接 as select

CREATE TABLE copy_all AS SELECT * FROM classes, 三行数据都过来了。 这是「备份一张小表」最快的方式,第 7 章做备份用的就是它。 同样要记住:约束和索引没过来。 真要「备份还能原样恢复」,得走导出工具: MySQL 是 mysqldump,它会连建表语句一起导出,恢复时先 create table 再 insert。 第 17 章讲备份。 判断标准很简单: 这份数据用来「临时看一眼」,AS SELECT 够;用来「出事时恢复」,必须用导出工具。

五、带条件复制:只搬一部分行

AS SELECT id, name, height FROM students WHERE gender = '女', 搬过来 6 行,而且只有三列。 select 里挑哪几列,新表就只有哪几列—— 这也是 AS SELECT 比 like 灵活的地方。 做「给某部门导一份数据」这种活儿,一句就够。 要导成文件给别人,再套一层导出工具(SELECT … INTO OUTFILE,本沙箱没有)。 注意 INTO OUTFILE 在 MySQL 里默认只能写到服务器本机,还要开 secure_file_priv, 不是随便就能用的。

六、复制表要留意的三件事

一、自增和主键不会跟过来(用 AS SELECT 时)。 往副本里插数据,可能插出重复的 id。 真要复制自增属性,得手动重建表结构。 二、索引不会跟过来。 副本上查得慢,不代表原表慢——别拿副本的性能当结论。 反过来也成立:在副本上测出来的「索引优化效果」,回原表可能完全不同。 三、外键也不会跟过来。 副本是一张独立的表,它和原表之间没有任何关联,改副本不影响原表, 反过来也一样。 这一点在造「测试数据」时是好事,在「迁移数据」时是坑。

七、动手 Demo:三种复制:只要结构、结构加数据、带条件挑列

Demo 四段:撞一次 like,用 AS SELECT 造一张空壳,再复制一张带数据的、一张带条件挑列的。 第二段后面那个 PRAGMA 是重点——它把「丢掉的约束」一条一条摆出来了。

import _q46 as Q
c = Q.con()

# 1) 只要结构:like 是 MySQL 的,这里报错
c.execute("CREATE TABLE copy_like LIKE classes")

# 2) 本沙箱的替代:where 1 = 0
Q.run(c, "CREATE TABLE copy_struct AS SELECT * FROM classes WHERE 1 = 0")
Q.show(c, "PRAGMA table_info(copy_struct)")

# 3) 结构加数据
Q.run(c, "CREATE TABLE copy_all AS SELECT * FROM classes")

# 4) 只搬一部分行,只挑几列
Q.run(c, "CREATE TABLE copy_girl AS SELECT id, name, height "
          "FROM students WHERE gender = '女'")

实跑输出:

--------------------------------------------------------------------
第四十课:复制表——只复制结构,还是连数据一起
--------------------------------------------------------------------

  只复制结构:MySQL 的 create table ... like
  **MySQL 写**:
  `CREATE TABLE 新表 LIKE 原表;`——
  **结构一模一样,包括主键、非空、默认值、索引,但一行数据都不带。**
  SQL> CREATE TABLE copy_like LIKE classes
  报错:near "LIKE": syntax error

  **`create table … like` 是本沙箱没有的语法**,报 `near "LIKE"`。 **教材原码照原样记。**

  本沙箱的等价写法:AS SELECT 加个假条件
  SQL> CREATE TABLE copy_struct AS SELECT * FROM classes WHERE 1 = 0
  完成

  SQL> SELECT COUNT(*) AS rows_in_copy FROM copy_struct
  rows_in_copy
  -------------
  0
  (1 行)

  SQL> PRAGMA table_info(copy_struct)
  cid     name        type     notnull     dflt_value     pk
  -------------------------------------------------------------------
  0       id          INT      0           NULL           0
  1       name        TEXT     0           NULL           0
  2       teacher     TEXT     0           NULL           0
  3       room        TEXT     0           NULL           0
  (4 行)

  **`WHERE 1 = 0` 让 `select` 一行都不返回,于是新表只有结构、没有数据。**
  **这一招在 SQLite 里就是 `like` 的替代品。**
  **但它和 `like` 有一个关键差别**:看 `PRAGMA` 的输出,
  **`id` 的类型变成了 `INT`,`pk` 是 0,`notnull` 也全是 0。**
  **也就是说:`AS SELECT` 只复制「列名和大致类型」,主键、非空、默认值、索引全丢了。**
  **这是它和 `create table … like` 最大的区别。**

  结构加数据:直接 AS SELECT
  SQL> CREATE TABLE copy_all AS SELECT * FROM classes
  完成

  SQL> SELECT id, name, teacher FROM copy_all ORDER BY id
  id     name     teacher
  ------------------------
  1      一班     马老师
  2      二班     孔老师
  3      三班     曹老师
  (3 行)

  **三行数据都过来了。** **这是「备份一张小表」最快的方式**,第 7 章做备份用的就是它。
  **同样要记住:约束和索引没过来。** **拿它当「临时快照」够用,当「正式备份」不够**——
  恢复回去时表是「裸」的。

  带条件复制:只搬一部分行
  SQL> CREATE TABLE copy_girl AS SELECT id, name, height FROM students WHERE gender = '女'
  完成

  SQL> SELECT id, name, height FROM copy_girl ORDER BY id
  id     name     height
  -----------------------
  2      王芳     160.20
  3      李娜     165.00
  5      陈静     158.60
  7      赵敏     162.80
  10     吴倩     163.50
  12     孙悦     161.40
  (6 行)

  **`select` 里挑哪几列,新表就只有哪几列**——**这也是 `AS SELECT` 比 `like` 灵活的地方。**
  **做「给某部门导一份数据」这种活儿,一句就够。**
  **要导成文件给别人,再套一层导出工具(`SELECT … INTO OUTFILE`,本沙箱没有)。**

  复制表要留意的三件事
  **一、自增和主键不会跟过来**(用 `AS SELECT` 时)。 **往副本里插数据,可能插出重复的 id。**
  **二、索引不会跟过来。** 副本上查得慢,不代表原表慢——**别拿副本的性能当结论。**
  **三、外键也不会跟过来。** 副本是一张独立的表,**它和原表之间没有任何关联**,
  改副本不影响原表(反过来也一样)。

第一段 like 报 near "LIKE",SQLite 不认这个语法。 第二段 WHERE 1 = 0 造出 0 行的空壳,但 PRAGMA 显示 id 变 INT、pk 是 0、 notnull 全 0——约束全丢了。 第三段复制整表,三行都在:一班、二班、三班。 第四段挑列带条件,搬过来 6 个女生、只有 3 列(id/name/height)。

30 秒实验

  1. CREATE TABLE bak AS SELECT * FROM classes 之后,bak 上有主键吗?怎么验证?
  2. 要造一张「结构和 classes 完全一样、但没有数据」的表,本沙箱该写什么?
  3. 为什么不能拿 AS SELECT 的副本当正式备份?

常见误区

  1. 把 AS SELECT 的副本当正式备份。 主键、非空、索引全丢,恢复回去是一张「裸表」, 数据质量全靠运气。
  2. 在副本上测性能,把结论套回原表。 副本没索引,慢是必然的,不代表原表也慢。
  3. 以为副本和原表还连着。 复制之后就完全独立了,改一边不影响另一边,别以为在「同步」。
自测三问(点开对答案) 1. `CREATE TABLE 新表 LIKE 原表` 复制了什么?没复制什么? —— 复制完整结构(列、类型、主键、非空、默认值、索引),不复制任何数据。本沙箱不支持这个语法。 2. `AS SELECT` 复制表的时候丢了哪些东西? —— 主键、非空、默认值、索引、自增属性都丢了,只留下列名和大致类型。 3. 「临时快照」和「正式备份」的区别在哪? —— 快照只要能读、能对比,用 `AS SELECT` 就够; 正式备份要能原样恢复,必须用 `mysqldump` 这类导出工具。

复制是为了留后路,下一篇是终点:8.7 删除表——最该慢一步的动作。

相关笔记