约束

SQL 对表/列有相关的约束,分别为:NOT NULL 非空约束;UNIQUE 唯一性约束;PRIMARY KEY 主键约束;FOREIGN KEY 外键约束;AUTO_INCREMENT 自增列;CHECK 检查约束;DEFAULT 默认值约束。

1 约束 constraint

1.1 对数据表约束的目的

为了保证数据的完整性,SQL 规范以约束的方式对表数据进行额外的条件限制。从以下四个方面考虑:

  • 实体完整性(Entity Integrity) :例如,同一个表中,不能存在两条完全相同无法区分的记录。
  • 域完整性(Domain Integrity) :例如,年龄范围 0-120,性别范围 “男/女”。
  • 引用完整性(Referential Integrity) :例如,员工所在部门,在部门表中要能找到这个部门。
  • 用户自定义完整性(User-defined Integrity) :例如:用户名唯一、密码不能为空等。

1.2 约束

约束是表级的强制规定。可以在创建表时规定约束(通过 CREATE TABLE 语句),或者在表创建之后通过 ALTER TABLE 语句规定约束

根据约束数据列的限制,约束可分为:

  • 单列约束:每个约束只约束一列

  • 多列约束:每个约束可约束多列数据

根据约束的作用范围,约束可分为:

  • 列级约束:只能作用在一个列上,跟在列的定义后面

  • 表级约束:可以作用在多个列上,不与列一起,而是单独定义

根据约束起的作用,约束可分为:

  • NOT NULL 非空约束,规定某个字段不能为空
  • UNIQUE 唯一性约束,规定某个字段在整个表中是唯一的
  • PRIMARY KEY 主键 (非空且唯一) 约束
  • FOREIGN KEY 外键约束
  • CHECK 检查约束
  • DEFAULT 默认值约束

特别地,MySQL8.0 后开始支持 CHECK 约束。

1.3 SQL 查看表的约束情况

1
2
3
4
5
6
7
8
-- information_schema 数据库名(系统库)
-- table_constraints 表名称(专门存储各个表的约束)

USE 数据库名;

SELECT *
FROM information_schema.table_constraints
WHERE table_name = '表名';

例如:

1
2
3
4
5
USE atguigudb;

SELECT *
FROM information_schema.table_constraints
WHERE table_name = 'employees';

数据库 atguigudb 的表 employees 的约束信息

2 NOT NULL 非空约束

2.1 非空约束

NOT NULL :限定某个字段/某列的值不允许为空

  • 默认所有的类型的值都可以是 NULL
  • 非空约束只能出现在表对象的列上,只能某个列单独限定非空,不能组合非空
  • 一个表可以有很多列都分别限定了非空
  • 空字符串 '' 不等于 NULL , 0 也不等于 NULL

2.2 具体 SQL 实现

2.2.1 添加非空约束

建表时

1
2
3
4
5
6
CREATE TABLE 表名称
(
字段名 数据类型,
字段名 数据类型 NOT NULL,
字段名 数据类型 NOT NULL
);

建表后

1
2
ALTER TABLE 表名称
MODIFY 字段名 数据类型 NOT NULL;

2.2.2 删除非空约束

1
2
3
4
5
6
7
ALTER TABLE 表名称
MODIFY 字段名 数据类型 NULL;

-- 或

ALTER TABLE 表名称
MODIFY 字段名 数据类型;

3 UNIQUE 唯一性约束

3.1 唯一性约束

UNIQUE :用来限制某个字段/某列的值不能重复。

  • 同一个表可以有多个唯一约束。
  • 唯一约束可以是某一个列的值唯一,也可以多个列组合的值唯一。
  • 唯一性约束允许列值为空。
  • 在创建唯一约束的时候,如果不给唯一约束命名,就默认和列名相同。
  • MySQL 会给唯一约束的列上默认创建一个唯一索引。

3.2 具体 SQL 实现

3.2.1 添加唯一性约束

建表时

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
CREATE TABLE 表名称
(
字段名 数据类型,
字段名 数据类型 UNIQUE,
字段名 数据类型 UNIQUE KEY,
字段名 数据类型
);

-- 或

CREATE TABLE 表名称
(
字段名 数据类型,
字段名 数据类型,
字段名 数据类型,
[CONSTRAINT 约束名] UNIQUE KEY (字段名)
);

例如:多个字段 NAME PASSWORD 均不可重复,为数据表级别的约束。单独

1
2
3
4
5
6
7
8
CREATE TABLE USER
(
id INT NOT NULL,
NAME VARCHAR(25),
PASSWORD VARCHAR(16),
-- 表级约束语法
CONSTRAINT uk_name_pwd UNIQUE (NAME, PASSWORD)
);
1
2
3
SELECT *
FROM information_schema.table_constraints
WHERE table_name = 'user_unique';

约束名

建表后

1
2
3
4
5
6
7
-- 方式 1
ALTER TABLE 表名称
ADD UNIQUE KEY (字段名列表);

-- 方式 2
ALTER TABLE 表名称
MODIFY 字段名 字段类型 UNIQUE;

字段名列表中如果是一个字段,表示该列的值唯一。如果是两个或更多个字段,那么复合唯一,即多个字段的组合是唯一的。

例如:

1
2
3
4
5
6
7
8
ALTER TABLE user_unique
ADD UNIQUE (NAME, PASSWORD);

ALTER TABLE user_unique
ADD CONSTRAINT uk_name_pwd UNIQUE (NAME, PASSWORD);

ALTER TABLE user_unique
MODIFY NAME char(10) UNIQUE;

3.2.2 复合唯一性约束

上面的 UNIQUE KEY (字段名列表) 多个字段名,表示复合唯一,即多个字段的组合是唯一的。

1
2
3
4
5
6
7
create table 表名称
(
字段名 数据类型,
字段名 数据类型,
字段名 数据类型,
UNIQUE KEY (字段名列表)
);

3.2.3 删除唯一性约束

添加唯一性约束的列上也会自动创建唯一索引

  • 删除唯一约束只能通过删除唯一索引的方式删除。
  • 删除时需要指定唯一索引名,唯一索引名就和唯一约束名一样。
  • 如果创建唯一约束时未指定名称,如果是单列,就默认和列名相同;如果是组合列,那么默认和 (字段名列表) 中排在第一个的列名相同。
1
2
3
4
5
-- 查看表的索引
SHOW INDEX FROM 表名;

-- 例如
SHOW INDEX FROM user_unique;

查看表的索引

也可以使用 information_schema.table_constraints 查看约束名。

1
2
3
SELECT *
FROM information_schema.table_constraints
WHERE table_name = 'user_unique';

约束名

删除唯一性约束 :无论如何,删除 UNIQUE 需要删除的是索引,索引的名称一般为约束名,用如下 SQL 语句删除。

1
2
ALTER TABLE 表名
DROP INDEX 索引名;

例如:

1
2
ALTER TABLE user_unique
DROP INDEX uk_name_pwd;

4 PRIMARY KEY 主键约束

4.1 主键约束

PRIMARY KEY :用来唯一标识表中的一行记录。

  • 主键约束相当于唯一约束 + 非空约束的组合,主键约束列不允许重复,也不允许出现空值。

  • 一个表最多只能有一个主键约束,建立主键约束可以在列级别创建,也可以在表级别上创建。

  • 主键约束对应着表中的一列或者多列(复合主键)。如果是多列组合的复合主键约束,那么这些列都不允许为空值,并且组合的值不允许重复。

  • MySQL 的主键名为 PRIMARY ,自定义重命名无用。

  • 当创建主键约束时,系统默认会在所在的列或列组合上建立对应的主键索引。如果删除主键约束,主键约束对应的索引自动删除。

【注意】不要修改主键字段的值

4.2 具体 SQL 实现

4.2.1 添加主键约束

建表时

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
-- example 1
CREATE TABLE 表名称
(
字段名 数据类型 PRIMARY KEY, -- 列级模式
字段名 数据类型,
字段名 数据类型
);

-- example 2
CREATE TABLE 表名称
(
字段名 数据类型,
字段名 数据类型,
字段名 数据类型,
[CONSTRAINT 约束名] PRIMARY KEY (字段名列表) -- 表级模式
);

例如:

1
2
3
4
5
create table temp
(
id INT PRIMARY KEY,
name VARCHAR(20)
);
1
2
3
4
5
6
7
8
mysql> DESC temp;
+-------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| id | int | NO | PRI | NULL | | -- 主键 PRI
| name | varchar(20) | YES | | NULL | |
+-------+-------------+------+-----+---------+-------+
2 rows in set (0.01 sec)

建表后

1
2
ALTER TABLE 表名称
ADD PRIMARY KEY (字段名列表);

4.2.2 复合主键约束

(字段名列表) 多个字段名共同构成主键:如果是多列组合的复合主键约束,那么这些列都不允许为空值,并且组合的值不允许重复。

1
2
3
4
5
6
7
CREATE TABLE 表名称
(
字段名 数据类型,
字段名 数据类型,
字段名 数据类型,
PRIMARY KEY (字段名列表)
);

4.2.3 删除主键约束

删除主键约束,不需要指定主键名,因为一个表只有一个主键,删除主键约束后,非空约束还存在。

1
ALTER TABLE 表名 DROP PRIMARY KEY;

【注意】删除时指定删除的为 PRIMARY KEY 不会存在歧义。即在 [CONSTRAINT 约束名] 自定义的约束名并不会起作用。MySQL 仍然使用 PRIMARY 作为主键约束的索引名和约束名:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
create table temp_primary
(
id INT,
name VARCHAR(20),
age INT,
CONSTRAINT temp_pri PRIMARY KEY (id, name) -- 自定义约束名 temp_pri
);

-- 查看索引名
SHOW INDEX FROM temp_primary;

-- 查看约束名
SELECT *
FROM information_schema.table_constraints
WHERE table_name = 'temp_primary';

索引名仍然为 PRIMARY 不是自定义的 temp_pri

约束名仍然为 PRIMARY 不是自定义的 temp_pri

5 FOREIGN KEY 外键约束

5.1 外键约束

FOREIGN KEY :限定某个表的某个字段的引用完整性。比如:员工表的员工所在部门,必须在部门表能找到对应的部分。

  • 主表(父表):被引用的表,被参考的表
  • 从表(子表):引用别人的表,参考别人的表

特点:

  • 从表的外键列,必须引用主表的主键或唯一约束的列(否则无法对应)。
  • 在创建外键约束时,如果不给外键约束命名,默认名不是列名,而是自动产生一个外键名。
  • 若创建表时指定外键约束,要先有主表,再创建从表。
  • 删表时,先删从表(或先删除外键约束),再删除主表。
  • 当主表的记录被从表参照时,主表的记录将不允许删除。如果要删除数据,需要先删除从表中依赖该记录的数据,然后才可以删除主表的数据。
  • 从表的外键列与主表被参照的列数据类型必须一样,逻辑意义一致。
  • 当创建外键约束时,系统默认会在所在的列上建立对应的普通索引。索引名是外键的约束名,以此提高外键查询的速度。
  • 删除外键约束后,必须手动删除对应的索引。

约束关系是针对双方的:

  • 添加了外键约束后,主表的修改和删除数据受约束
  • 添加了外键约束后,从表的添加和修改数据受约束
  • 在从表上建立外键,要求主表必须存在
  • 删除主表时,要求从表从表先删除,或将从表中外键引用该主表的关系先删除

5.2 具体 SQL 实现

5.2.1 添加外键约束

建表时

1
2
3
4
5
6
7
8
9
10
11
12
13
14
-- 1. 先有主表
CREATE TABLE 主表名称
(
字段1 数据类型 PRIMARY KEY,
字段2 数据类型
);

-- 2. 再指定从表外键
CREATE TABLE 从表名称
(
字段1 数据类型 PRIMARY KEY,
字段2 数据类型,
[CONSTRAINT 外键约束名] FOREIGN KEY (从表的某个字段) REFERENCES 主表名 (被参考字段)
);

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
-- 1. 先有主表 部门表
CREATE TABLE dept_foreign
(
d_id INT PRIMARY KEY,
d_name VARCHAR(50)
);

-- 2. 再指定从表外键 员工表的部门 id 指向部门表的 id
CREATE TABLE emp_foreign
(
e_id INT PRIMARY KEY,
e_name VARCHAR(5),
dept_id INT,
FOREIGN KEY (dept_id) REFERENCES dept_foreign (d_id)
)

索引名默认为列名 dept_id ,但约束名系统自动产生 emp_foreign_ibfk_1

1
2
3
4
5
SHOW INDEX FROM emp_foreign;  -- dept_id

SELECT *
FROM information_schema.table_constraints
WHERE table_name = 'emp_foreign'; -- emp_foreign_ibfk_1

建表后

1
2
ALTER TABLE 从表名
ADD [CONSTRAINT 约束名] FOREIGN KEY (从表某一字段名) REFERENCES 主表名 (主表字段名);

例如:

1
2
ALTER TABLE emp_foreign
ADD CONSTRAINT emp_foreign_key FOREIGN KEY (dept_id) REFERENCES dept_foreign (d_id);

此时索引名变为自定义的约束名 emp_foreign_key ,但约束名除了系统自动产生的约束名 emp_foreign_ibfk_1 外,新增了自定义的约束名 emp_foreign_key

5.2.2 约束等级

父表进行数据更新/删除时,子表的不同变化:

  • Cascade 方式 :在父表上 update/delete 记录时,同步 update/delete 掉子表的匹配记录。
  • Set null 方式 :在父表上 update/delete 记录时,将子表上匹配记录的列设为 null ,但是要注意子表的外键列不能有非空约束。
  • No action 方式 :如果子表中有匹配的记录,则不允许对父表对应候选键进行 update/delete 操作。
  • Restrict 方式 :同 No action 方式, 都是立即检查外键约束。
  • Set default 方式 :父表有变更时,子表将外键列设置成一个默认的值。

如果没有指定等级,默认 Restrict 方式

对于外键约束,最好是采用: ON UPDATE CASCADE ON DELETE RESTRICT 的方式。即更新时采用 Cascade 方式,删除时采用 Restrict 方式。

1
2
3
4
5
6
7
CREATE TABLE emp_foreign
(
e_id INT PRIMARY KEY,
e_name VARCHAR(5),
dept_id INT,
FOREIGN KEY (dept_id) REFERENCES dept_foreign (d_id) ON UPDATE CASCADE ON DELETE RESTRICT
);

5.2.3 删除外键约束

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
-- 1. 查看约束名
SELECT *
FROM information_schema.table_constraints
WHERE table_name = '表名称';

-- 2. 删除外键约束
ALTER TABLE 从表名
DROP FOREIGN KEY 外键约束名;

-- 3. 查看索引名
SHOW INDEX FROM 表名称;

-- 4. 删除索引
ALTER TABLE 从表名
DROP INDEX 索引名;

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
-- 1. 查看约束名
SELECT *
FROM information_schema.table_constraints
WHERE table_name = 'emp_foreign';

-- 2. 删除外键约束
ALTER TABLE emp_foreign
DROP FOREIGN KEY emp_foreign_ibfk_1;

-- 3. 查看索引名
SHOW INDEX FROM emp_foreign;

-- 4. 删除索引
ALTER TABLE emp_foreign
DROP INDEX dept_id;

6 AUTO_INCREMENT 自增列

6.1 自增列概述

AUTO_INCREMENT :某个字段的值自增。

  • 一个表最多只能有一个自增长列。当需要产生唯一标识符或顺序值时,可设置自增长。
  • 自增列约束的列必须是键列(主键列或唯一键列)
  • 自增约束的列的数据类型必须是整数类型
  • 如果自增列指定了 0 和 NULL ,会在当前最大值的基础上自增;如果自增列手动指定了具体值,直接赋值为具体值。

6.2 具体 SQL 实现

6.2.1 添加自增列

建表时

1
2
3
4
5
6
7
8
9
10
11
12
13
14
-- 常见格式
CREATE TABLE 表名称
(
字段名 数据类型 PRIMARY KEY AUTO_INCREMENT,
字段名 数据类型 NOT NULL DEFAULT 默认值
);

CREATE TABLE 表名称
(
字段名 数据类型 DEFAULT 默认值,
字段名 数据类型 UNIQUE KEY AUTO_INCREMENT,
字段名 数据类型 NOT NULL DEFAULT 默认值,
PRIMARY KEY (字段名)
);

例如:

1
2
3
4
5
create table emp_increment
(
e_id INT PRIMARY KEY AUTO_INCREMENT,
e_name VARCHAR(20)
);

建表后

1
2
ALTER TABLE 表名
MODIFY 字段名 数据类型 AUTO_INCREMENT;

例如:

1
2
ALTER TABLE emp_increment
MODIFY e_id INT AUTO_INCREMENT;

6.2.2 删除自增列

1
2
ALTER TABLE 表名称
MODIFY 字段名 数据类型; -- 去掉 AUTO_INCREMENT 相当于删除

例如:

1
2
ALTER TABLE emp_increment
MODIFY e_id INT;

7 CHECK 检查约束

7.1 检查约束

CHECK :检查某个字段的值是否符号某些要求。

例如:对于性别,只有 MaleFemale ,可以对数据进行初步检查。

7.2 具体 SQL 实现

1
2
3
4
5
6
CREATE TABLE 表名
(
字段名 数据类型,
字段名 数据类型,
字段名 数据类型 CHECK (约束表达式)
);

例如:

1
2
3
4
5
6
CREATE TABLE emp_check
(
e_id INT PRIMARY KEY,
e_name VARCHAR(5),
gender CHAR CHECK (gender in ('男', '女'))
);
1
2
3
-- 插入数据
INSERT INTO emp_check
VALUES (1, 'Mike', '男');
1
2
3
4
5
-- 错误数据
INSERT INTO emp_check
VALUES (1, 'Mike', 'M');

ERROR 3819 (HY000): Check constraint 'emp_check_chk_1' is violated.

8 DEFAULT 默认值约束

8.1 默认值约束

DEFAULT :给某个字段/某列指定默认值,一旦设置默认值,在插入数据时,如果此字段没有显式赋值,则赋值为默认值。

8.2 具体 SQL 实现

8.2.1 添加默认值约束

建表时

1
2
3
4
5
CREATE TABLE 表名
(
字段名 数据类型 PRIMARY KEY,
字段名 数据类型 DEFAULT 默认值
);

例如:

1
2
3
4
5
6
7
CREATE TABLE emp_default
(
e_id INT PRIMARY KEY,
e_name VARCHAR(20) NOT NULL,
sal FLOAT DEFAULT 0.0,
tel CHAR(11) NOT NULL DEFAULT '123'
);

建表后

1
2
ALTER TABLE 表名称
MODIFY 字段名 数据类型 default 默认值 [其他约束];

例如:

1
2
ALTER TABLE emp_default
MODIFY sal FLOAT DEFAULT 1.0;

8.2.2 删除默认值约束

类似的,当删除某个约束时,重新写一遍约束,但删去这个约束即可。

1
2
ALTER TABLE 表名称
MODIFY 字段名 数据类型 [其他约束]; -- 删除 DEFAULT 约束