目录:
- 前言
- 表的约束
- 1、空属性
- 2、默认值
- 空属性与默认值
- 3、列描述
- 4、zerofill
- 5、主键约束
- 6、自增长
- 7、唯一键约束
- 8、外键约束
前言
剑指offer:一年又5天 |
---|
表的约束
我们上一篇文章所讲的数据类型也是一种约束–不同类型有对应的数据范围;约束本质上是通过技术手段倒逼程序员输入正确的数据,反过来站在数据库角度:凡是数据库内的数据都是符合约束的;
约束保证了数据库中数据的完整性和可预期性。
但是仅仅有类型约束还是不够的,本篇文章我们会讲解表的其他约束:空值约束,默认值约束,列描述,零填充,主键,唯一键和外键约束。
1、空属性
两个值:null(默认的)和not null(不为空)
数据库默认字段基本都是字段为空,但是实际开发时,尽可能保证字段不为空,因为数据为空没办法参与运算。
mysql> use db2;
Database changed
mysql> create table tb1(-> c1 int,-> c2 int not null -- 设置c2列为非空-> );
Query OK, 0 rows affected (0.06 sec)mysql> show create table tb1\G
*************************** 1. row ***************************Table: tb1
Create Table: CREATE TABLE `tb1` (`c1` int(11) DEFAULT NULL, -- c1列我们没有设置因此没有‘not null’的约束,并且默认值为null`c2` int(11) NOT NULL -- c2列设置不能为空,没有默认值
) ENGINE=InnoDB DEFAULT CHARSET=utf8
-- 再次声明一下:我们输入的sql语句会先提交到本地的mysqld服务器,经过服务器优化后再执行,
-- 因此现在我们看到的是经过服务器优化后的且实际执行的sql语句,
-- 这些默认值以及engine,charset都是mysqld自动为我们写入的
1 row in set (0.00 sec)mysql> desc tb1;
+-------+---------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra | -- 我们查看第三列属性:Null表示是否可以为null
+-------+---------+------+-----+---------+-------+ -- c1列为yes,c2列为no
| c1 | int(11) | YES | | NULL | |
| c2 | int(11) | NO | | NULL | |
+-------+---------+------+-----+---------+-------+
2 rows in set (0.00 sec)mysql> insert into tb1(c1, c2) values(1,1); -- 插入c1,c2
Query OK, 1 row affected (0.01 sec)mysql> insert into tb1(c1, c2) values(2,2); -- 插入c1,c2
Query OK, 1 row affected (0.00 sec)mysql> insert into tb1(c2) values(3); -- 只插入c2
Query OK, 1 row affected (0.00 sec)mysql> insert into tb1(c1) values(4); -- 只插入c1
ERROR 1364 (HY000): Field 'c2' doesn't have a default value -- 出错,提示没有默认值,我们先不看后面再说mysql> insert into tb1(c1, c2) values(4,null); -- c1插入4,c2插入null
ERROR 1048 (23000): Column 'c2' cannot be null -- 出错,提示c2不能为空mysql> insert into tb1(c1, c2) values(5,null); -- c1插入5,c2插入null
ERROR 1048 (23000): Column 'c2' cannot be null -- 出错,提示c2不能为空mysql> select * from tb1;
+------+----+
| c1 | c2 |
+------+----+
| 1 | 1 |
| 2 | 2 |
| NULL | 3 | -- 只插入c2,c1默认为空->插入成功
+------+----+ -- 插入c1为4,c2为null->插入失败,因为c2设置为not null也就是不可以为空
3 rows in set (0.00 sec)
结论:设置为not null的列,必须给他设置数据。
站在正常的业务逻辑中:
如果班级没有名字,你不知道你在哪个班级
如果教室名字可以为空,就不知道在哪上课
所以我们在设计数据库表的时候,一定要在表中进行限制,满足上面条件的数据就不能插入到表中。这就是“约束”。
什么叫null不会参与运算:这里的null和C语言的null不同,C语言的NULL与数字0和字符’\0’在数值上等价–都0,但是MySQL中null就是什么都没有,什么都没有当然就不能进行操作;
区分 null 与 ‘’(两个单引号), null是什么都没有, ''是一个字符串,但是这个字符串中什么都没写。
示例:
mysql> select 1 + 1;
+-------+
| 1 + 1 |
+-------+
| 2 | -- 运算结果
+-------+
1 row in set (0.00 sec)mysql> select 99 * 10;
+---------+
| 99 * 10 |
+---------+
| 990 | -- 运算结果
+---------+
1 row in set (0.00 sec)mysql> select 1 * null;
+----------+
| 1 + null |
+----------+
| NULL | -- null不参与运算
+----------+
1 row in set (0.00 sec)
2、默认值
所谓默认值就是用户自己设置了就用用户设置的,用户没有设置就这个提前设置好的。
mysql> use db1;
Database changed
mysql> create table person( -- 场景:注册一个社交网站账号-> name varchar(20) default 'momo', -- 名字默认为 'momo'-> gander char(2) default '未知', -- 性别默认为 '未知'-> age int -- 年龄没有写默认值-> );
Query OK, 0 rows affected (0.04 sec)mysql> show create table person\G
*************************** 1. row ***************************Table: person
Create Table: CREATE TABLE `person` (`name` varchar(20) DEFAULT 'momo',`gander` char(2) DEFAULT '未知',`age` int(11) DEFAULT NULL -- mysql服务器自行配置:年龄默认为null
) ENGINE=InnoDB DEFAULT CHARSET=utf8
1 row in set (0.00 sec)mysql> insert into person(name, gander, age) values('宁姚', '女', 20); -- 用户写了就用用户的
Query OK, 1 row affected (0.01 sec)mysql> insert into person(name, gander) values('陈平安', '男'); -- 用户没写就使用默认
Query OK, 1 row affected (0.01 sec)mysql> insert into person(name) values('老道士'); -- 用户没写就使用默认
Query OK, 1 row affected (0.00 sec)mysql> insert into person(age) values(1); -- 用户没写就使用默认
Query OK, 1 row affected (0.00 sec)mysql> select * from person;
+-----------+--------+------+
| name | gander | age |
+-----------+--------+------+
| 宁姚 | 女 | 20 |
| 陈平安 | 男 | NULL |
| 老道士 | 未知 | NULL |
| momo | 未知 | 1 |
+-----------+--------+------+
4 rows in set (0.00 sec)
空属性与默认值
mysql> create table tb1(-> c1 int,-> c2 int not null,-> c3 int not null default 0-> );
Query OK, 0 rows affected (0.03 sec)mysql> show create table tb1\G
*************************** 1. row ***************************Table: tb1
Create Table: CREATE TABLE `tb1` (`c1` int(11) DEFAULT NULL,`c2` int(11) NOT NULL,`c3` int(11) NOT NULL DEFAULT '0'
) ENGINE=InnoDB DEFAULT CHARSET=utf8
1 row in set (0.00 sec)mysql> desc tb1;
+-------+---------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+---------+------+-----+---------+-------+
| c1 | int(11) | YES | | NULL | |
| c2 | int(11) | NO | | NULL | |
| c3 | int(11) | NO | | 0 | |
+-------+---------+------+-----+---------+-------+
3 rows in set (0.00 sec)mysql> insert into tb1(c1, c2, c3) values(1, 1, 1); -- 插入成功
Query OK, 1 row affected (0.01 sec)mysql> insert into tb1(c1, c2, c3) values(2, 2, 2); -- 插入成功
Query OK, 1 row affected (0.01 sec)mysql> insert into tb1(c2, c3) values(2, 2); -- 插入成功,c1不给值,使用默认值:null
Query OK, 1 row affected (0.01 sec)mysql> insert into tb1(c1, c3) values(2, 2); -- 插入失败,c2不给值,由于创建时声明为not null,服务器就没有给默认值null,因此c2是没有值的
ERROR 1364 (HY000): Field 'c2' doesn't have a default valuemysql> insert into tb1(c1, c2, c3) values(2, null, 2); -- 插入失败,c2有not null约束,所以不能插入null
ERROR 1048 (23000): Column 'c2' cannot be nullmysql> insert into tb1(c1, c2) values(2, 2); -- 插入成功,c3不给值,使用默认值 0
Query OK, 1 row affected (0.00 sec)mysql> insert into tb1(c1, c2, c3) values(2, 2, null); -- 插入失败,c3有not null约束,不能插入null
ERROR 1048 (23000): Column 'c3' cannot be null
mysql> select * from tb1;
+------+----+----+
| c1 | c2 | c3 |
+------+----+----+
| 1 | 1 | 1 |
| 2 | 2 | 2 |
| NULL | 2 | 2 |
| 2 | 2 | 0 |
+------+----+----+
4 rows in set (0.00 sec)
总结:
- 没有设置not null和default约束:mysql服务器会自行添加default为null;
- 设置了not null约束:mysqld不会自行添加default为null,并且用户插入数据时不允许插入null;
- 用户在插入数据时可以插入null;
- default在用户没有插入该列属性时使用;
- 既设置了not null又设置了default:可以不插入数据,但是插入数据时一定不能插入 null;
- null和default并不冲突,而是相互补充的,当然一般不会同时设置。
3、列描述
comment是对该列数据的简单描述,类似于C语言的注释,是给程序员看的但并不起到实际约束作用。
mysql> create table tb2(-> name varchar(20) comment '这是用户姓名', -- 列描述 “这是用户姓名”-> age tinyint unsigned comment '这是用户年龄,要在200以内' -- 列描述 “这是用户年龄,要在200以内”-> );
Query OK, 0 rows affected (0.04 sec)mysql> show create table tb2\G
*************************** 1. row ***************************Table: tb2
Create Table: CREATE TABLE `tb2` (`name` varchar(20) DEFAULT NULL COMMENT '这是用户姓名', -- 列描述 “这是用户姓名”`age` tinyint(3) unsigned DEFAULT NULL COMMENT '这是用户年龄,要在200以内' -- 列描述 “这是用户年龄,要在200以内”
) ENGINE=InnoDB DEFAULT CHARSET=utf8
1 row in set (0.00 sec)mysql> insert into tb2(name, age) values('张三', 20);
Query OK, 1 row affected (0.01 sec)mysql> insert into tb2(name, age) values('李四', 0);
Query OK, 1 row affected (0.01 sec)mysql> insert into tb2(name, age) values('王五', 200);
Query OK, 1 row affected (0.01 sec)mysql> insert into tb2(name, age) values('赵六', 201);
Query OK, 1 row affected (0.01 sec)mysql> insert into tb2(name, age) values('赵六', 255);
Query OK, 1 row affected (0.00 sec)mysql> select * from tb2;
+--------+------+
| name | age |
+--------+------+
| 张三 | 20 |
| 李四 | 0 |
| 王五 | 200 |
| 赵六 | 201 | -- 超过200的数据照样可以插入成功
| 赵六 | 255 |
+--------+------+
5 rows in set (0.00 sec)mysql> desc tb2; -- 在表结构中没有comment字段
+-------+---------------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+---------------------+------+-----+---------+-------+
| name | varchar(20) | YES | | NULL | |
| age | tinyint(3) unsigned | YES | | NULL | |
+-------+---------------------+------+-----+---------+-------+
2 rows in set (0.00 sec)
4、zerofill
我们在前面讲了mysql的各种数据类型,以及使用各种数据类型的注意事项,比如float(4,2) 表示最多四位且小数占两位;char(2)表示最多两个字符,那就会有很多疑惑:int后面的数字表示什么意思?
mysql> create table tb3(-> c1 int,-> c2 int-> );
Query OK, 0 rows affected (0.06 sec)mysql> show create table tb3\G
*************************** 1. row ***************************Table: tb3
Create Table: CREATE TABLE `tb3` (`c1` int(11) DEFAULT NULL, -- 11的含义是什么?`c2` int(11) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8
1 row in set (0.00 sec)mysql> insert into tb3(c1, c2) values(1, 1);
Query OK, 1 row affected (0.01 sec)mysql> insert into tb3(c1, c2) values(10, 10);
Query OK, 1 row affected (0.01 sec)mysql> select * from tb3;
+------+------+
| c1 | c2 |
+------+------+
| 1 | 1 |
| 10 | 10 |
+------+------+
2 rows in set (0.00 sec)mysql> alter table tb3 modify c2 int zerofill; -- 修改c2列,添加zerofill约束
Query OK, 2 rows affected (0.09 sec)
Records: 2 Duplicates: 0 Warnings: 0mysql> select * from tb3;
+------+------------+
| c1 | c2 |
+------+------------+
| 1 | 0000000001 | -- 观察发现:1前面有9个0,带上1一共10位
| 10 | 0000000010 | -- 观察发现:10前面有8个0,带上10一共10位
+------+------------+
2 rows in set (0.00 sec)mysql> insert into tb3(c1, c2) values(100, 100);
Query OK, 1 row affected (0.01 sec)mysql> select * from tb3;
+------+------------+
| c1 | c2 |
+------+------------+
| 1 | 0000000001 |
| 10 | 0000000010 |
| 100 | 0000000100 | -- 现在我们发现:int(10)表示的是显示位数,那是不是和float的第一个数字一样限制最大显示位数?
+------+------------+
3 rows in set (0.00 sec)mysql> alter table tb3 modify c2 int(3) zerofill; -- 我们修改c2为int(3),继续测试
Query OK, 0 rows affected (0.00 sec)
Records: 0 Duplicates: 0 Warnings: 0mysql> select * from tb3;
+------+------+
| c1 | c2 |
+------+------+
| 1 | 001 |
| 10 | 010 | -- 显示位数都为3为,不足3位的,高位补0
| 100 | 100 |
+------+------+
3 rows in set (0.00 sec)mysql> insert into tb3(c1, c2) values(1000, 1000); -- 我们发现插入成功了
Query OK, 1 row affected (0.00 sec)mysql> insert into tb3(c1, c2) values(10000, 10000); -- 我们发现插入成功了
Query OK, 1 row affected (0.00 sec)mysql>
mysql> select * from tb3;
+-------+-------+
| c1 | c2 |
+-------+-------+
| 1 | 001 |
| 10 | 010 |
| 100 | 100 |
| 1000 | 1000 | -- 查看也没有问题
| 10000 | 10000 |
+-------+-------+
5 rows in set (0.00 sec)
结论:整形数据类型后面的数字表示的是最小显示位数,如果加上zerofill约束则不足最小显示位数的情况下会在高位补0;
思考:那么为什么默认是10,为什么不是9,不是11?
回答:int类型大小为-2^31 ~ 2^31 -1 ,及2*10^9 (21亿多),因此10位刚好足够显示int的最大值。
补充:001 与 1是否一样
mysql> select c2 ,hex(c2) from tb3 where c2 = 1; -- 查找c2=1的记录, hex()使用10进制形式显示
+------+---------+
| c2 | hex(c2) |
+------+---------+
| 001 | 1 |
+------+---------+
1 row in set (0.00 sec)
结论:数字001还是1,只是显示形式不同而已。
5、主键约束
语法:
primary key(列名);
主键:primary key用来唯一的约束该字段里面的数据,不能重复,不能为空,一张表中最多只能有一个主键;主键所在的列通常是整数类型。
案例:
mysql> create table tb4(-> id int primary key, -> name varchar(20) not null,-> phone varchar(11)-> );
Query OK, 0 rows affected (0.03 sec)mysql> desc tb4;
+-------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| id | int(11) | NO | PRI | NULL | | -- key属性为PRI就是主键的意思primary, 主键的Null属性自动设置为no
| name | varchar(20) | NO | | NULL | |
| phone | varchar(11) | YES | | NULL | |
+-------+-------------+------+-----+---------+-------+
3 rows in set (0.00 sec)
主键不能重复不能为空:
mysql> insert into tb4(id, name, phone) values(001, '张三', '12345678'); -- 插入成功
Query OK, 1 row affected (0.00 sec)mysql> insert into tb4(id, name, phone) values(002, '李四', '23456789'); -- 插入成功
Query OK, 1 row affected (0.00 sec)mysql> insert into tb4(id, name, phone) values(002, '李四', '23456789'); -- 插入失败, 主键重复
ERROR 1062 (23000): Duplicate entry '2' for key 'PRIMARY'mysql> insert into tb4(id, name, phone) values(002, '王五', '23456789'); -- 插入失败, 主键重复
ERROR 1062 (23000): Duplicate entry '2' for key 'PRIMARY'mysql> insert into tb4(id, name, phone) values(003, '李四', '23456789'); -- 插入成功,主键不重复即可,其他列没有限制
Query OK, 1 row affected (0.00 sec)mysql> insert into tb4(id, name, phone) values(null, '李四', '23456789'); -- 插入失败, 主键不能为空
ERROR 1048 (23000): Column 'id' cannot be nullmysql> select * from tb4;
+----+--------+----------+
| id | name | phone |
+----+--------+----------+
| 1 | 张三 | 12345678 |
| 2 | 李四 | 23456789 |
| 3 | 李四 | 23456789 |
+----+--------+----------+
4 rows in set (0.00 sec)
表已经创建好后再追加主键
mysql> desc tb5;
+-------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| id | int(11) | YES | | NULL | |
| name | varchar(20) | YES | | NULL | |
+-------+-------------+------+-----+---------+-------+
2 rows in set (0.00 sec)mysql> alter table tb5 modify id int primary key; -- 设置主键(前提:id列数据无重复)
Query OK, 0 rows affected (0.05 sec)
Records: 0 Duplicates: 0 Warnings: 0mysql> desc tb5;
+-------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| id | int(11) | NO | PRI | NULL | |
| name | varchar(20) | YES | | NULL | |
+-------+-------------+------+-----+---------+-------+
2 rows in set (0.00 sec)
删除主键
mysql> alter table tb5 drop primary key;
Query OK, 0 rows affected (0.06 sec)
Records: 0 Duplicates: 0 Warnings: 0mysql> desc tb5;
+-------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| id | int(11) | NO | | NULL | | -- 主键约束去掉了,null约束还在
| name | varchar(20) | YES | | NULL | |
+-------+-------------+------+-----+---------+-------+
2 rows in set (0.00 sec)
复合主键:一个表只能有一个主键,一个主键可以有多个列。
-- 情景:学生选课
mysql> create table tb6(-> id int,-> name varchar(20),-> class_id int, -- 课程id-> primary key(id, class_id) -- id和class_id 共同构成主键-> );
Query OK, 0 rows affected (0.04 sec)mysql> desc tb6;
+----------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+----------+-------------+------+-----+---------+-------+
| id | int(11) | NO | PRI | NULL | | -- id和class_id都是PRI
| name | varchar(20) | YES | | NULL | |
| class_id | int(11) | NO | PRI | NULL | |
+----------+-------------+------+-----+---------+-------+
3 rows in set (0.00 sec)
mysql> insert into tb6(id, name, class_id) values(001, '张三', 601);
Query OK, 1 row affected (0.01 sec)mysql> insert into tb6(id, name, class_id) values(001, '张三', 602);
Query OK, 1 row affected (0.00 sec)mysql> insert into tb6(id, name, class_id) values(002, '李四', 602);
Query OK, 1 row affected (0.01 sec)mysql> insert into tb6(id, name, class_id) values(002, '李四', 602); -- 主键重复,主键为:2-602
ERROR 1062 (23000): Duplicate entry '2-602' for key 'PRIMARY'
mysql> select * from tb6;
+----+--------+----------+
| id | name | class_id |
+----+--------+----------+
| 1 | 张三 | 601 |
| 1 | 张三 | 602 | -- 一个学生可以选多门课
| 2 | 李四 | 602 | -- 一门课可以被多个学生选
+----+--------+----------+ -- 一个学生不能重复选一门课
3 rows in set (0.00 sec)
6、自增长
auto_increment:当对应的字段,不给值,会自动的被系统触发,系统会从当前字段中已经有的最大值+1操作,得到一个新的不同的值。通常和主键搭配使用,作为逻辑主键。
自增长的特点:
- 任何一个字段要做自增长,前提是本身是一个索引(key一栏有值)
- 自增长字段必须是整数
- 一张表最多只能有一个自增长
mysql> create table tb7(-> id int primary key auto_increment, -- id:主键且自增长-> name varchar(20) not null-> );
Query OK, 0 rows affected (0.03 sec)mysql> show create table tb7\G
*************************** 1. row ***************************Table: tb7
Create Table: CREATE TABLE `tb7` (`id` int(11) NOT NULL AUTO_INCREMENT,`name` varchar(20) NOT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
1 row in set (0.00 sec)mysql> desc tb7;
+-------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra | -- 额外配置:自增长
+-------+-------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| name | varchar(20) | NO | | NULL | |
+-------+-------------+------+-----+---------+----------------+
2 rows in set (0.00 sec)
mysql> insert into tb7(id, name) values(1, '张三');
Query OK, 1 row affected (0.01 sec)mysql> insert into tb7(id, name) values(2, '李四');
Query OK, 1 row affected (0.01 sec)mysql> insert into tb7(name) values('李四'); -- 不插入id,使用它的自增长
Query OK, 1 row affected (0.01 sec)mysql> insert into tb7(name) values('李四');
Query OK, 1 row affected (0.01 sec)mysql> insert into tb7(name) values('李四');
Query OK, 1 row affected (0.00 sec)mysql> select * from tb7;
+----+--------+
| id | name |
+----+--------+
| 1 | 张三 |
| 2 | 李四 |
| 3 | 李四 |
| 4 | 李四 |
| 5 | 李四 |
+----+--------+
5 rows in set (0.00 sec)mysql> select last_insert_id(); -- 查看最后一次插入的自增长列的值:5
+------------------+
| last_insert_id() |
+------------------+
| 5 |
+------------------+
1 row in set (0.00 sec)mysql> show create table tb7\G
*************************** 1. row ***************************Table: tb7
Create Table: CREATE TABLE `tb7` (`id` int(11) NOT NULL AUTO_INCREMENT,`name` varchar(20) NOT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8 -- 中间第二个属性表示下一次插入时自增长列的值:6
1 row in set (0.00 sec)
验证auto_increment自增长策略
mysql> insert into tb7(id, name) values(105, '张飞');
Query OK, 1 row affected (0.01 sec)mysql> insert into tb7(name) values('关羽');
Query OK, 1 row affected (0.01 sec)mysql> select * from tb7;
+-----+--------+
| id | name |
+-----+--------+
| 1 | 张三 |
| 2 | 李四 |
| 3 | 李四 |
| 4 | 李四 |
| 5 | 李四 |
| 105 | 张飞 |
| 106 | 关羽 | -- 接着最大值105往后增长
+-----+--------+
7 rows in set (0.00 sec)mysql> select last_insert_id();
+------------------+
| last_insert_id() |
+------------------+
| 106 |
+------------------+
1 row in set (0.00 sec)mysql> show create table tb7\G
*************************** 1. row ***************************Table: tb7
Create Table: CREATE TABLE `tb7` (`id` int(11) NOT NULL AUTO_INCREMENT,`name` varchar(20) NOT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=107 DEFAULT CHARSET=utf8
1 row in set (0.00 sec)
验证了前面说的:系统会从当前字段中已经有的最大值+1操作,得到一个新的不同的值。
7、唯一键约束
语法:
unique key(列名);
一张表中有往往有很多字段需要唯一性,数据不能重复,但是一张表中只能有一个主键:唯一键就可以解决表中有多个字段需要唯一性约束的问题。
唯一键的本质和主键差不多,唯一键允许为空,而且可以多个为空,空字段不做唯一性比较。
关于唯一键和主键的区别:
我们可以简单理解成,主键更多的是标识唯一性的。而唯一键更多的是保证在业务上,不要和别的信息出现重复(可为空)。
场景:
学校统计各个学生的学号、姓名、电话号和家庭地址信息;
姓名和家庭地址可以出现重复,但是学号和电话号肯定是各不相同的,
那么我们可以选择学号或者电话号作为主键(在这里我们选一列作为主键,不是不能选两列只是在这里选两列逻辑不通)
这里学号作为主键更合适所以我们选择学号,
那么电话号怎么办,如果我们不对它进行限制,因为记录时的疏忽导致好多位同学电话号相同,这显然是不合理的。
mysql> create table stu(-> id int primary key,-> name varchar(20) not null,-> phone varchar(11) unique, -- 设置唯一键约束,也可以写 unique key-> address varchar(32)-> );
Query OK, 0 rows affected (0.04 sec)mysql> show create table stu\G
*************************** 1. row ***************************Table: stu
Create Table: CREATE TABLE `stu` (`id` int(11) NOT NULL,`name` varchar(20) NOT NULL,`phone` varchar(11) DEFAULT NULL,`address` varchar(32) DEFAULT NULL,PRIMARY KEY (`id`),UNIQUE KEY `phone` (`phone`) -- 唯一键约束
) ENGINE=InnoDB DEFAULT CHARSET=utf8
1 row in set (0.00 sec)mysql> desc stu;
+---------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+---------+-------------+------+-----+---------+-------+
| id | int(11) | NO | PRI | NULL | | -- 主键约束,默认不能为空
| name | varchar(20) | NO | | NULL | |
| phone | varchar(11) | YES | UNI | NULL | | -- 唯一键约束UNI:unique,默认可以为null
| address | varchar(32) | YES | | NULL | |
+---------+-------------+------+-----+---------+-------+
4 rows in set (0.00 sec)
mysql> insert into stu(id, name, phone, address) values(1, '张三', '12323432', '骊珠洞天');
Query OK, 1 row affected (0.01 sec)mysql> insert into stu(id, name, phone, address) values(2, '李四', '12323789', '骊珠洞天');
Query OK, 1 row affected (0.01 sec)mysql> insert into stu(id, name, phone, address) values(3, '李四', '12323789', '骊珠洞天'); -- phone重复
ERROR 1062 (23000): Duplicate entry '12323789' for key 'phone'mysql> insert into stu(id, name, phone, address) values(3, '王五', '12323789', '骊珠洞天'); -- phone重复
ERROR 1062 (23000): Duplicate entry '12323789' for key 'phone'mysql> insert into stu(id, name, phone, address) values(3, '王五', '12323888', '骊珠洞天'); -- 插入成功
Query OK, 1 row affected (0.00 sec)mysql> insert into stu(id, name, phone, address) values(4, '王五', null, '骊珠洞天'); -- 插入成功,电话设为null
Query OK, 1 row affected (0.00 sec)mysql> insert into stu(id, name, address) values(5, '赵六', '骊珠洞天'); -- 插入成功,电话使用默认值(null)
Query OK, 1 row affected (0.01 sec)mysql> select * from stu;
+----+--------+----------+--------------+
| id | name | phone | address |
+----+--------+----------+--------------+
| 1 | 张三 | 12323432 | 骊珠洞天 |
| 2 | 李四 | 12323789 | 骊珠洞天 |
| 3 | 王五 | 12323888 | 骊珠洞天 |
| 4 | 王五 | NULL | 骊珠洞天 |
| 5 | 赵六 | NULL | 骊珠洞天 |
+----+--------+----------+--------------+
5 rows in set (0.00 sec)
8、外键约束
外键用于定义主表和从表之间的关系:外键约束主要定义在从表上,主表则必须是有主键约束或unique约束。当定义外键后,要求外键列数据必须在主表的主键列存在或为null。
语法:
foreign key (字段名) references 主表(列)
设置场景:学生选课,如果每个学生选课时都记录他的选课id,选课的名字和教课老师,那么在这张表中会有大量重复的数据,既让表变得复杂也会增加存储的代价,因此我们可以将两张表拆开存储,通过课程id关联在一起。
mysql> create table stu2(-> id int,-> name varchar(20) not null,-> course_id int,-> primary key(id, course_id)-> );
Query OK, 0 rows affected (0.03 sec)mysql> create table cou2(-> id int primary key,-> name varchar(20) not null,-> teacher varchar(20) not null-> );
Query OK, 0 rows affected (0.03 sec)
mysql> insert into cou2 values(601, '计网', 'teacher李');
Query OK, 1 row affected (0.01 sec)mysql> insert into cou2 values(602, '计组', 'teacher王');
Query OK, 1 row affected (0.01 sec)mysql> insert into stu2 values(1, '张三', 601);
Query OK, 1 row affected (0.01 sec)mysql> insert into stu2 values(1, '张三', 602);
Query OK, 1 row affected (0.01 sec)mysql> insert into stu2 values(2, '李四', 602);
Query OK, 1 row affected (0.01 sec)mysql> insert into stu2 values(2, '李四', 603); -- ??
Query OK, 1 row affected (0.00 sec)mysql> select * from stu2;
+----+--------+-----------+
| id | name | course_id |
+----+--------+-----------+
| 1 | 张三 | 601 |
| 1 | 张三 | 602 |
| 2 | 李四 | 602 |
| 2 | 李四 | 603 | -- 李四同学选了603课程,但是我们并没有这个课,这就不符合现实情况
+----+--------+-----------+ -- (不过在逻辑上并没有问题,因为我们前面说:靠课程id联系两张表,只是口头约定,数据库并不知道)
4 rows in set (0.00 sec)mysql> select * from cou2;
+-----+--------+------------+
| id | name | teacher |
+-----+--------+------------+
| 601 | 计网 | teacher李 |
| 602 | 计组 | teacher王 |
+-----+--------+------------+
2 rows in set (0.00 sec)mysql> delete from cou2 where id = 602;
Query OK, 1 row affected (0.00 sec) -- 同样不合理,602课程还有学生,学校不能直接把这门课删掉mysql> select * from cou2;
+-----+--------+------------+
| id | name | teacher |
+-----+--------+------------+
| 601 | 计网 | teacher李 |
+-----+--------+------------+
1 row in set (0.00 sec)
添加外键:
mysql> create table cou3(-> id int primary key,-> name varchar(20) not null,-> teacher varchar(20) not null-> );
Query OK, 0 rows affected (0.02 sec)mysql> create table stu3(-> id int,-> name varchar(20) not null,-> course_id int,-> primary key(id, course_id), -> foreign key(course_id) references cou3(id) -- 外键:学生表的课程id与课程表的id相互关联-> );
Query OK, 0 rows affected (0.03 sec)mysql> desc cou3;
+---------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+---------+-------------+------+-----+---------+-------+
| id | int(11) | NO | PRI | NULL | |
| name | varchar(20) | NO | | NULL | |
| teacher | varchar(20) | NO | | NULL | |
+---------+-------------+------+-----+---------+-------+
3 rows in set (0.01 sec)mysql> desc stu3;
+-----------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-----------+-------------+------+-----+---------+-------+
| id | int(11) | NO | PRI | NULL | |
| name | varchar(20) | NO | | NULL | |
| course_id | int(11) | NO | PRI | NULL | | -- course_id既是主键又是外键,外键属性没有显示出来
+-----------+-------------+------+-----+---------+-------+
3 rows in set (0.00 sec)mysql> alter table stu3 drop primary key; -- 删除学生表的主键
Query OK, 0 rows affected (0.08 sec)
Records: 0 Duplicates: 0 Warnings: 0mysql> desc stu3;
+-----------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-----------+-------------+------+-----+---------+-------+
| id | int(11) | NO | | NULL | |
| name | varchar(20) | NO | | NULL | |
| course_id | int(11) | NO | MUL | NULL | | -- 外键约束标识(之一):mul
+-----------+-------------+------+-----+---------+-------+
3 rows in set (0.00 sec)mysql> alter table stu3 add primary key(id, course_id); -- 学生表添加主键约束
Query OK, 0 rows affected (0.05 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> insert into cou3 values(601, '计网', 'teacher张');
Query OK, 1 row affected (0.00 sec)mysql> insert into cou3 values(602, '计组', 'teacher王');
Query OK, 1 row affected (0.01 sec)mysql> select * from cou3;
+-----+--------+------------+
| id | name | teacher |
+-----+--------+------------+
| 601 | 计网 | teacher张 |
| 602 | 计组 | teacher王 |
+-----+--------+------------+
2 rows in set (0.00 sec)mysql> insert into stu3 values(1, '张三', 601);
Query OK, 1 row affected (0.01 sec)mysql> insert into stu3 values(1, '张三', 602);
Query OK, 1 row affected (0.00 sec)mysql> insert into stu3 values(2, '李四', 602);
Query OK, 1 row affected (0.00 sec)mysql> insert into stu3 values(2, '李四', 603); -- 不允许插入,外键约束错误:因为课程表里没有603
ERROR 1452 (23000): Cannot add or update a child row: a foreign key constraint fails (`db1`.`stu3`, CONSTRAINT `stu3_ibfk_1` FOREIGN KEY (`course_id`) REFERENCES `cou3` (`id`))mysql> insert into stu3 values(2, '李四', 605); -- 不允许插入
ERROR 1452 (23000): Cannot add or update a child row: a foreign key constraint fails (`db1`.`stu3`, CONSTRAINT `stu3_ibfk_1` FOREIGN KEY (`course_id`) REFERENCES `cou3` (`id`))mysql> delete from cou3 where id = 602; -- 不允许删除,因为602课程已经有学生报名(在数据库看来:学生表中仍有记录关联着602这个字段)
ERROR 1451 (23000): Cannot delete or update a parent row: a foreign key constraint fails (`db1`.`stu3`, CONSTRAINT `stu3_ibfk_1` FOREIGN KEY (`course_id`) REFERENCES `cou3` (`id`))
mysql> select * from stu3;
+----+--------+-----------+
| id | name | course_id |
+----+--------+-----------+
| 1 | 张三 | 601 |
| 1 | 张三 | 602 |
| 2 | 李四 | 602 |
+----+--------+-----------+
3 rows in set (0.00 sec)mysql> select * from cou3;
+-----+--------+------------+
| id | name | teacher |
+-----+--------+------------+
| 601 | 计网 | teacher张 |
| 602 | 计组 | teacher王 |
+-----+--------+------------+
2 rows in set (0.00 sec)
此时我们的两张表才算是真正地关联了起来,对两张表的操作都需要受到外键的约束,通过外键来检查我们操作的合法性。
补充:
mysql> delete from stu3 where course_id = 602; -- 602班没有学生
Query OK, 2 rows affected (0.00 sec)mysql> select * from stu3;
+----+--------+-----------+
| id | name | course_id |
+----+--------+-----------+
| 1 | 张三 | 601 |
+----+--------+-----------+
1 row in set (0.00 sec)mysql> delete from cou3 where id = 602; -- 就可以撤销这个班级
Query OK, 1 row affected (0.01 sec)mysql> select * from cou3;
+-----+--------+------------+
| id | name | teacher |
+-----+--------+------------+
| 601 | 计网 | teacher张 |
+-----+--------+------------+
1 row in set (0.00 sec)