数据完整性约束
主键约束
主键就是表中的一列或多个列的一组,其值能唯一地标志表中的每一行。通过定义PRIMARY KEY约束来创建主键,而且PRIMARY KEY约束中的列不能取空值。由于PRIMARY KEY约束能确保数据的唯一,所以经常用来定义标志列。当为表定义PRIMARY KEY约束时,MySQL为主键列创建唯一性索引,实现数据的唯一性,在查询中使用主键时,该索引可用来对数据进行快速访问。如果 PRIMARY KEY 约束是由多列组合定义的,则某一列的值可以重复,但 PRIMARY KEY 约束定义中所有列的组合值必须唯一。
可以用两种方式定义主键:作为列或表的完整性约束。作为列的完整性约束时,只需在列定义的时候加上关键字PRIMARY KEY,这个在2.1.2节中做过介绍。作为表的完整性约束时,需要在语句最后加上一条PRIMARY KEY(col_name,…)语句。
创建表XS1,将姓名定义为主键。
CREATE TABLE XS1
(
学号 varchar(6) NULL,
姓名 varchar(8) NOT NULL PRIMARY KEY ,
出生日期 datetime
);
说明:例中主键定义于空指定之后,空指定也可以在主键之后指定。
当表中的主键为复合主键时,只能定义为表的完整性约束。
创建course表来记录每门课程的学生学号、姓名、课程号、学分和毕业日期。其中学号、课程号和毕业日期构成复合主键。
CREATE TABLE course
(
学号 varchar(6) NOT NULL,
姓名 varchar(8) NOT NULL,
毕业日期 date NOT NULL,
课程号 varchar(3) ,
学分 tinyint ,
PRIMARY KEY (学号, 课程号, 毕业日期)
);
如果作为主键的一部分的一个列没有定义为NOT NULL,MySQL就自动把这个列定义为NOT NULL。实际上,在前面的例5.8中,可以忽略姓名列中的NOT NULL声明,但是为了清楚起见,最好包含这个空指定。
原则上,任何列或者列的组合都可以充当一个主键。但是主键列必须遵守一些规则。这些规则源自于关系模型理论和MySQL所制定的规则:
(1)每个表只能定义一个主键。来自关系模型的这一规则也适用于MySQL。
(2)关系模型理论要求必须为每个表定义一个主键。然而,MySQL并不要求这样,可以创建一个没有主键的表。但是,从安全角度应该为每个基础表指定一个主键。主要原因在于,没有主键,可能在一个表中存储两个相同的行。因此,两个行不能彼此区分。在查询过程中,它们将会满足同样的条件,在更新的时候也总是一起更新,可能会导致数据库崩溃。
(3)表中的两个不同的行在主键上不能具有相同的值。这就是唯一性规则。
(4)如果从一个复合主键中删除一列后,剩下的列构成主键仍然满足唯一性原则,那么,这个复合主键是不正确的,这条规则称为最小化规则(minimality rule)。也就是说,复合主键不应该包含一个不必要的列。
(5)一个列名在一个主键的列列表中只能出现一次。
MySQL自动地为主键创建一个索引。通常,这个索引名为PRIMARY。然而,可以重新给这个索引起名。
创建例5.9中的course表,把主键创建的索引命名为INDEX_course。
CREATE TABLE course
(
学号 varchar(6) NOT NULL,
姓名 varchar(8) NOT NULL,
毕业日期 datetime NOT NULL,
课程号 varchar(3),
学分 tinyint ,
PRIMARY KEY INDEX_course(学号, 课程号, 毕业日期)
);
在关系模型中,替代键像主键一样,是表的一列或一组列,它们的值在任何时候都是唯一的。替代键是没有被选做主键的候选键。定义替代键的关键字是UNIQUE。
在表XS1中将姓名列定义为一个替代键。
CREATE TABLE XS1
(
学号 varchar(6) NULL,
姓名 varchar(8) NOT NULL UNIQUE,
出生日期 datetime NULL,
PRIMARY KEY(学号)
);
说明:关键字UNIQUE表示“姓名”是一个替代键,其列值必须是唯一的。
替代键也可以定义为表的完整性约束,前面语句可以这样定义:
CREATE TABLE XS1
(
学号 varchar(6) NULL,
姓名 varchar(8) NOT NULL,
出生日期 datetime NULL,
PRIMARY KEY(学号),
UNIQUE(姓名)
);
在MySQL中替代键和主键的区别主要有以下几点。
(1)一个数据表只能创建一个主键。但一个表可以有若干个UNIQUE键,并且它们甚至可以重合,例如,在C1和C2列上定义了一个替代键,并且在C2和C3上定义了另一个替代键,这两个替代键在C2列上重合了,而MySQL允许这样。
(2)主键字段的值不允许为NULL,而UNIQUE字段的值可取NULL,但是必须使用NULL或NOT NULL声明。
(3)一般创建PRIMARY KEY约束时,系统会自动产生PRIMARY KEY索引。创建UNIQUE约束时,系统自动产生UNIQUE索引。
通过PRIMERY KEY约束和UNIQUE约束可以实现表的所谓实体完整性约束。定义为PRIMERY KEY和UNIQUE KEY的列上都不允许出现的值。
在本书所举例的XSCJ数据库中,有很多规则是和表之间的关系有关的。例如,存储在XS_KC表中的所有学号必须存在于XS表的学号列中。XS_KC表中的所有课程号也必须出现在KC表的课程号列中。这种类型的关系就是参照完整性约束(referential integrity constraint)。参照完整性约束是一种特殊的完整性约束,实现为一个外键。所以XS_KC表中的学号列和课程号列都可以定义为一个外键。可以在创建表或修改表时定义一个外键声明。
定义外键的语法格式已经在介绍索引时给出了,这里列出reference_definition的定义。
reference_definition语法格式如下:
REFERENCES tbl_name [(index_col_name,...)]
[ON DELETE {RESTRICT | CASCADE | SET NULL | NO ACTION}]
[ON UPDATE {RESTRICT | CASCADE | SET NULL | NO ACTION}]
说明:外键被定义为表的完整性约束,reference_definition中包含了外键所参照的表和列,还可以声明参照动作。
● tb1_name:外键所参照的表名,这个表叫做被参照表。而外键所在的表叫做参照表。因此,在下页中的例5.12中,XS1是参照表,而XS是被参照表。
● index_col_name:格式为
col_name [(length)] [ASC | DESC]
col_name:被参照的列名。外键可以引用一个或多个列,外键中的所有列值在引用的列中必须全部存在。外键可以只引用主键和替代键。外键不能引用被参照表中随机的一组列,它必须是被参照表的列的一个组合且其中的值都保证是唯一的。
● ON DELETE | ON UPDATE:可以为每个外键定义参照动作。参照动作包含两部分:
在第一部分中,指定这个参照动作应用哪一条语句。这里有两条相关的语句,即UPDATE和DELETE语句;
在第二部分中,指定采取哪个动作。可能采取的动作是RESTRICT、CASCADE、SET NULL、NO ACTION和SET DEFAULT。接下来说明这些不同动作的含义。
RESTRICT:当要删除或更新父表中被参照列上在外键中出现的值时,拒绝对父表的删除或更新操作。
CASCADE:从父表删除或更新行时自动删除或更新子表中匹配的行。
SET NULL:当从父表删除或更新行时,设置子表中与之对应的外键列为NULL。如果外键列没有指定NOT NULL限定词,这就是合法的。
NO ACTION:NO ACTION意味着不采取动作,就是如果有一个相关的外键值在被参考的表里,删除或更新父表中主要键值的企图不被允许,和RESTRICT一样。
SET DEFAULT:作用和SET NULL一样,只不过SET DEFAULT是指定子表中的外键列为默认值。
如果没有指定动作,两个参照动作就会默认地使用RESTRICT。
外键目前只可以用在那些使用InnoDB存储引擎创建的表中,对于其他类型的表,MySQL服务器能够解析CREATE TABLE语句中的FOREIGN KEY语法,但不能使用或保存它。
创建XS1表,所有的XS表中学生学号都必须出现在XS1表中,假设已经使用学号列作为主键创建了XS表。
CREATE TABLE XS1
(
学号 varchar(6) NULL,
姓名 varchar(8) NOT NULL,
出生日期 datetime NULL,
PRIMARY KEY (姓名),
FOREIGN KEY (学号)
REFERENCES XS (学号)
ON DELETE RESTRICT
ON UPDATE RESTRICT
);
说明:在这条语句中,定义一个外键的实际作用是,在这条语句执行后,确保MySQL插入到外键中的每一个非空值都已经在被参照表中作为主键出现。
这意味着,对于XS1表中的每一个学号,都执行一次检查,看这个号码是否已经出现在XS表的学号列(主键)中。如果情况不是这样,用户或应用程序会接收到一条出错消息,并且更新被拒绝。这也适用于使用UPDATE语句更新XS1表中的学号列。即MySQL确保了XS1表中的学号列的内容总是XS表中学号列的内容的一个子集。也就是说,下面的SELECT语句不会返回任何行:
SELECT *
FROM XS1
WHERE 学号 NOT IN
(SELECT 学号
FROM XS
);
当指定一个外键的时候,以下的规则适用:
(1)被参照表必须已经用一条CREATE TABLE语句创建了,或者必须是当前正在创建的表。在后一种情况下,参照表是同一个表。
(2)必须为被参照表定义主键。
(3)必须在被参照表的表名后面指定列名(或列名的组合)。这个列(或列组合)必须是这个表的主键或替代键。
(4)尽管主键是不能够包含空值的,但允许在外键中出现一个空值。这意味着,只要外键的每个非空值出现在指定的主键中,这个外键的内容就是正确的。
(5)外键中的列的数目必须和被参照表的主键中的列的数目相同。
(6)外键中的列的数据类型必须和被参照表的主键中的列的数据类型对应相等。
与外键相关的被参照表和参照表可以是同一个表。这样的表称为自参照表(self-referencing table),这种结构称为自参照完整性(self-referential integrity)。例如,可以创建这样的XS1表:
CREATE TABLE XS1
(
学号 varchar(6) NOT NULL,
姓名 varchar(8) NOT NULL,
出生日期 datetime NULL,
PRIMARY KEY (学号),
FOREIGN KEY (学号)
REFERENCES XS1 (学号)
);
创建带有参照动作ASCADE的XS1表。
CREATE TABLE XS1
(
学号 varchar(6) NOT NULL,
姓名 varchar(8) NOT NULL,
出生日期 datetime NULL,
PRIMARY KEY (学号),
FOREIGN KEY (学号)
REFERENCES XS (学号)
ON UPDATE CASCADE
);
说明:这个参照动作的作用是在主表更新时,子表产生连锁更新动作,有些人称它为“级联”操作。就是说,如果XS表中有一个学号为“081101”的值修改为“091101”,则XS1表中的学号列上为“081101”的值也相应地改为“091101”。
同样地,如果例中的参照动作为ON DELETE SET NULL,则表示如果删除了XS表中的学号为“081101”的一行,则同时将XS1表中所有学号为“081101”的列值改为NULL。
主键、替代键、外键都是常见的完整性约束的例子。但是,每个数据库都还有一些专用的完整性约束。例如,KC表中星期数要在1~7之间,XS表中出生日期必须大于1986年1月1日。这样的规则可以使用CHECK完整性约束来指定。
CHECK完整性约束在创建表的时候定义。可以定义为列完整性约束,也可以定义为表完整性约束。
语法格式为:
CHECK(expr)
说明:expr是一个表达式,指定需要检查的条件,在更新表数据的时候,MySQL会检查更新后的数据行是否满足CHECK的条件。
创建表student,只考虑学号和性别两列,性别只能包含男或女。
CREATE TABLE student
(
学号 char(6) NOT NULL,
性别 char(1) NOT NULL
CHECK(性别 IN ('男', '女'))
);
这里CHECK完整性约束指定了性别允许哪个值,由于CHECK包含在列自身的定义中,所以CHECK完整性约束被定义为列完整性约束。
创建表student1,只考虑学号和出生日期两列,出生日期必须大于1980年1月1日。
CREATE TABLE student1
(
学号 char(6) NOT NULL,
出生日期 date NOT NULL
CHECK(出生日期>'1980-01-01')
);
前面的CHECK完整性约束中使用的表达式都很简单,MySQL还允许使用更为复杂的表达式。例如,可以在条件中加入子查询。
创建表student2,只考虑学号和性别两列,并且确认性别列中的所有值来源于student表的性别列中。
CREATE TABLE student2
(
学号 char(6) NOT NULL,
性别 char(1) NOT NULL
CHECK( 性别 IN
(SELECT 性别 FROM student)
)
);
如果指定的完整性约束中,要相互比较一个表的两个或多个列,那么该列完整性约束必须定义表完整性约束。
创建表student3,有学号、最好成绩和平均成绩3列,要求最好成绩必须大于平均成绩。
CREATE TABLE student3
(
学号 char(6) NOT NULL,
最好成绩 INT(1) NOT NULL,
平均成绩 INT(1) NOT NULL,
CHECK(最好成绩>平均成绩)
);
也可以同时定义多个CHECK完整性约束,中间用逗号隔开。
然而,不幸的是,在目前的MySQL版本中,CHECK完整性约束还没有被强化,上面例子中定义的CHECK约束会被MySQL分析,但会被忽略,也就是说,这里的CHECK约束暂时只是一个注释,不会起任何作用。相信在未来的版本中它能得到扩展。
如果一条INSERT、UPDATE或DELETE语句违反了完整性约束,则MySQL返回一条出错消息并且拒绝更新,一个更新可能会导致多个完整性约束的违反。在这种情况下,应用程序获取几条出错消息。为了确切地表示是违反了哪一个完整性约束,可以为每个完整性约束分配一个名字,随后,出错消息包含这个名字,从而使得消息对于应用程序更有意义。
CONSTRAINT关键字用来指定完整性约束的名字。语法格式为:
CONSTRAINT [symbol]
symbol为指定的名字,这个名字在完整性约束的前面被定义,在数据库里这个名字必须是唯一的。如果它没有被给出,则MySQL自动创建这个名字。只能给表完整性约束指定名字,而无法给列完整性约束指定名字。
创建与例5.8中相同的XS1表,并为主键命名。
CREATE TABLE XS1
(
学号 varchar(6) NULL,
姓名 varchar(8) NOT NULL,
出生日期 datetime NULL
CONSTRAINT PRIMARY_KEY_XS1
PRIMARY KEY(姓名)
);
说明:本例中给主键姓名分配了名字PRIMARY_KEY_XS1。
在定义完整性约束的时候应当尽可能地分配名字,以便在删除完整性约束的时候,可以更容易地引用它们。这意味着,表完整性约束比列完整性约束更受欢迎,因为不可能为后者分配一个名字。
如果使用一条DROP TABLE语句删除一个表,所有的完整性约束都自动被删除了。被参照表的所有外键也都被删除了,使用ALTER TABLE语句,完整性可以独立地被删除,而不用去删除表本身。删除的语法和5.1.3节删除索引的语法一样。
删除表XS的主键。
ALTER TABLE XS DROP PRIMARY KEY;