文章目录
- 数据库约束
- NOT NULL(非空约束)
- UNIQUE(唯一约束)
- DEFAULT(缺省约束)
- PRIMARY KEY(主键约束)
- AUTO_INCREMENT 自增
- FOREIGN KEY(外键约束)
- CHECK(检查约束)
- 表的设计
- 表的关系
- 一对一
- 一对多
- 多对多
- 三大范式
- 第一范式
- 第二范式
- 第三范式
数据库约束
数据库中主要有六种约束:
NOT NULL(非空约束)
- 指示某列不能存储NULL
值。UNIQUE(唯一约束)
- 保证某列的每行必须有唯一的值。DEFAULT(缺省约束)
- 规定没有给列赋值时的默认值。PRIMARY KEY(主键约束)
-NOT NULL
和UNIQUE
的结合。确保某列(或两个列多个列的结合)有唯一标识,有助于更容易更快速地找到表中的一个特定的记录。FOREIGN KEY(外键约束)
- 保证一个表中的数据匹配另一个表中的值的参照完整性。CHECK(检查约束)
- 保证列中的值符合指定的条件。对于MySQL
数据库,对CHECK
子句进行分析,但是忽略CHECK
子句。
NOT NULL(非空约束)
指示某列不能存储 NULL 值。
mysql> create table book(-> id int,-> name varchar(12),-> price double,-> publish date,-> num int NOT NULL-> );
Query OK, 0 rows affected (0.02 sec)// 可以看到 num 的 NULL 属性已经变为 NO(不允许)
mysql> DESC book;
+---------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+---------+-------------+------+-----+---------+-------+
| id | int | YES | | NULL | |
| name | varchar(12) | YES | | NULL | |
| price | double | YES | | NULL | |
| publish | date | YES | | NULL | |
| num | int | NO | | NULL | |
+---------+-------------+------+-----+---------+-------+
5 rows in set (0.00 sec)// 尝试一下插入一个 num 为 NULL 的数据
mysql> INSERT INTO book VALUES(2, "C++", 13.6, "2000-08-02", NULL);
ERROR 1048 (23000): Column 'num' cannot be null// 可以发现会报错: num 列不可以为 NULL
UNIQUE(唯一约束)
保证某列的每行必须有唯一的值,即对于添加了唯一约束的数据项不能有重复。
mysql> create table book(-> id int UNIQUE,-> name varchar(12),-> price double,-> publish date,-> num int-> );
Query OK, 0 rows affected (0.03 sec)// 可以看到 id 的 key 属性已经被标记为 UNI
mysql> desc book;
+---------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+---------+-------------+------+-----+---------+-------+
| id | int | YES | UNI | NULL | |
| name | varchar(12) | YES | | NULL | |
| price | double | YES | | NULL | |
| publish | date | YES | | NULL | |
| num | int | YES | | NULL | |
+---------+-------------+------+-----+---------+-------+
5 rows in set (0.00 sec)mysql> INSERT INTO book VALUES(1, "C++", 13.5, NULL, 10);
Query OK, 1 row affected (0.00 sec)mysql> INSERT INTO book VALUES(1, "JAVA", 3.5, "2000-08-02", 5);
ERROR 1062 (23000): Duplicate entry '1' for key 'book.id'
// 虽然第二条数据除了 id,其他列的值都不同于第一条数据,然而还是会报错:重复输入“1”作为“book.id”的值
DEFAULT(缺省约束)
规定没有给列赋值时的默认值。
mysql> create table book(-> id int,-> name varchar(12),-> price double,-> publish date DEFAULT "2000-08-02",-> num int-> );
Query OK, 0 rows affected (0.02 sec)// 可以看到 publish 的 Default 属性被设置为 2000-08-02
mysql> desc book;
+---------+-------------+------+-----+------------+-------+
| Field | Type | Null | Key | Default | Extra |
+---------+-------------+------+-----+------------+-------+
| id | int | YES | | NULL | |
| name | varchar(12) | YES | | NULL | |
| price | double | YES | | NULL | |
| publish | date | YES | | 2000-08-02 | |
| num | int | YES | | NULL | |
+---------+-------------+------+-----+------------+-------+
5 rows in set (0.01 sec)// 指定列插入,不包含 publish 列
mysql> INSERT INTO book(id, name, price, num) VALUES(3, "C++", 13.5, 15);
Query OK, 1 row affected (0.00 sec)mysql> SELECT * FROM book;
+------+------+-------+------------+------+
| id | name | price | publish | num |
+------+------+-------+------------+------+
| 3 | C++ | 13.5 | 2000-08-02 | 15 |
+------+------+-------+------------+------+
1 rows in set (0.00 sec)
PRIMARY KEY(主键约束)
NOT NULL
和 UNIQUE
的结合。确保某列(或两个列多个列的结合)有唯一标识,有助于更容易更快速地找到表中的一个特定的记录。
主键(primary key
)的特性即 非空且唯一,如果在没有指定主键的时候,如果某一列(或一组列)具有非空且唯一的特性,他就会被暂定为主键,但是主键只能有一个。
表中的任何列都可以作为主键,只要它满足以下条件:
- 任意两行都不具有相同的主键值;
- 每个行都必须具有一个主键值(主键列不允许NULL值)。
上面提到多个列也可以作为主键。在使用多列作为主键时,上述条件必须应用到构成主键的所有列,所有列值的 组合 必须是唯一的(但单个列的值可以不唯一)。
除MySQL强制实施的规则外,应该坚持的几个普遍认可的最好习惯为:
- 不更新主键列中的值;
- 不重用主键列的值;
- 不在主键列中使用可能会更改的值。(例如,如果使用一个名字作为主键以标识某个供应商,当该供应商合并和更改其名字时,必须更改这个主键。)
// 使用 NOT NULL UNIQUE
mysql> CREATE TABLE pen(-> id int NOT NULL UNIQUE,-> name varchar(12),-> price double,-> num int-> );
Query OK, 0 rows affected (0.06 sec)// id 的 KEY 属性变为 PRI
mysql> DESC pen;
+-------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| id | int | NO | PRI | NULL | |
| name | varchar(12) | YES | | NULL | |
| price | double | YES | | NULL | |
| num | int | YES | | NULL | |
+-------+-------------+------+-----+---------+-------+
4 rows in set (0.00 sec)// 使用 PRIMARY KEY
mysql> CREATE TABLE clothes(-> id int PRIMARY KEY,-> name varchar(12),-> price double,-> num int-> );
Query OK, 0 rows affected (0.02 sec)// id 的 KEY 属性变为 PRI
mysql> DESC clothes;
+-------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| id | int | NO | PRI | NULL | |
| name | varchar(12) | YES | | NULL | |
| price | double | YES | | NULL | |
| num | int | YES | | NULL | |
+-------+-------------+------+-----+---------+-------+
4 rows in set (0.01 sec)
但是 非空且唯一 并不一定就是主键,如果有多个非空不唯一,则只有第一个是主键。
// 将 id 和 num 都设置为非空且唯一
mysql> CREATE TABLE pen(-> id int NOT NULL UNIQUE,-> name varchar(12),-> price double,-> num int UNIQUE NOT NULL-> );
Query OK, 0 rows affected (0.02 sec)// 只有 id 是 PRI
mysql> DESC pen;
+-------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| id | int | NO | PRI | NULL | |
| name | varchar(12) | YES | | NULL | |
| price | double | YES | | NULL | |
| num | int | NO | UNI | NULL | |
+-------+-------------+------+-----+---------+-------+
4 rows in set (0.00 sec)
但我们之前也提到过,是允许 多个列 组成主键的,那么怎么实现呢?
mysql> CREATE TABLE pen(-> id int PRIMARY KEY,-> name varchar(12) PRIMARY KEY,-> price double,-> num int-> );
ERROR 1068 (42000): Multiple primary key defined
通过报错我们发现,如果在两个列后分别声明主键的话会被认为 定义了多个主键。
// 正确做法是在声明完所有列之后,再声明主键是由哪些列构成的
mysql> CREATE TABLE pen(-> id int,-> name varchar(12),-> price double,-> num int,-> PRIMARY KEY(id, name)-> );
Query OK, 0 rows affected (0.03 sec)mysql> DESC pen;
+-------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| id | int | NO | PRI | NULL | |
| name | varchar(12) | NO | PRI | NULL | |
| price | double | YES | | NULL | |
| num | int | YES | | NULL | |
+-------+-------------+------+-----+---------+-------+
4 rows in set (0.01 sec)
AUTO_INCREMENT 自增
添加自增属性的项必须为数字,并且必须为主键,并且只有缺省的时候才会使用自增。
- 如果插入的
AUTO_INCREMENT
属性的列值为缺省,则赋值为上一条记录的值+1
,没有上一条记录,则赋值为1
。 - 如果删除了表中数据,序号并不会重置,而是继续从删除的位置自增。
mysql> CREATE TABLE pen(-> id int PRIMARY KEY AUTO_INCREMENT,-> name varchar(12),-> price double,-> num int-> );
Query OK, 0 rows affected (0.02 sec)// 可以看到 id 的 Extra 属性变成了 auto_increment
mysql> DESC pen;
+-------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+----------------+
| id | int | NO | PRI | NULL | auto_increment |
| name | varchar(12) | YES | | NULL | |
| price | double | YES | | NULL | |
| num | int | YES | | NULL | |
+-------+-------------+------+-----+---------+----------------+
4 rows in set (0.00 sec)// 加入一条未设 id 的数据
mysql> INSERT INTO pen(name, price, num) VALUES("A", 2.34, 5);
Query OK, 1 row affected (0.01 sec)// id 被自动设置为 1
mysql> SELECT * FROM pen;
+----+------+-------+------+
| id | name | price | num |
+----+------+-------+------+
| 1 | A | 2.34 | 5 |
+----+------+-------+------+
1 row in set (0.00 sec)// 增加两条数据,一条设置 id
mysql> INSERT INTO pen VALUES(3, "B", 3.45, 7);
Query OK, 1 row affected (0.00 sec)
// 一条不设 id
mysql> INSERT INTO pen(name, price, num) VALUES("C", 6.34, 10);
Query OK, 1 row affected (0.00 sec)// 未设 id 的数据其 id 会变成上一条记录的 id+1
mysql> SELECT * FROM pen;
+----+------+-------+------+
| id | name | price | num |
+----+------+-------+------+
| 1 | A | 2.34 | 5 |
| 3 | B | 3.45 | 7 |
| 4 | C | 6.34 | 10 |
+----+------+-------+------+
3 rows in set (0.00 sec)// 删掉所有 id<=4 的数据
mysql> DELETE FROM pen WHERE id <= 4;
Query OK, 3 rows affected (0.00 sec)// 增加两条数据,都没有设置 id 值
mysql> INSERT INTO pen(name, price, num) VALUES("D", 7.34, 1);
Query OK, 1 row affected (0.00 sec)
mysql> INSERT INTO pen(name, price, num) VALUES("E", 12.34, 13);
Query OK, 1 row affected (0.00 sec)// 新增的两条数据的 id 并不是从 1 开始,而是根据上一条记录(虽然它已经不存在了)的 id 自增
mysql> SELECT * FROM pen;
+----+------+-------+------+
| id | name | price | num |
+----+------+-------+------+
| 5 | D | 7.34 | 1 |
| 6 | E | 12.34 | 13 |
+----+------+-------+------+
2 rows in set (0.00 sec)
FOREIGN KEY(外键约束)
外键:外键为表中的某一列,包含了另一个表的主键,定义了两个表之间的关系。例如学生表中存储了班级的信息,但是在班级表中并没有这个班级存在,就会导致数据出现冲突,所以必须将两个表关联起来。
语法
FOREIGN KEY (外键项) REFERENCES 关联表名(关联表中的对应项)
示例
// 创建班级表
mysql> CREATE TABLE class(-> id int PRIMARY KEY AUTO_INCREMENT-> );
Query OK, 0 rows affected (0.03 sec)
// 创建学生表
mysql> CREATE TABLE student(-> id int PRIMARY KEY AUTO_INCREMENT,-> num int,-> name varchar(10),-> classid int,-> FOREIGN KEY (classid) REFERENCES class(id)-> );
Query OK, 0 rows affected (0.03 sec)
// 创建两个班级
mysql> INSERT INTO class VALUES(181);
Query OK, 1 row affected (0.01 sec)
mysql> INSERT INTO class VALUES(182);
Query OK, 1 row affected (0.00 sec)
// 插入三条学生数据
mysql> INSERT INTO student(name, classid) VALUES("李四", 182);
Query OK, 1 row affected (0.03 sec)
mysql> INSERT INTO student(name, classid) VALUES("陈六", 181);
Query OK, 1 row affected (0.00 sec)
// 第三条失败,原因在于没有186班
mysql> INSERT INTO student(name, classid) VALUES("张三", 186);
ERROR 1452 (23000): Cannot add or update a child row: a foreign key constraint fails (`test`.`student`, CONSTRAINT `student_ibfk_1` FOREIGN KEY (`classid`) REFERENCES `class` (`id`))
CHECK(检查约束)
保证列中的值符合指定的条件。对于MySQL数据库,对CHECK子句进行分析,但是忽略CHECK子句。
mysql> CREATE TABLE stu(-> id int,-> age int,-> name varchar(10),-> CHECK(id < 5)-> );
Query OK, 0 rows affected (0.03 sec)mysql> INSERT INTO stu(id) VALUES(3);
Query OK, 1 row affected (0.01 sec)mysql> INSERT INTO stu(id) VALUES(8);
ERROR 3819 (HY000): Check constraint 'stu_chk_1' is violated.
mysql> SELECT * FROM stu;
+------+------+------+
| id | age | name |
+------+------+------+
| 3 | NULL | NULL |
+------+------+------+
1 row in set (0.00 sec)
值得一提的是,在 MySQL 8.0.16
版本之前的 CHECK 约束
,能被解析但是被忽略掉了,就是 不符合CHECK的数据依然可以被加入到表中。 而在之后的版本上,支持 CHECK约束
,但仍有缺陷!想要 INSER INTO
一个不满足 CHECK
的数据时确实不能通过(详见上面的示例中),但是如果 INSERT INTO
时,未设置被 CHECK
约束的 列
,那么 即使这个列的默认值不符合 CHECK约束
,也还是可以创建成功。
可以看到 主动赋值 不满足 CHECK
的语句会报错,而 默认值 不满足 CHECK
的语句却可以成功插入。
表的设计
表的关系
一对一
例如人和身份证的关系,每个人都对应有着只属于自己的身份证:
一对多
例如学生和班级的关系,一个班级拥有多个学生,但是一个学生只能属于一个班级:
多对多
例如学生、课程、选课表的关系。一个学生可以选择多门课程,一个课程也可以被多个学生选择:
三大范式
表的关系只是设计的最基础的一项,考虑好关系,确认好数据项,将数据填进去即可。
但是那样的设计并不合理,可能会存在 数据冗余、传输性能、查询性能 等问题,所以需要用到三大范式来规范数据库表的设计,减少数据库的冗余性。
范式是针对数据库表设计的几种方案,目前关系数据库有六种范式:第一范式(1NF)、第二范式(2NF)、第三范式(3NF)、巴斯-科德范式(BCNF)、第四范式(4NF)和第五范式(5NF,又称完美范式)。
通常我们使用的都是第一范式(1NF)、第二范式(2NF)、第三范式(3NF),所以又将他们称为三大范式。
第一范式
要求数据库表的每一列都是不可分割的原子数据项。
在这个表中,家庭信息和学校信息并不是原子的,例如家庭信息中包含了家庭组成和所在地,学校信息包含了年级和学位。
对于第一范式,需要确保每一项数据都是不可分割的原子性数据,不能是一个集合。因此要做出如下调整:
第二范式
在第一范式的基础上,非主键数据必须完全依赖主键,不能部分依赖(针对组合主键)。
拿下面这个表来说,相同 订单号 的 订单时间和订单金额 是 一样 的,但是相同 产品号 的 订单时间和订单金额 却 不一样 。这说明订单时间和订单金额 部分依赖主键 —— 只依赖了组合主键中的订单号而没有依赖产品号 。
所以需要将其分割出去单独建立一个表:
第三范式
在第二范式的基础上,每一个非主键数据都必须要和主键直接依赖而非间接依赖,即不能依赖非主键数据。旨在体现依赖关系不可传递性。
如下图,班主任性别 与 班主任年龄 直接依赖于班主任姓名(非主键数据),而与 主键——学号 并没有直接的依赖关系,而是间接,这就是依赖传递:
所以需要将这两项分割出去单独建表: