约束
1. 概述
为了防止不符合规范的数据进入数据库,在用户对数据进行插入、修改、删除等操作时,DBMS自动按照一定的约束条件对数据进行监测,使不符合规范的数据不能进入数据库,以确保数据库中存储的数据正确、有效、相容。
约束条件与数据类型的宽度一样,都是可选参数,主要分为以下几种:
约束条件 | 解释 |
---|---|
NOT NULL | 非空约束,指定某列不能为空; |
UNIQUE | 唯一约束,指定某列或者几列组合不能重复 |
PRIMARY KEY | 主键,指定该列的值可以唯一地标识该列记录 |
FOREIGN KEY | 外键,指定该行记录从属于主表中的一条记录,主要用于参照完整性 |
2. NOT NULL
是否可空,null表示空,非字符串
not null - 不可空
null - 可空
2.1 实例
mysql> create table t12 (id int not null);
Query OK, 0 rows affected (0.02 sec)
mysql> select * from t12;
Empty set (0.00 sec)
mysql> desc t12;
+-------+---------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+---------+------+-----+---------+-------+
| id | int(11) | NO | | NULL | |
+-------+---------+------+-----+---------+-------+
row in set (0.00 sec)
#不能向id列插入空元素。
mysql> insert into t12 values (null);
ERROR 1048 (23000): Column 'id' cannot be null
mysql> insert into t12 values (1);
Query OK, 1 row affected (0.01 sec)
2.2 DEFAULT
我们约束某一列不为空,如果这一列中经常有重复的内容,就需要我们频繁的插入,这样会给我们的操作带来新的负担,于是就出现了默认值的概念。
默认值,创建列时可以指定默认值,当插入数据时如果未主动设置,则自动添加默认值。
not null + default
mysql> create table t13 (id1 int not null,id2 int not null default 222);
Query OK, 0 rows affected (0.01 sec)
mysql> desc t13;
+-------+---------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+---------+------+-----+---------+-------+
| id1 | int(11) | NO | | NULL | |
| id2 | int(11) | NO | | 222 | |
+-------+---------+------+-----+---------+-------+
rows in set (0.01 sec)
# 只向id1字段添加值,会发现id2字段会使用默认值填充
mysql> insert into t13 (id1) values (111);
Query OK, 1 row affected (0.00 sec)
mysql> select * from t13;
+-----+-----+
| id1 | id2 |
+-----+-----+
| 111 | 222 |
+-----+-----+
row in set (0.00 sec)
# id1字段不能为空,所以不能单独向id2字段填充值;
mysql> insert into t13 (id2) values (223);
ERROR 1364 (HY000): Field 'id1' doesn't have a default value
# 向id1,id2中分别填充数据,id2的填充数据会覆盖默认值
mysql> insert into t13 (id1,id2) values (112,223);
Query OK, 1 row affected (0.00 sec)
mysql> select * from t13;
+-----+-----+
| id1 | id2 |
+-----+-----+
| 111 | 222 |
| 112 | 223 |
+-----+-----+
rows in set (0.00 sec)
3. UNIQUE
唯一约束,指定某列或者几列组合不能重复。
3.1 示例:
方法一:
create table department1(
id int,
name varchar(20) unique,
comment varchar(100)
);
方法二:
create table department2(
id int,
name varchar(20),
comment varchar(100),
unique(name)
);
mysql> insert into department1 values(1,'IT','技术');
Query OK, 1 row affected (0.00 sec)
mysql> insert into department1 values(1,'IT','技术');
ERROR 1062 (23000): Duplicate entry 'IT' for key 'name'
3.2 not null + unique
mysql> create table t1(id int not null unique);
Query OK, 0 rows affected (0.02 sec)
mysql> desc t1;
+-------+---------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+---------+------+-----+---------+-------+
| id | int(11) | NO | PRI | NULL | |
+-------+---------+------+-----+---------+-------+
row in set (0.00 sec)
3.3 联合唯一
create table service(
id int primary key auto_increment,
name varchar(20),
host varchar(15) not null,
port int not null,
unique(host,port) #联合唯一
);
mysql> insert into service values
-> (1,'nginx','192.168.0.10',80),
-> (2,'haproxy','192.168.0.20',80),
-> (3,'mysql','192.168.0.30',3306)
-> ;
Query OK, 3 rows affected (0.01 sec)
Records: 3 Duplicates: 0 Warnings: 0
mysql> insert into service(name,host,port) values('nginx','192.168.0.10',80);
ERROR 1062 (23000): Duplicate entry '192.168.0.10-80' for key 'host'
4. PRIMARY KEY
主键为了保证表中的每一条数据的该字段都是表格中的唯一值。换言之,它是用来独一无二地确认一个表格中的每一行数据。
主键可以包含一个字段或多个字段。当主键包含多个栏位时,称为组合键 (Composite Key),也可以叫联合主键。
主键可以在建置新表格时设定 (运用 CREATE TABLE 语句),或是以改变现有的表格架构方式设定 (运用 ALTER TABLE)。
主键必须唯一,主键值非空;可以是单一字段,也可以是多字段组合。
4.1 单字段主键
============单列做主键===============
#方法一:not null+unique
create table department1(
id int not null unique, #主键
name varchar(20) not null unique,
comment varchar(100)
);
mysql> desc department1;
+---------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+---------+--------------+------+-----+---------+-------+
| id | int(11) | NO | PRI | NULL | |
| name | varchar(20) | NO | UNI | NULL | |
| comment | varchar(100) | YES | | NULL | |
+---------+--------------+------+-----+---------+-------+
rows in set (0.01 sec)
#方法二:在某一个字段后用primary key
create table department2(
id int primary key, #主键
name varchar(20),
comment varchar(100)
);
mysql> desc department2;
+---------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+---------+--------------+------+-----+---------+-------+
| id | int(11) | NO | PRI | NULL | |
| name | varchar(20) | YES | | NULL | |
| comment | varchar(100) | YES | | NULL | |
+---------+--------------+------+-----+---------+-------+
rows in set (0.00 sec)
#方法三:在所有字段后单独定义primary key
create table department3(
id int,
name varchar(20),
comment varchar(100),
primary key(id); #创建主键并为其命名pk_name
mysql> desc department3;
+---------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+---------+--------------+------+-----+---------+-------+
| id | int(11) | NO | PRI | NULL | |
| name | varchar(20) | YES | | NULL | |
| comment | varchar(100) | YES | | NULL | |
+---------+--------------+------+-----+---------+-------+
rows in set (0.01 sec)
# 方法四:给已经建成的表添加主键约束
mysql> create table department4(
-> id int,
-> name varchar(20),
-> comment varchar(100));
Query OK, 0 rows affected (0.01 sec)
mysql> desc department4;
+---------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+---------+--------------+------+-----+---------+-------+
| id | int(11) | YES | | NULL | |
| name | varchar(20) | YES | | NULL | |
| comment | varchar(100) | YES | | NULL | |
+---------+--------------+------+-----+---------+-------+
rows in set (0.01 sec)
mysql> alter table department4 modify id int primary key;
Query OK, 0 rows affected (0.02 sec)
Records: 0 Duplicates: 0 Warnings: 0
mysql> desc department4;
+---------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+---------+--------------+------+-----+---------+-------+
| id | int(11) | NO | PRI | NULL | |
| name | varchar(20) | YES | | NULL | |
| comment | varchar(100) | YES | | NULL | |
+---------+--------------+------+-----+---------+-------+
rows in set (0.01 sec)
4.2 多字段主键
==================多列做主键================
create table service(
ip varchar(15),
port char(5),
service_name varchar(10) not null,
primary key(ip,port)
);
mysql> desc service;
+--------------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+--------------+-------------+------+-----+---------+-------+
| ip | varchar(15) | NO | PRI | NULL | |
| port | char(5) | NO | PRI | NULL | |
| service_name | varchar(10) | NO | | NULL | |
+--------------+-------------+------+-----+---------+-------+
rows in set (0.00 sec)
mysql> insert into service values
-> ('172.16.45.10','3306','mysqld'),
-> ('172.16.45.11','3306','mariadb')
-> ;
Query OK, 2 rows affected (0.00 sec)
Records: 2 Duplicates: 0 Warnings: 0
mysql> insert into service values ('172.16.45.10','3306','nginx');
ERROR 1062 (23000): Duplicate entry '172.16.45.10-3306' for key 'PRIMARY'
4.3 自动增长 auto_increment
约束字段为自动增长,被约束的字段必须同时被key约束
4.3.1 设置自动增长
#不指定id,则自动增长
create table student(
id int primary key auto_increment,
name varchar(20),
sex enum('male','female') default 'male'
);
mysql> desc student;
+-------+-----------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------+-----------------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| name | varchar(20) | YES | | NULL | |
| sex | enum('male','female') | YES | | male | |
+-------+-----------------------+------+-----+---------+----------------+
mysql> insert into student(name) values
-> ('nick'),
-> ('tank')
-> ;
mysql> select * from student;
+----+------+------+
| id | name | sex |
+----+------+------+
| 1 | nick | male |
| 2 | tank | male |
+----+------+------+
#也可以指定id
mysql> insert into student values(4,'asb','female');
Query OK, 1 row affected (0.00 sec)
mysql> insert into student values(7,'wsb','female');
Query OK, 1 row affected (0.00 sec)
mysql> select * from student;
+----+------+--------+
| id | name | sex |
+----+------+--------+
| 1 | nick | male |
| 2 | tank | male |
| 4 | asb | female |
| 7 | wsb | female |
+----+------+--------+
#对于自增的字段,在用delete删除后,再插入值,该字段仍按照删除前的位置继续增长
mysql> delete from student;
Query OK, 4 rows affected (0.00 sec)
mysql> select * from student;
Empty set (0.00 sec)
mysql> insert into student(name) values('ysb');
mysql> select * from student;
+----+------+------+
| id | name | sex |
+----+------+------+
| 8 | ysb | male |
+----+------+------+
#应该用truncate清空表,比起delete一条一条地删除记录,truncate是直接清空表,在删除大表时用它
mysql> truncate student;
Query OK, 0 rows affected (0.01 sec)
mysql> insert into student(name) values('nick');
Query OK, 1 row affected (0.01 sec)
mysql> select * from student;
+----+------+------+
| id | name | sex |
+----+------+------+
| 1 | nick | male |
+----+------+------+
row in set (0.00 sec)
5. FOREIGN KEY
多表 :
假设我们要描述所有公司的员工,需要描述的属性有这些 : 工号 姓名 部门
公司有3个部门,但是有1个亿的员工,那意味着部门这个字段需要重复存储,部门名字越长,越浪费
解决方法: 我们完全可以定义一个部门表 然后让员工信息表关联该表,如何关联,即foreign key
5.1 foreign key 带来的约束作用
- 必须先创建主表, 再创建从表
- 在从表中插入一条记录,关联了一个主表中不存在的id , 导致插入失败, 必须保证外键的值必须是在主表中存在的
- 插入数据的顺序, 先插入主表记录, 在插入从表记录
- 从表更新外键时也必须保证, 外键的值在主表中是存在的
- 删除主表记录前 要保证从表中没有外键关联被删除的id
- 更新删除主表记录的主键时, 要保证从表中没有外键关联主表主键
5.2 示例
create table class(
id int primary key auto_increment,
name char(20)
);
create table student(
id int primary key auto_increment,
name char(20),
gender char(1),
c_id int,
foreign key(c_id) references class(id)
);
5.3 级联操作
当我们需要删除部门(主表)信息 时,必须先删除从表中关联的数据,很麻烦
级联操作指的就是,当你操作主表时,自动的操作从表
5.3.1 两种级联操作
-
级联的删除
当删除主表时自动删除从表中相关数据
-
级联更新
当主表的主键更新时自动的更新关联的从表数据
案例, 以上面的班级个学员为例:
drop table if exists class;
# 如果这表存在 才执行删除 可以避免报错 if exists
# if not exists 如果不存在才执行
create table class(
id int primary key auto_increment,
name char(20)
);
insert into class values(null,"py9");
insert into class values(null,"py10");
#创建表的时候指定级联操作
drop table if exists student;
create table student(
id int primary key auto_increment,
name char(20),
gender char(1),
c_id int,
foreign key(c_id) references class(id)
on update cascade
on delete cascade
);
# 级联操作可以单独使用 也可以一起使用 空格隔开即可
insert into student values(null,"jack","m",1);
insert into student values(null,"rose","m",1);
insert into student values(null,"tom","m",2);
多表关系
1. 为什么要分表操作
有人说, 一张表不是很好吗? 分成多个表进行操作又乱又麻烦
其实不然, 就以下表为例:
看起来是不是很乱, 而且如果生产部的主管发生变更, 那所有生产部人员的Manager都需要进行相应的变更, 这样极大地增加了工作的难度和复杂度
所以我们就需要进行分表操作, 如果部门主管发生变更, 只需要改右边的表就可以了, 左边的表不需要变动
多表操作的优点:
- 节省内存空间
- 表与表之间分开, 除了外键约束之外, 没有任何关系, 修改一个表不会影响另外的表
2. 多对一
指的是从表的多条数据指向主表的同一个记录,比如员工和部门的关系, 一个部门可以有好多员工, 这就是多对一
处理方式
在多的一方保存相应的一的一方的编号
#部门:
create table dept(
id int primary key auto_increment,
name char(20),
job char(50),
manager char(10)
);
#老师表:
create table teacher(
id int primary key auto_increment,
name char(20),
gender char(1),
dept_id int,
foreign key(t_id) references teacher(id),
);
3. 多对多
例如老师和学生, 一个学生有不同的老师来教不同的课程, 二一个老师又有很多学生, 这就是多对多关系
处理方式
建立一个中间表, 专门存放关系, 至少具备两个字段分别指向老师和学生的主键,两个字段都是外键 如下:
create table t_s_r(
id int primary key auto_increment,
t_id int,
s_id int,
foreign key(t_id) references teacher(id),
foreign key(s_id) references student(id),
unique key(t_id,s_id)
);
上表中id是可选的,问题是如何保证没有重复的关系 ?
方式1:
给两个字段设置为联合唯一 + 非空
# 假设表已经存在了
alter table t_s_r add unique key(t_id,s_id);
# 创建表时指定 多字段联合唯一
create table t_s_r2(t_id int,s_id int,unique key(t_id,s_id));
方式2:
# 推荐 将中间的关系表 两个id 作为联合主键 同时具备了 唯一且非空约束
create table t_s_r3(t_id int,s_id int,primary key(t_id,s_id));
案例
create table student(id int primary key auto_increment,name char(10));
create table teacher(id int primary key auto_increment,name char(10));
create table t_s_r(
t_id int,
s_id int,
foreign key(t_id) references teacher(id),
foreign key(s_id) references student(id),
primary key(t_id,s_id)
);
# 先插入 学生或是老师都可以 但是关系表一定是最后添加的
insert into teacher values(null,"bgon"),(null,"nike");
insert into student values(null,"老王"),(null,"老李");
# 老王被bgon教过
insert into t_s_r values(1,1);
# nike教过老李
insert into t_s_r values(2,2);
# nike教过老王
insert into t_s_r values(2,1);
已知老师名称为bgon 请找出他教过那些学生
1.通过名字获取 bgon的id
2.拿着id取关系表中拿到一堆学生的id
3.通过学生的id取出学生的信息
select id from teacher where name = "bgon";
select s_id from t_s_r where t_id = 1;
select * from student where id = 1;
# 子查询方式 把一条语句的结果作为另一条语句的条件!
select * from student where id = (select s_id from t_s_r where t_id = (select id from teacher where name = "bgon"));
已知学生名为老李 请查询出 哪些老师教过他
1.通过名字获取老李的id
select id from student where name = "老李";
2.拿着id去关系表 找出老师的id
select t_id from t_s_r where s_id = 2;
3.通过老师的id取出老师的信息
select name from teacher where id = x;
# 子查询方式:
select name from teacher where id = (
select t_id from t_s_r where s_id = (
select id from student where name = "老李"
)
);
4. 一对一
如一个客户对应一个学生, 站在两边看都是一对一的关系,
处理方式,
确定先后顺序, 将先存在的数据作为主表 ,后存在的作为从表
使两个表的id保持一一对应
方法1: 从表的id 即是主键又是外键
方法2:从表的id设置为外键 并保证唯一
案例:
# 人员表
create table person(
id int primary key auto_increment,
name char(10),
age int
);
# 详情表
create table person_info(
id int primary key,
height float,
weight float,
foreign key(id) references person(id)
);
#再这样的关系中 必须先插入主表即person 拿到一个id 在添加详情表的数据
#将一条完整数拆分到不同表中,可以提高查询的效率,上述方式称之为垂直分表!