20.约束
向T_Person表中录入数据,由于疏忽它录入的数据中有一条记录中的FName字段忘记了填入数据,这就造成了一个没有名字的人员的出现,即数据库中的数据遭到了污染。可以在宿主程序中通过应用逻辑来保证数据的正确性,比如在用户点击【保存】按钮的时候去校验是否录入了“人员名称”,如果没有录入则提示用户“人员名称不能为空!”。这样可以保证绝大多数情况下的数据的正确性,但是在如下几种情况下仍然无法保证数据的正确性:1.宿主程序中存在Bug,导致不正确的数据被保存到了数据表中;2.有一定技术条件的用户跳过宿主程序,直接修改数据库破坏数据的正确性。因此需要数据库系统提供指定数据表中数据约束条件的机制,这样校验在数据库系统这一最终层面来完成,保证了数据万无一失的正确性,而且在数据库系统中比在宿主程序中的校验更加高效。
数据库系统中主要提供了如下几种约束:非空约束;唯一约束; Check约束;主键约束;外键约束。
1.非空约束
在定义数据表的时候,默认情况下所有字段都是允许为空值的,如果需要禁止字段为空,那么就需要在创建表的时候显示指定。指定一个字段为空的方式就是在字段定义后增加Not Null。
CREATE TABLE T_Person ( FNumber VARCHAR(20) NOT NULL, FName VARCHAR(20) NOT NULL, FAge INT NOT NULL )
2.唯一约束
唯一约束又称为Unique约束,它用于防止一个特定的列中两个记录具有一致的值,比如在员工信息表中希望防止两个或者多个人具有相同的身份证号码。唯一约束分为单字段唯一约束与复合唯一约束两种类型。
CREATE TABLE T_Person ( FNumber varchar(20) unique, FName varchar(20), FAge int )
CREATE TABLE T_Person ( FNumber varchar(20), FDepartmentNumber varchar(20), FName varchar(20), FAge int, constraint unic_dep_num unique(FNumber,FDepartmentNumber) )
ALTER TABLE T_Person add constraint unic_1 unique(字段1,字段2...字段n)
ALTER TABLE T_Person drop constraint unic_1; ALTER TABLE T_Person drop constraint unic_2; ALTER TABLE T_Person drop constraint unic_3;
3.Check约束
Check约束会检查输入到记录中的值是否满足一个条件,如果不满足这个条件则对数据库做的修改不会成功.
CREATE TABLE T_Person ( FNumber varchar(20), FName varchar(20), FAge int check(FAge >0), FWorkYear int check(FWorkYear>0) )
这种Check约束中不能引入其他的列,下面的写法是错的。
CREATE TABLE T_Person ( FNumber varchar(20), FName varchar(20), FAge int, FWorkYear int check(FWorkYear< FAge) )
如果希望Check子句中的条件语句中使用其他列,则必须在Create Table语句的末尾使用Constraint关键字定义它。
CREATE TABLE T_Person ( FNumber varchar(20), FName varchar(20), FAge int, FWorkYear int , constraint ck_1 check(FWorkYear< FAge) )
--添加 alter table T_Person add constraint ck_2 check(FAge>14) --删除 alter table T_Person drop constraint ck_2;
4.主键约束
主键约束是外键关联的基础条件。主键约束为表之间的关联提供了链接点。 主键必须能够唯一标识一条记录,也就是主键字段中的值必须是唯一的,而且不能包含NULL值。
create table T_Person ( FNumber varchar(20) primary key, FName varchar(20), FAge int )
复合主键创建:
create table T_Person ( FNumber varchar(20), FName varchar(20), FAge int, constraint pk_1 primary key(FNumber,FName) )
尽管在创建表的时候就定义主键是一个好的习惯,但是如果表创建了时候没有定义主键,那么也可以在以后添加主键。不过通过这种方式添加主键的时候有一个附加条件,那就是组成主键的字段必须包含Not Null约束。如果在没有添加非空约束的字段上创建主键,系统将会爆出错误信息。
create table T_Person ( FNumber varchar(20) not null, FName varchar(20) not null, FAge int ) alter table T_Person add constraint pk_1 primary key(FNumber,FName)
alter table T_Person drop constraint pk_1
一般情况下,一张数据表必须要有一个主键字段,这样这个主键字段就可以唯一标识这条记录了。不过采用什么样的字段来做为主键字段还是一个必须解决的问题,目前
有两种常用的主键策略:业务主键与逻辑主键。
业务主键是指采用业务数据中的某个字段做为主键,比如在员工档案表中可以用工号来做为主键、在车辆管理系统中可以用车牌号做为主键字段。逻辑主键,也称代理主键,是指不采用任何业务数据做为主键,而是采用一个没有业务意义的不重复值做主键,比如在员工档案表中用一个自动增长的字段来做为主键,这个字段没有任何的业务意义。
使用业务主键是比较简单的,但是会存在潜在的问题,一个是业务主键并不能真正的保证唯一性,第二个是做为主键的数据一旦发生变化就会带来维护的问题。假设在社区人员信息表中使用身份证号码做为主键,由于我国身份证编号制度还存在一定缺陷,所以存在不少的身份证号码重复现象,这样一旦社区中有两个同样身份证号的人员出现,系统将会出现问题;即使能够杜绝身份证号码重复现象,也会存在升级的问题,比如今后出现身份证号码位数升级的问题,那么由于很多表都是通过身份证号码这个主键字段来关联社区人员信息表的,那么不仅要升级社区人员信息表,还要将这些关联表进行升级。因此建议尽量不要用业务字段做主键,而是使用没有业务意义主键。
使用逻辑主键可以保证主键值的唯一性,并且在业务发生变化时,适应性更强一些。不过使用代理主键也有劣势,那就是主键字段由于没有任何业务意义,所以在使用的时候比较麻烦。不过总的来与业务主键比起来,逻辑主键更有优势,因此除非有特别的里有,否则使用逻辑主键是一个好的习惯。
如果决定采用逻辑主键的话,使用什么样的主键生成策略则是必须考虑的。常用的主键生成策略有:自动增长字段和UUID。使用自动增长字段就是每次向表中插入记录的时候DBMS自动为主键设定一个自动增长的值;使用UUID 则是为主键字段设置一个UUID 类型的值,这个UUID 值采用UUID 算法来生成,这样可以保证生成的值是绝对唯一的。
使用自动增长字段的优势在于速度比较快,根据统计UUID 算法要比自动增长字段的生成速度慢约30倍;使用自动增长字段的劣势在于进行表合并的时候会存在冲突的问题,比如A表和B表的结构完全相同,而且它们都采用自动增长字段来生成主键,如果想将A表和B表合并为一张表的话那么就有可能由于A表中的记录的主键值和B表中的记录的主键值冲突而造成合并失败,而使用UUID 算法则不会有这个问题,因为UUID 算法能够保证两个UUID 值是唯一的。
5.外键约束
当一些信息在表中重复出现的时候,我们就要考虑要将它们提取到另外一张表中了,然后在源表中引用新创建的中的数据。比如很多作者都著有不止一本著作,应该把作者信息放到单独的表中,将图书信息保存到另外一张表中,然后将这两张表关联。
create table T_Author ( FId varchar(20) primary key, --作者Id FName varchar(100), --姓名 FAge int, --年龄 FEmail varchar(20)--邮箱 ); create table T_Book ( FId varchar(20) primary key, --图书编号 FName varchar(100), --图书名称 FPageCount int, --页数 FAuthorId varchar(20) --作者Id );
T_Book的FAuthorId字段来自于T_Author表的FId字段,下面插入数据。
insert into T_Author(FId,FName,FAge,FEmail) values('1','lily',20,'lily@cownew.com'); insert into T_Author(FId,FName,FAge,FEmail) values('2','kingchou',23,'kingchou@cownew.com'); insert into T_Author(FId,FName,FAge,FEmail) values('3','stef',28,'stef@cownew.com'); insert into T_Author(FId,FName,FAge,FEmail) values('4','long',26,'long@cownew.com'); insert into T_Author(FId,FName,FAge,FEmail) values('5','badboy',31,'badboy@cownew.com'); insert into T_Book(FId,FName,FPageCount,FAuthorId) values('1','About Java',300,'1'); insert into T_Book(FId,FName,FPageCount,FAuthorId) values('2','Inside Ruby',330,'2'); insert into T_Book(FId,FName,FPageCount,FAuthorId) values('3','Inside Curses',200,'5'); insert into T_Book(FId,FName,FPageCount,FAuthorId) values('4','Python In Action',450,'4'); insert into T_Book(FId,FName,FPageCount,FAuthorId) values('5','WPF Anywhere',250,'1'); insert into T_Book(FId,FName,FPageCount,FAuthorId) values('6','C# KickStart',280,'3'); insert into T_Book(FId,FName,FPageCount,FAuthorId) values('7','Compling',800,'1'); insert into T_Book(FId,FName,FPageCount,FAuthorId) values('8','Faster VB.Net',300,'5');
表结构数据:
虽然在T_Book表中有FAuthorId字段,但这个字段和T_Author表中的FId并没有内在的关联,目前仅仅是意义上有联系。FAuthorId可以不与FId想匹配,SQL提供的外键约束机制可以解决这个问题,它允许指定一个表中的一个列的值是另外一个表的外间,即一个表中的一个列是引用另外一个表中的记录。这里T_Book表中的FAuthorId引用的是T_Author表的FId。
添加外键约束:
create table T_Author ( FId varchar(20) primary key, --作者Id,主键 FName varchar(100), --姓名 FAge int, --年龄 FEmail varchar(20)--邮箱 ); create table T_Book ( FId varchar(20) primary key, --图书编号,主键 FName varchar(100), --图书名称 FPageCount int, --页数 FAuthorId varchar(20), --作者Id foreign key (FAuthorId) references T_Author(FId) --外键 );
尝试向表中插入违反外键约束的数据:
insert into T_Book(FId,FName,FPageCount,FAuthorId) values('9','About WinCE',320,'9');
因为表T_Author中没有主键值等于9的记录,所以上面的SQL语句执行后数据库系统会报出如下的错误信息:
同样,我们不能删除被T_Book表引用的T_Author表中的数据,比如我们想执行下面的SQL语句将作者long从T_AUTHOR表中删除:
delete from T_Author where FName='lily'
因为T_Authro表中的FName='lily'在T_Book表中有相关的外键引用,所以会导致删除失败:
除非将T_Book表中引入了T_Author表中字段的记录全部删除,这样在T_Author表的记录的删除不会对T_Book照成影响,这时才可以删除。下面是为先有表添加外键约束:
alter table T_Book add constraint fk_book_author foreign key (FAuthorId) references T_Author(FId)
6.自动增长字段
MSSQLServer 中设定一个字段为自动增长字段非只要在表定义中指定字段为IDENTITY即可,格式为IDENTITY(startvalue,step),其中的startvalue参数值为起始数
字,step参数值为步长,即每次自动增长时增加的值。
比如下面的SQL语句创建T_Person表,其中主键FId为自动增长字段,并且设定100为起始数字,步长为3:
CREATE TABLE T_Person ( FId INT PRIMARY KEY IDENTITY(100,3), FName VARCHAR(20), FAge INT );
执行上面的SQL语句后就创建成功了T_Person表,然后执行下面的SQL语句向T_Person表中插入一些数据:
INSERT INTO T_Person(FName,FAge) VALUES('Tom',18); INSERT INTO T_Person(FName,FAge) VALUES('Jim',81); INSERT INTO T_Person(FName,FAge) VALUES('Kerry',33);
注意这里的INSERT语句没有为FId字段设定任何值,因为DBMS会自动为FId字段设定值。