<audio id="audio" title="03 | 表：怎么创建和修改表？" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/7d/4f/7d74a01588b6a686fa489e7b7d1c144f.mp3"></audio>

你好，我是朱晓峰。今天，我们来聊一聊怎么创建和修改数据表。

创建和修改数据表，是数据存储过程中的重要一环。我们不仅需要把表创建出来，还需要正确地设置限定条件，这样才能确保数据的一致性和完整性。同时，表中的数据会随着业务需求的变化而变化，添加和修改相应的字段也是常见的操作。这节课，我们就来学习下具体的方法。

在我们的超市项目里，客户经常需要进货，这就需要在MySQL数据库里面创建一个表，来管理进货相关的数据。我们先看看这个表里有什么内容。

假设这个表叫做进货单头表（importhead），如下图所示：

<img src="https://static001.geekbang.org/resource/image/83/ff/83c593cb7679ed5f99c29d937712c8ff.jpg" alt="">

这里的1、2、3表示门店的3种进货方式，分别是配送中心配送、门店采买和供货商直供。

其中，“1（配送中心配送）”是标准进货方式。因为超市是连锁经营，为了确保商品质量和品类一致，超过9成的门店进货，是通过配送中心进行配送的。因此，我们希望这个字段的值能够默认是1，这样一来，除非有特别的指定，否则，门店进货单的进货方式，就自动设置成“1”了。

现在，客户需要一个类似的表来存储进货数据，而且进货方式还有3个可能的取值范围，需要设置默认值，那么，应该怎么创建这个表呢？另外，创建好表以后，又该怎么进行修改呢？

## 如何创建数据表？

首先，我们要知道MySQL创建表的语法结构：

```
CREATE TABLE &lt;表名&gt;
{
字段名1 数据类型 [字段级别约束] [默认值]，
字段名2 数据类型 [字段级别约束] [默认值]，
......
[表级别约束]
};

```

在这里，我们通过定义表名、表中的字段、表的属性等，把一张表创建出来。

你可能注意到了，在MySQL创建表的语法结构里面，有一个词叫做“约束”。**“约束”限定了表中数据应该满足的条件**。MySQL会根据这些限定条件，对表的操作进行监控，阻止破坏约束条件的操作执行，并提示错误，从而确保表中数据的唯一性、合法性和完整性。这是创建表时不可缺少的一部分。

下面我来带你创建刚刚提到的进货单表。需要注意的是，这里我们需要定义默认值，也就是要定义默认值约束，除此之外，还有很多种约束，一会儿我再细讲。

我们先来看基本的数据表创建流程，创建代码如下：

```
CREATE TABLE demo.importhead
(
listnumber INT,
supplierid INT,
stocknumber INT,
--我们在字段importype定义为INT类型的后面，按照MySQL创建表的语法，加了默认值1。
importtype INT DEFAULT 1,
quantity DECIMAL(10,3),
importvalue DECIMAL(10,2),
recorder INT,
recordingdate DATETIME
);

```

运行这个SQL语句，表demo.importhead就按照我们的要求被创建出来了。

在创建表的时候，字段名称要避开MySQL的[系统关键字](https://dev.mysql.com/doc/refman/8.0/en/keywords.html#keywords-removed-in-current-series)，原因是MySQL系统保留的关键字都有特定的含义，如果作为字段名称出现在SQL语句中，MySQL会把这个字段名称理解为系统关键字，从而导致SQL语句无法正常运行。比如，刚刚我们把进货金额设置为“importvalue”，而不是“value”，就是因为，“value”是MySQL的系统关键字。

好了，现在我们尝试往刚刚创建的表里插入一条记录，来验证一下对字段“importtype”定义的默认值约束是否起了作用。

```
INSERT INTO demo.importhead
(
listnumber,
supplierid,
stocknumber,
-- 这里我们没有插入字段importtype的值
quantity,
importvalue,
recorder,
recordingdate
)
VALUES
(
3456,
1,
1,
10,
100,
1,
'2020-12-10'
);

```

插入完成后，我们来查询一下表的内容：

```
SELECT *
FROM demo.importhead

```

运行结果如下：

```
mysql&gt; select * from demo.importhead;
+------------+------------+-------------+------------+----------+-------------+----------+---------------------+
| listnumber | supplierid | stocknumber | importtype | quantity | importvalue | recorder | recordingdate       |
+------------+------------+-------------+------------+----------+-------------+----------+---------------------+
|       1234 |          1 |           1 |          1 |   10.000 |      100.00 |        1 | 2020-12-10 00:00:00 |
|       2345 |          1 |           1 |          2 |   20.000 |     2000.00 |        1 | 2020-12-10 00:00:00 |
|       3456 |          1 |           1 |          1 |   20.000 |     2000.00 |        1 | 2020-12-10 00:00:00 |
+------------+------------+-------------+------------+----------+-------------+----------+---------------------+
3 rows in set (0.00 sec)

```

你会发现，字段importtype的值已经是1了。这样，通过在创建表的时候设置默认值，我们就实现了将字段的默认值定义为1的目的。

到这里，表就被创建出来了。

### 都有哪些约束？

刚刚这种给字段设置默认值的做法，就是默认约束。设置了默认约束，插入数据的时候，如果不明确给字段赋值，那么系统会把设置的默认值自动赋值给字段。

除了默认约束，还有主键约束、外键约束、非空约束、唯一性约束和自增约束。

我们在[上节课](https://time.geekbang.org/column/article/350470)里学的主键，其实就是主键约束，我就不多说了。外键约束涉及表与表之间的关联，以及确保表的数据一致性的问题，内容比较多，后面我在讲“关联表”的时候，再给你具体解释。

现在，我来重点给你介绍一下非空约束、唯一性约束和自增约束。

**1.非空约束**

非空约束表示字段值不能为空，如果创建表的时候，指明某个字段非空，那么添加数据的时候，这个字段必须有值，否则系统就会提示错误。

**2.唯一性约束**

唯一性约束表示这个字段的值不能重复，否则系统会提示错误。跟主键约束相比，唯一性约束要更加弱一些。

在一个表中，我们可以指定多个字段满足唯一性约束，而主键约束则只能有一个，这也是MySQL系统决定的。另外，**满足主键约束的字段，自动满足非空约束，但是满足唯一性约束的字段，则可以是空值**。

为了方便你理解，我来举个例子。比如，我们有个商品信息表goodsmaster，如下所示：

<img src="https://static001.geekbang.org/resource/image/5f/93/5f73f86c51f7b259fef9d52eaf5da893.jpg" alt="">

barcode代表条码，goodsname代表名称。为了防止条码重复，我们可以定义字段“barcode”满足唯一性约束。这样一来，条码就不能重复，但是可以为空，而且只能有一条记录条码为空。

同样道理，为了防止名称重复，我们也可以定义字段“goodsname”满足唯一性约束。但是，由于无论名称和条码都可能重用，或者可能为空，都不适合做主键。因此，对这个表来说，可以添加一个满足唯一性要求的新字段来做主键。

**3.自增约束**

自增约束可以让MySQL自动给字段赋值，且保证不会重复，非常有用，只是不容易用好。所以，我借助一个例子来给你具体讲一讲。

假如我们有这样一个商品信息表：

<img src="https://static001.geekbang.org/resource/image/52/c2/524bfc84ab3555283dd981a14f2a06c2.jpg" alt="">

从这个表中，我们可以看到，barcode、goodsname和price都不能确保唯一性，所以没有任何一个字段可以做主键，因此，我们可以自己添加一个字段itemnumber，并且每次添加一条数据的时候，要给值增加1。怎么实现呢？我们就可以通过定义自增约束的方式，让系统自动帮我们赋值，从而满足唯一性，这样就可以做主键了。

<img src="https://static001.geekbang.org/resource/image/e0/b1/e004230d1b626e17c3822c678cb1e5b1.jpg" alt="">

这里，有2个问题需要你注意一下。

第一，在数据表中，只有整数类型的字段（包括TINYINT、SMALLINT、MEDIUMINT、INT和BIGINT），才可以定义自增约束。自增约束的字段，每增加一条数据，值自动增加1。

第二，你可以给自增约束的字段赋值，这个时候，MySQL会重置自增约束字段的自增基数，下次添加数据的时候，自动以自增约束字段的最大值加1为新的字段值。

举个例子，我们通过Workbench把数据表demo.goodsmaster中的字段itemnumber，定义为满足自增约束，如下图所示：

<img src="https://static001.geekbang.org/resource/image/4b/22/4bb1264e88ee558e51061e0956d13f22.png" alt="">

然后，我们插入一条测试记录：

```
INSERT INTO demo.goodsmaster
(
itemnumber,
barcode,
goodsname,
specification,
unit,
price
)
VALUES
(
-- 指定商品编号为100：
100,
'0003',
'测试1',
'',
'个',
10
);

```

运行这条语句之后，查看表的内容，我们得到：

```
mysql&gt; select * from demo.goodsmaster;
+------------+---------+-----------+---------------+------+----+
| itemnumber | barcode | goodsname | specification | unit | price |
+------------+---------+-----------+---------------+------+-------+
|          1 | 0001    | 书        | 16开          | 本   | 89.00 |
|          2 | 0002    | 地图      | NULL          | 张   |  9.90 |
|          3 | 0003    | 笔        | 10支          | 包   |  3.00 |
|        100 | 0003    | 测试1     |               | 个   | 10.00 |
+------------+---------+-----------+---------------+------+-------+
4 rows in set (0.02 sec)

```

这个时候，字段“itemnumber”的值不连续，最大值是我们刚刚插入的100。

接着，我们再插入一条数据：

```
INSERT INTO demo.goodsmaster
(
-- 不指定自增字段itemnumber的值
barcode,
goodsname,
specification,
unit,
supplierid,
price
)
VALUES
(
'0004',
'测试2',
'',
'个',
1,
20
);

```

运行这条语句之后，我们再查看表的内容：

```
mysql&gt; select * from demo.goodsmaster;
+------------+---------+-----------+---------------+------+-------+
| itemnumber | barcode | goodsname | specification | unit | price |
+------------+---------+-----------+---------------+------+-------+
|          1 | 0001    | 书        | 16开          | 本   | 89.00 |
|          2 | 0002    | 地图      | NULL          | 张   |  9.90 |
|          3 | 0003    | 笔        | 10支          | 包   |  3.00 |
|        100 | 0003    | 测试1     |               | 个   | 10.00 |
|        101 | 0004    | 测试2     |               | 个   | 20.00 |
+------------+---------+-----------+---------------+------+-------+
5 rows in set (0.00 sec)

```

可以看到，系统自动给自增字段“itemnumber”，在最大值的基础之上加了1，赋值为101。

好了，到这里，我们就学会了创建表和定义约束的方法。约束要根据业务需要定义在相应的字段上，这样才能保证数据是准确的，你一定要注意它的使用方法。

## 如何修改表？

创建完表以后，我们经常还需要修改表，下面我们就来学习下修改表的方法。

在咱们的超市项目中，当我们创建新表的时候，会出现这样的情况：我们前面创建的进货单表，是用来存储进货数据的。但是，我们还要创建一个进货单历史表（importheadhist），用来存储验收过的进货数据。这个表的结构跟进货单表类似，只是多了2个字段，分别是验收人（confirmer）和验收时间（confirmdate）。针对这种情况，我们很容易想到可以通过复制表结构，然后在这个基础上通过修改表结构，来创建新的表。具体怎么实现呢？接下来，我就给你讲解一下。

首先，我们要把原来的表结构复制一下，代码如下：

```
CREATE TABLE demo.importheadhist
LIKE demo.importhead;

```

运行这个语句之后，一个跟demo.importhead有相同表结构的空表demo.importheadhist，就被创建出来了。

这个新创建出来的表，还不是我们需要的表，我们需要对这个表进行修改，通过添加字段和修改字段，来获得我们需要的“进货单历史表”。

### 添加字段

你可能会想到，我们可以通过Workbench，用可视化操作来修改表的结构，如下图所示：

<img src="https://static001.geekbang.org/resource/image/53/3f/537d01d3c33f8c9f804056e7795d1e3f.png" alt="">

这样当然没问题，但是我想给你讲一个更方便灵活的方式：**用SQL语句来修改表的结构**。

现在我要给这个新的表增加2个字段：confirmer和confirmdate，就可以用下面的代码：

```
mysql&gt; ALTER TABLE demo.importheadhist
    -&gt; ADD confirmer INT; -- 添加一个字段confirmer，类型INT
Query OK, 0 rows affected (0.04 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql&gt; ALTER TABLE demo.importheadhist
    -&gt; ADD confirmdate DATETIME; -- 添加一个字段confirmdate，类型是DATETIME
Query OK, 0 rows affected (0.04 sec)
Records: 0  Duplicates: 0  Warnings: 0

```

运行这个SQL语句，查看表的结构，你会发现，在字段的最后，多了两个字段：

- “confirmer”，数据类型是INT；
- “confirmdate"，类型是DATETIME。

我们来查看一下表结构：

```
mysql&gt; DESCRIBE demo.importheadhist;
+----------------+---------------+------+-----+---------+-------+
| Field          | Type          | Null | Key | Default | Extra |
+----------------+---------------+------+-----+---------+-------+
| listnumber     | int           | NO   | PRI | NULL    |       |
| supplierid     | int           | NO   |     | NULL    |       |
| stocknumber    | int           | NO   |     | NULL    |       |
| importtype     | int           | YES  |     | 1       |       |
| quantity       | decimal(10,3) | YES  |     | NULL    |       |
| importvalue    | decimal(10,2) | YES  |     | NULL    |       |
| recorder       | int           | YES  |     | NULL    |       |
| recordingdate  | datetime      | YES  |     | NULL    |       |
| confirmer      | int           | YES  |     | NULL    |       |
| confirmdate    | datetime      | YES  |     | NULL    |       |
+----------------+---------------+------+-----+---------+-------+
10 rows in set (0.02 sec)

```

这样，通过简单增加2个字段，我们就获得了进货单历史表。

### 修改字段

除了添加字段，我们可能还要修改字段，比如，我们要把字段名称“quantity”改成“importquantity”，并且把字段类型改为DOUBLE，该怎么操作呢？

我们可以通过修改表结构语句ALTER TABLE，来修改字段：

```
mysql&gt; ALTER TABLE demo.importheadhist
    -&gt; CHANGE quantity importquantity DOUBLE;
Query OK, 0 rows affected (0.15 sec)
Records: 0  Duplicates: 0  Warnings: 0

```

运行这个SQL语句，查看表的结构，我们会得到下面的结果：

```
mysql&gt; DESCRIBE demo.importheadhist;
+----------------+---------------+------+-----+---------+-------+
| Field          | Type          | Null | Key | Default | Extra |
+----------------+---------------+------+-----+---------+-------+
| listnumber     | int           | NO   | PRI | NULL    |       |
| supplierid     | int           | NO   |     | NULL    |       |
| stocknumber    | int           | NO   |     | NULL    |       |
| importtype     | int           | YES  |     | 1       |       |
| importquantity | double        | YES  |     | NULL    |       |
| importvalue    | decimal(10,2) | YES  |     | NULL    |       |
| recorder       | int           | YES  |     | NULL    |       |
| recordingdate  | datetime      | YES  |     | NULL    |       |
| confirmer      | int           | YES  |     | NULL    |       |
| confirmdate    | datetime      | YES  |     | NULL    |       |
+----------------+---------------+------+-----+---------+-------+
10 rows in set (0.02 sec)

```

可以看到，字段名称和字段类型全部都改过来了。

如果你不想改变字段名称，只想改变字段类型，例如，把字段“importquantity”类型改成DECIMAL(10,3)，你可以这样写：

```
ALTER TABLE demo.importheadhist
MODIFY importquantity DECIMAL(10,3);

```

运行SQL语句，查看表结构，你会发现，已经改过来了。

```
mysql&gt; DESCRIBE demo.importheadhist;
+----------------+---------------+------+-----+---------+-------+
| Field          | Type          | Null | Key | Default | Extra |
+----------------+---------------+------+-----+---------+-------+
| listnumber     | int           | NO   | PRI | NULL    |       |
| supplierid     | int           | NO   |     | NULL    |       |
| stocknumber    | int           | NO   |     | NULL    |       |
| importtype     | int           | YES  |     | 1       |       |
| importquantity | decimal(10,3) | YES  |     | NULL    |       |
| importvalue    | decimal(10,2) | YES  |     | NULL    |       |
| recorder       | int           | YES  |     | NULL    |       |
| recordingdate  | datetime      | YES  |     | NULL    |       |
| confirmer      | int           | YES  |     | NULL    |       |
| confirmdate    | datetime      | YES  |     | NULL    |       |
+----------------+---------------+------+-----+---------+-------+
10 rows in set (0.02 sec)

```

我们还可以通过SQL语句**向表中添加一个字段**，我们甚至可以指定添加字段在表中的位置。

比如说，在字段supplierid之后，添加一个字段suppliername，数据类型是TEXT。我们可以这样写SQL语句：

```
ALTER TABLE demo.importheadhist
ADD suppliername TEXT AFTER supplierid;

```

运行SQL语句，查看表结构：

```
mysql&gt; DESCRIBE demo.importheadhist;
+----------------+---------------+------+-----+---------+-------+
| Field          | Type          | Null | Key | Default | Extra |
+----------------+---------------+------+-----+---------+-------+
| listnumber     | int           | NO   | PRI | NULL    |       |
| supplierid     | int           | NO   |     | NULL    |       |
| suppliername   | text          | YES  |     | NULL    |       |
| stocknumber    | int           | NO   |     | NULL    |       |
| importtype     | int           | YES  |     | 1       |       |
| importquantity | decimal(10,3) | YES  |     | NULL    |       |
| importvalue    | decimal(10,2) | YES  |     | NULL    |       |
| recorder       | int           | YES  |     | NULL    |       |
| recordingdate  | datetime      | YES  |     | NULL    |       |
| confirmer      | int           | YES  |     | NULL    |       |
| confirmdate    | datetime      | YES  |     | NULL    |       |
+----------------+---------------+------+-----+---------+-------+
11 rows in set (0.02 sec)

```

到这里，我们就完成了修改字段在表中位置的操作。

## 总结

这节课，我们学习了创建和修改数据表的具体方法。在讲创建表时，我讲到了一个重要的概念，就是约束，包括默认约束、非空约束、唯一性约束和自增约束等。

- 默认值约束：就是给字段设置一个默认值。
- 非空约束：就是声明字段不能为空值。
- 唯一性约束：就是声明字段不能重复。
- 自增约束：就是声明字段值能够自动加1，且不会重复。

在修改表时，我们可以通过修改已经存在的表创建新表，也可以通过添加字段、修改字段的方式来修改数据表。

最后，我给你汇总了一些常用的创建表的SQL语句，你一定要牢记。

```
CREATE TABLE
(
字段名 字段类型 PRIMARY KEY
);
CREATE TABLE
(
字段名 字段类型 NOT NULL
);
CREATE TABLE
(
字段名 字段类型 UNIQUE
);
CREATE TABLE
(
字段名 字段类型 DEFAULT 值
);
-- 这里要注意自增类型的条件，字段类型必须是整数类型。
CREATE TABLE
(
字段名 字段类型 AUTO_INCREMENT
);
-- 在一个已经存在的表基础上，创建一个新表
CREATE demo.importheadhist LIKE demo.importhead;
-- 修改表的相关语句
ALTER TABLE 表名 CHANGE 旧字段名 新字段名 数据类型;
ALTER TABLE 表名 ADD COLUMN 字段名 字段类型 FIRST|AFTER 字段名;
ALTER TABLE 表名 MODIFY 字段名 字段类型 FIRST|AFTER 字段名;

```

对于初学者来说，掌握了今天的内容，就足够对数据表进行操作了。不过，MySQL支持的数据表操作不只这些，我来举几个简单的小例子，你可以了解一下，有个印象。

比如，你可以在表一级指定表的存储引擎：

```
ALTER TABLE 表名 ENGINE=INNODB;

```

你还可以通过指定关键字AUTO_EXTENDSIZE，来指定存储文件自增空间的大小，从而提高存储空间的利用率。

在MySQL 8.0.23之后的版本中，你甚至还可以通过INVISIBLE关键字，使字段不可见，但却可以使用。

如果你想了解更多有关数据表的操作，我也给你提供两份资料：[MySQL创建表文档](https://dev.mysql.com/doc/refman/8.0/en/create-table.html)和[MySQL修改表文档](https://dev.mysql.com/doc/refman/8.0/en/alter-table.html)。这些都是MySQL的官方文档，相信会对你有所帮助。

## 思考题

请你写一个SQL语句，将表demo.goodsmaster中的字段“salesprice”改成不能重复，并且不能为空。

欢迎在留言区写下你的思考和答案，我们一起交流讨论。如果你觉得今天的内容对你有所帮助，欢迎你把它分享给你的朋友或同事，我们下节课见。
