<>第十三章:约束
 <>13.1:约束(constraint)概述
 * 
为什么需要约束
 数据完整性(Data Integrity)是指数据的精确性(Accuracy)和可靠性(Reliability
)。它是防止数据库中存在不符合语义规定的数据和防止因错误信息的输入输出造成无效操作或错误信息而提出的。
为了保证数据的完整性,SQL规范以约束的方式对表数据进行额外的条件限制。
 * 实体完整性(Entity Integrity)
 * 域完整性(Domain Integrity)
 * 引用完整性(Referential Integrity)
 * 用户自定义完整性(User-defined Integrity) 
 * 
什么是约束
约束是表级的强制规定。可以在创建表时规定约束(通过CREATE TABLE语句),或者在表创建之后通过ALTER TABLE语句规定约束。
 * 
约束的分类
 * 
根据约束数据列的限制
 * 单列约束:每个约束只约束一列
 * 多列约束:每个约束可约束多列数据 
 * 
根据约束的作用范围
 * 列级约束:只能作用在一个列上,跟在列的定义后面。
 * 表级约束:可以作用在多个列上,不与列一起,而是单独定义。 
位置支持的约束类型是否可以起约束名
列级约束列的后面语法都支持,但外键没有效果不可以
表级约束所有列的下面默认和非空不支持,其他支持可以(主键没有效果)
 * 
根据约束起的作用
 * NOT NULL:非空约束,规定某个字段不能为空
 * UNIQUE:唯一约束,规定某个字段在整个表中唯一的
 * PRIMARY KEY:主键(非空且唯一)约束
 * FOREIGN KEY:外键约束
 * CHECK:检查约束
 * DEFAULT:默认值约束 
 <>13.2:非空约束
 * 
作用
限定某个字段/某列的值不允许为空。
 * 
关键字
NOT NULL
 * 
特点
 * 默认,所有的类型的值都可以是NULL,包括INT、FLOAT等数据类型。
 * 非空约束只能出现在表对象的列上,只能某个列单独限定非空,不能组合非空。
 * 一个表可以有很多列都分别限定了非空。
 * 空字符串"不等于NULL,0也不等于NULL"。 
 * 
添加非空约束
# 建表时 CREATE TABLE test1( id INT NOT NULL, last_name VARCHAR(15) NOT NULL, 
emailVARCHAR(25), salary DECIMAL(10, 2) ); #建表后 ALTER TABLE test1 MODIFY email 
VARCHAR(25) NOT NULL; 
 * 
删除非空约束
ALTER TABLE test1 MODIFY email VARCHAR(25) NULL; 
 <>13.3:唯一约束
 * 
作用
用来限制某个字段/某列的值不能重复。【允许出现多个空值:NULL】
 * 
关键字
UNIQUE
 * 
特点
 * 同一个表可以有多个唯一约束。
 * 唯一约束可以是某一列的值唯一,也可以多个列组合的值唯一。
 * 唯一性约束允许列值为空。
 * 在创建唯一约束的时候,如果不给唯一约束命名,就默认和列名相同。
 * MYSQL会给唯一约束的列上默认创建一个唯一索引。 
 * 
添加唯一约束
 * 
键表时添加
CREATE TABLE test2( id INT UNIQUE, # 列级约束 last_name VARCHAR(15), email VARCHAR(
25), salary DECIMAL(10, 2), #表级约束 CONSTRAINT uk_test2_email UNIQUE(email) ); 
 * 
建表后指定唯一约束
#方式1: ALTER TABLE test2 ADD CONSTRAINT uk_test2_sal UNIQUE(salary); #方式2: ALTER
TABLE test2 MODIFY last_name VARCHAR(15) UNIQUE; 
 * 
复合唯一约束
CREATE TABLE USER( id INT, `name` VARCHAR(15), `password` VARCHAR(25), #表级约束 
CONSTRAINT uk_user_name_pwd UNIQUE(`name`, `password`) ); 
 * 
删除唯一约束
 * 添加唯一性约束的列上也会自动创建唯一索引。
 * 删除唯一约束只能通过删除唯一索引的方式删除。
 * 删除时需要指定唯一索引名,唯一索引名就和唯一约束名一样。
 * 如果创建唯一约束时并未指定名称,如果是单列,就默认和列名相同,如果是组合列,那么默认和()中排在第一个的列名相同。也可以自定义唯一性约束名。 ALTER
TABLE test2 DROP INDEX last_name; ALTER TABLE test2 DROP INDEX uk_test2_sal; 
 <>13.4:PRIMARY KEY约束
 * 
作用
用来唯一标识表中的一行记录。
 * 
关键字
primary key
 * 
特点
 * 主键约束相当于唯一约束+非空约束的组合,主键约束列不允许重复,也不允许出现空值。
 * 一个表最多只能有一个主键约束,建立主键约束可以在列级别创建,也可以在表级别上创建。
 * 主键约束对应着表中的一列或者多列(复合主键)。
 * 如果是多列组合的复合主键约束,那么这些列都不允许为空值,并且组合的值不允许重复。
 * MySQL的主键名是PRIMARY,就算自己命名了主键约束名也没用。
 * 
当创建主键约束时,系统默认会在所在的列或列组合上建立对应的主键索引(能够根据主键查询的,就根据主键查询,效率更高)。如果删除主键约束了,主键约束对应的索引就会自动删除了。
 * 注意:不要修改主键字段的值。修改了主键的值,可能会破坏数据的完整性。 
 * 
添加主键约束
 * 
键表时指定主键约束
# 方式一 CREATE TABLE test4( id INT PRIMARY KEY, last_name VARCHAR(15), salary 
DECIMAL(10, 2), email VARCHAR(25) ); # 方式二 CREATE TABLE test5( id INT, last_name
VARCHAR(15), salary DECIMAL(10, 2), email VARCHAR(25), CONSTRAINT pk_test5_id 
PRIMARY KEY(id) #没有必要起名字 ); 
 * 
建表后添加主键约束
CREATE TABLE test6( id INT, last_name VARCHAR(15), salary DECIMAL(10, 2), email
VARCHAR(25) ); ALTER TABLE test6 ADD PRIMARY KEY(id); 
 * 
复合主键
CREATE TABLE user1( id INT, NAME VARCHAR(15), PASSWORD VARCHAR(25), PRIMARY KEY
(NAME, PASSWORD) ); 
 * 
删除主键约束
ALTER TABLE test6 DROP PRIMARY KEY; 
 <>13.5:自增列
 * 
作用
某个字段的值自增。
 * 
关键字
atuo_increment
 * 
特点和要求
 * 一个表最多只能有一个自增长列。
 * 当需要产生唯一标识或顺序值时,可设置自增长。
 * 自增长列约束的列必须是键列(主键列,唯一键列)。
 * 自增约束的列的数据类型必须是整数类型。
 * 如果自增列指定了0和null,会在当前最大值的基础上自增;如果自增列手动指定了具体值,直接赋值为具体值。 
 * 
指定自增约束
 * 
建表是指定自增列
CREATE TABLE test7( id INT PRIMARY KEY AUTO_INCREMENT, last_name VARCHAR(15) );
 * 
建表后指定自增长列
CREATE TABLE test8( id INT PRIMARY KEY, last_name VARCHAR(15) ); ALTER TABLE 
test8MODIFY id INT AUTO_INCREMENT; 
 * 
删除自增约束
ALTER TABLE test8 MODIFY id INT; 
 * 
MySQL 8.0新特性-自增变量的持久化
 在MySQL 8.0之前,自增主键AUTO_INCREMENT的值如果大于max(primary key) + 1,在MySQL重启后,会重置
AUTO_INCREMENT=max(primary key) + 1,这种现象在某些情况下会导致业务主键冲突或者其他难以发现的问题。
 在MySQL 5.7系统中,对于自增主键的分配规则,是由InnoDB数据字典内部一个计数器
来决定的,而该计数器只在内存中维护,并不会持久到磁盘中。当数据库重启时,该计数器会被初始化。
 MySQL 8.0将自增主键的计数器持久化到重做日志中。每次计数器发生改变,都会将其写入重做日志中。如果数据库重启,InnoDB
会根据重做日志中的信息来初始化计数器的内存值。
 <>13.6:FOREIGN KEY约束
 * 
作用
限定某个表的某个字段的引用完整性。
 * 
关键字
FOREIGN KEY
 * 
主表和从表/父表和子表
 * 主表(父表):被引用的表,被参考的表。
 * 从表(子表):引用别人的表,参考别人的表。 
 * 
特点
 * 从表的外键列,必须引用/参考主表的主键或唯一约束的列。
 * 在创建外键约束时,如果不给外键约束命名,默认名不是列名,而是自动产生一个外键名,也可以指定外键约束名。
 * 创建(CREATE)表时看指定外键约束的话,先创建主表,在创建从表。
 * 删除表时,先删从表(或先删除外键约束),在删除主表。
 * 当主表的记录被从表参照时,主表的记录将不允许删除,如果要删除数据,需要先删除从表中依赖该记录的数据,然后才可以删除主表的数据。
 * 在"从表"中指定外键约束,并且一个表可以建立多个外键约束。
 * 从表的外键列与主表被参照的列名字可以不相同,但是数据类型必须一样,逻辑意义一致。如果类型不一样,创建子表时,就会出现错误ERROR 
1005(HY000): Can't create table'database.tablename'(errno: 150)。
 * 当创建外键约束时,系统默认会在所在的列上建立对应的普通索引。但是索引名是外键的约束名。
 * 删除外键约束后,必须手动删除对应的索引。 
 * 
添加外键约束
 * 
建表时添加外键约束
#①先创建主表 CREATE TABLE dept1( dept_id INT PRIMARY KEY, dept_name VARCHAR(15) ); 
#②再创建从表 CREATE TABLE emp1( emp_id INT PRIMARY KEY AUTO_INCREMENT, emp_name 
VARCHAR(15), department_id INT, CONSTRAINT fk_emp1_dept_id FOREIGN KEY (
department_id) REFERENCES dept1(dept_id) ); 
 * 
建表后添加外键约束
CREATE TABLE dept2( dept_id INT PRIMARY KEY, dept_name VARCHAR(15) ); CREATE 
TABLE emp2( emp_id INT PRIMARY KEY AUTO_INCREMENT, emp_name VARCHAR(15), 
department_idINT ); ALTER TABLE emp2 ADD CONSTRAINT fk_emp2_dept_id FOREIGN KEY(
department_id) REFERENCES dept2(dept_id); 
 * 
约束等级
 * Cascade方式:在父表上update/delete记录时,同步update/delete掉子表的匹配记录。
 * Set null方式:在父表上update/delete记录时,将子表上匹配记录的列设为null,但是要注意子表的外键列不能为not null。
 * No action方式:如果子表中有匹配的记录,则不允许对父表对应候选键进行update/delete操作。
 * Restrict方式:同no action,都是立即检查外键约束。
 * Set default方式:(在可视化工具SQLyog中可能显示空白):父表有变更时,子表将外键列设置成一个默认的值,但Innodb不能识别。 
如果没有指定等级,就相当于Restrict方式。对于外键约束,最好是采用:ON UPDATE CASCADE ON DELETE RESTRICT的方式。
CREATE TABLE dept( did INT PRIMARY KEY, #部门编号 dname VARCHAR(50) #部门名称 ); CREATE
TABLE emp( eid INT PRIMARY KEY, #员工编号 ename VARCHAR(5), #员工姓名 deptid INT, 
#员工所在的部门 #把修改操作设置为级联修改等级,把删除操作设置为set null等级 FOREIGN KEY (deptid) REFERENCES dept
(did) ON UPDATE CASCADE ON DELETE SET NULL ); INSERT INTO dept VALUES(1001, 
'教学部'); INSERT INTO dept VALUES(1002, '财务部'); INSERT INTO dept VALUES(1003, 
'咨询部'); INSERT INTO emp VALUES(1, '张三', 1001); #在添加这条记录时,要求部门表有1001部门 INSERT 
INTO emp VALUES(2, '李四', 1001); INSERT INTO emp VALUES(3, '王五', 1002); UPDATE 
deptSET did = 1004 WHERE did = 1002; DELETE FROM dept WHERE did = 1004; SELECT *
FROM dept; SELECT * FROM emp; /* dept表 +--------+------------+ | did | dname | 
+--------+------------+ | 1001 | 教学部 | +--------+------------+ | 1003 | 咨询部 | 
+--------+------------+ */ /* emp表 +--------+------------+------------+ | eid | 
ename | deptid | +--------+------------+------------+ | 1 | 张三 | 1001 | 
+--------+------------+------------+ | 2 | 李四 | 1001 | 
+--------+------------+------------+ | 3 | 王五 | null | 
+--------+------------+------------+ */ 
 * 
删除外键约束
ALTER TABLE emp1 DROP FOREIGN KEY fk_emp1_dept_id; 
 * 
开发中应用场景
 * 
如果两个表之间有关系(一对一、一对多),他们之间是否一定要建外键约束?
不是。
 * 
建和不建外键约束有什么区别?
 建外键约束,你的操作(创建表、删除表、添加、修改、删除)会受到限制,从语法层面受到限制。
 不建外键约束,你的操作(创建表、删除表、添加、修改、删除)不受限制,要保证数据的引用完整性,只能依靠程序员的自觉,或者是在Java程序中进行限定。
 * 
那么建和不建外建约束和查询有没有关系?
没有。
 * 
说明
 在MySQL里,外建约束是有成本的,需要消耗系统资源。对于大并发的SQL操作,有可能会不适合。比如大型网站的中央数据库,可能会
因为外建约束的系统开销而变得非常慢。所以,MySQL允许你不使用系统自带的外键约束,在应用层面
完成检查数据的一致性的逻辑。也就是说,即使你不用外键约束,也要想办法通过应用层面的附加逻辑,来实现外键约束的功能,确保数据的一致性。
 * 
阿里开发规范
【强制】不得使用外键与级联,一切外键概念必须在应用层解决。
 <>13.7:CHECK约束
 * 
作用
检查某个字段的值是否符合要求,一般指的是值的范围。
 * 
关键字
CHECK
 * 
说明:MySQL 5.7不支持
 MySQL 5.7可以使用check约束,但check约束对数据验证没有任何作用。添加数据时,没有任何错误或警告。但是**MySQL 8.0中可以使用
check约束了**。
CREATE TABLE test10( id INT, last_name VARCHAR(15), salary DECIMAL(10, 2) CHECK
(salary > 2000) ); INSERT INTO test10 VALUES(1, 'Tom', 2500); #添加失败 INSERT INTO 
test10VALUES(2, 'Tom1', 1500); SELECT * FROM test10; /* test10表 
+--------+------------+------------+ | id | last_name | salary | 
+--------+------------+------------+ | 1 | Tom | 2500.00 | 
+--------+------------+------------+ */ 
 <>13.8:DEFAULT约束
 * 
作用
给某个字段/某列指定默认值,一旦设置默认值,在插入数据时,如果此字段没有显示赋值,则赋值为默认值。
 * 
关键字
DEFAULT
 * 
字段加默认值
 * 
建表时添加默认值
CREATE TABLE test11( id INT, last_name VARCHAR(15), salary DECIMAL(10, 2) 
DEFAULT 2000 ); 
 * 
建表后添加默认值
CREATE TABLE test12( id INT, last_name VARCHAR(15), salary DECIMAL(10, 2) ); 
ALTER TABLE test12 MODIFY salary DECIMAL(8, 2) DEFAULT 2500; 
 * 
删除默认值约束
ALTER TABLE test12 MODIFY salary DECIMAL(8, 2);