5 . 4 . 6 强制数据完整性
如前所述,本书假定您至少对数据库理论有所熟悉,所以这里不会详细说明约束如何
维护数据完整性。本节所涉及的是如何创建这些约束,以及其他用来保持数据完整性和一
致性的数据库对象。
1 .主键约束
一个表有且只能有一个主键约束。它用来唯一标识表中每一行。主键约束可定义在单
个列或列的组合(如果唯一标识每行需要多列)之上。了解SQL Server如何强制主键定义中 指定的键值的唯一性十分重要。SQL Server使用的方法是在参与键的列上创建唯一的索引。 在不排序数据的情况下来强制唯一性会非常低效。SQL Server在这方面的问题是,如 果聚集索引不存在,它会默认一个唯一的聚集索引。决定什么列参与主键和决定什么列定
义表数据的物理结构是完全不同的。不应假定一个主键同时也是表的聚集键。记住,表的
所有非聚集索引将包含聚集索引键作为指向数据行的指针。如果主键很长,使用聚集索引
支持主键对于非聚集索引的存储和检索是非常不利的。
可以在“表设计器”窗口中选择列,然后单击“表设计器”工具栏上的“设置主键”
按钮来创建主键,或者使用Transact-SQL在 CREATE TABLE或 ALTER TABLE命令中进
行创建。下面的例子演示了如何在创建中和创建后在表上设置主键。
在使用CREATE TABLE语句时,可以将约束作为列定义的一部分进行定义,或是在
所有列定义的末尾作为表定义的一部分进行定义。第一个例子显示了使用CREATE TABLE
命令将主键约束作为列定义的一部分创建:
USE AdventureWorks2008 GO
CREATE TABLE dbo.CreditCards( CreditCardID int IDENTITY(1,1) NOT NULL CONSTRAINT PK一CreditCardID PRIMARY KEY NONCLUSTERED (CreditCardID)r CardType nvarchar(50) NOT NULL, CardNumber nvarchar(25) NOT NULL, ExpMonth tinyint NOT NULL, ExpYear smallint NOT NULL, ModifiedDate datetime NOT NULL)
下一个例子也是使用CREATE TABLE命令创建主键约束,但是是在所有列定义的末
尾将主键约束作为表定义的一部分创建的。
CREATE TABLE dbo.CreditCards(
CreditCardID in t IDENTITY(1,1) NOT NULL,
CardType nvarchar (50) NOT NULL, CardNumber nvarchar(25) NOT NULL, ExpMonth tinyint NOT NULL, ExpYear smallint NOT NULL, ModifiedDate datetime NOT NULL, CONSTRAINT PK_CreditCardID PRIMARY KEY NONCLUSTERED (CreditCardID))
在两个例子中,CONSTRAINT关键字和约束名称是可选的。如果省略名称,SQL Server 会指派一个系统生成的名称。建议为所有约束都提供名称,因为它将显示在SQL Server
Management Studio中,以及SQL Server生成的错误消息中,更友好的名称会很有用。选
择的名称在包含该表的架构中必须是唯一的。
最后一个例子展示了如何使用ALTER TABLE命令,将主键约束添加到现有表中:
ALTER TABLE dbo.CreditCards ADD CONSTRAINT PK_CreditCardID PRIMARY KEY NONCLUSTERED (CreditCardID)
另外要记住,如果省略关键字NONCLUSTERED, SQL Server会创建一个聚集索引以
强制实现这个键(如果未定义的话)。确保这是想要的结果,因为这个错误很常见。
2 .唯一约束
虽然一个表只能有一个主键约束,但它可以有很多唯一约束。例如,一家快递公司可
能需要一个如下所示的表来记录司机的相关信息:
CREATE TABLE dbo.Driver( DriverID int IDENTITY(1,1) NOT NULL
CONSTRAINT PK一Driverld PRIMARY KEY CLUSTERED'
LastName varchar(75) NOT NULL, FirstName varchar(75) NOT NULL, Middlelnitial varchar (3) NULL, SocSecNum char(9) NOT NULL, LicenseNum varchar(25) NOT NULL)
在这个例子中,除了主键之外,雇主可能还想要确保社会安全号码和司机的驾驶执照
号码都是唯一的。为什么我们不使用社会安全号码或驾驶执照号码作为主键?其实有很多
原因可以说明这两列并不适合用作主键。
对于社会安全号码来说,安全性是个大问题。因为大多数主键被用作外键,所以社会
安全号码就会重复出现在几个地方。鉴于私人信息的敏感性,管理员绝不希望出现这种情
况。另一个原因既适用于社会安全号码,也适用于司机的驾驶执照号码。因为这两种号码
并不是数字,而是字符串,所以它们不是用来强制引用完整性的最佳值,因为联接条件将
会变得很大,而不是一个更有效率的整型值。
要考虑的另一件事是值究竟是否是唯一的。社会安全号码可被重用,这会 带 来 一 些 问
题。通常这只会在您计划将数据保存很长一段时间时发生,但确实会发生这种情况。
要创建唯一约束,有两个选择:在表上创建唯一索引或唯一约束。唯一索引和唯一约
束的行为相似,而 SQL Server将创建一个唯一索引来强制唯一约束。这 和 “先有蛋还是先 有鸡?”的问题类似。不过也有不同,虽然区别很小。如果创建一个唯一约束,那么要删
除唯一索引,只能通过删除该约束完成。此时不能使用DROP INDEX命令。
要以图形化方式创建唯一索引或约束,可以右击表名并 选 择 “设计”命令来打开表以
供修改。
打 开 “表设计器”工具栏,单 击 “管理索引和键”按钮(如图5-25所示)。
在 “索引/键 ”对话框上(如图5-26所示),单 击 “添加”按钮,然后指定新索引或键的
属性。注意,对 于 “类型”属性,可以选择“索引”或 “唯 •键 ”。如 果 “是唯一的”属性
被设置为“是”,那么无论是索引还是唯一键都将具有同样的效果。
要 强 制 LicenseNum列的唯一性,可以使用下面命令中的任意一条,因为得到的结
果是一样的。
ALTER TABLE dbo.Driver ADD CONSTRAINT UX_LicenseNum UNIQUE NONCLUSTERED(LicenseNum)
CREATE UNIQUE NONCLUSTERED INDEX UX_LicenseNum ON dbo.Driver(LicenseNum)
3 . 外键约束
外键约束用来保证表之间的引用完整性。要在表上创建一个外键约束,定义在外键中
的列必须映射到主键表中的列,这些列被指定为主键或者有唯一约束(唯一约束和唯一索
引都可以)。
下面的例子基于之前创建的dbo.Driver表,以及可用下面的脚木创建的dbo.DriverRecord表。
CREATE TABLE dbo.DriverRecord( RecordID int IDENTITY (lz 1) NOT NULL PRIMARY KEY NONCLUSTERED, DriverlD int, InfractionlD int NOT NULL, RecordDate datetime NOT NULL)
要使用图形化工具创建外键,可 在 “对象资源管理器”中展开DriverRecord表。右击
键 ”节点,然 后 单 击 “新建外键”命令。“外键关系”对话框(如图5-27所示)将会显示。
树状视图的“键”文件夹下看到新创建的外键。如果没有看到,可右击DriverRecord表并 选择“刷新”命令
外键约束选项
外键约束有一些高级选项,它们可以在创建中和创建后改变外键约束的行为,下面将
介绍这些髙级选项。可以在“外键关系”对话框中的“常规”和 “表设计器”部分设置这
些选项,或者通过Transact-SQL进行设置。在介绍这些选项时,还将给出创建外键和设置 选项所需代码的例子。
提示:
下面的例子使用了同样的约束名称。要按顺序执行示例,必须在重建约束之前删除现有
的约束。可以使用SQL Server Management Studio的 “对象资源管理器”或者执行ALTER TABLE dbo.DriverRecord DROP CONSTRAINT FK_DriverRecord_Driver 脚本删除约束。
WITH CHECK
这是添加外键约束时的默认设置。该设置指定了应对外键表中任何现有数据进行验
证,使其符合该约束:
ALTER TABLE dbo.DriverRecord WITH CHECK ADD CONSTRAINT FK_DriverRecord_Driver FOREIGN KEY (DriverlD) REFERENCES dbo.Driver (DriverlD)
WITH NOCHECK
此设置指定了不对现有数据进行验证来使其符合新的约束。如果知道所有现有数据都
符合约束,那么可以使用这个选项使创建过程更有效率。但是要记住,在创建过程中任何
不符合约束的记录将被忽略。然而,在后面对不符合约束的行进行更新时,将会强制该约
束,从而导致错误。
ALTER TABLE dbo.DriverRecord WITH NOCHECK ADD CONSTRAINT FK_DriverRecord_Driver FOREIGN KEY (DriverlD) REFERENCES dbo.Driver (DriverlD)
级联约束
外键默认阻止更新或删除父值(主键值或唯一值)。然而有些时候这种行为并不合适。
SQL Server提供了指定在父记录被删除或更新的情况下对子记录采取何种行动的选项。
ON DELETE NO ACTION和 ON UPDATE NO ACTION是外键的默认设置。这些设置指
定,任何试图删除或更新由其他表中现有行的外键所引用的键值的行为都会失败。
除了 默认的 NO ACTION 设 置 ,还有 CASCADE、SET NULL 和 SET DEFAULT 选项,
它们允许删除或更新键值,以按定义的方式级联至定义为拥有外键关系的表。
ON DELETE CASCADE
这一选项指定了,如果父行被删除,任何子记录也都会被删除。如果子记录下面也有
子记录,将强制执行那些表上的外键选项,进行级联操作或者失败。
ALTER TABLE dbo.DriverRecord WITH NOCHECK ADD CONSTRAINT FK_DriverRecord_Driver FOREIGN KEY (DriverlD) REFERENCES dbo.Driver (DriverlD) ON DELETE CASCADE
ON UPDATE CASCADE
如果父键被更新,该更新会级联至任意引用了父键的子记录。
ALTER TABLE dbo.DriverRecord WITH NOCHECK ADD CONSTRAINT FK_DriverRecord_Driver FOREIGN KEY (DriverlD) REFERENCES dbo.Driver (DriverlD) ON UPDATE CASCADE
ON DELETE SET NULL
使用该设置,如果父行被删除,则任意子记录的外键都会被设置为NULL。外键列必
须允许空值以使这个选项起作用。
ALTER TABLE dbo.DriverRecord WITH NOCHECK ADD CONSTRAINT FK_DriverRecord_Driver FOREIGN KEY (DriverlD) REFERENCES dbo.Driver (DriverlD) ON DELETE SET NULL
ON UPDATE SET NULL
如果相应的父键被更新,则任意子记录的外键都会被设为NULL。外键列必须允许空
值以使这个选项起作用。
ALTER TABLE dbo.DriverRecord WITH NOCHECK ADD CONSTRAINT FK_DriverRecord_Driver FOREIGN KEY (DriverlD) REFERENCES dbo.Driver (DriverlD) ON UPDATE SET NULL
ON DELETE SET DEFAULT
在删除父记录时,相应的子键值会被设为定义在该列上的任意DEFAULT约束指定的
值 。如 果 没 有 DEFAULT约束存在,那么只要外键列允许空值,该 值 将 被 设 为 NULL。
DEFAULT约束指定的值在父表中必须有一个相应的行。
ALTER TABLE dbo.DriverRecord WITH NOCHECK
ADD CONSTRAINT FK_DriverRecorci一Driver FOREIGN KEY (DriverlD) REFERENCES dbo.Driver (DriverlD)
ON DELETE SET DEFAULT
ON UPDATE SET DEFAULT
当一个父键值被更新时,任何相应的子记录也将更新为由定义在外键列上的
DEFAULT约束指定的值。和前面的选项一样,父表中必须存在默认值。如果没有定义
DEFAULT,而外键列允许空值,子值将被设为NULL。
ALTER TABLE dbo.DriverRecord WITH NOCHECK ADD CONSTRAINT FK_DriverRecord_Driver FOREIGN KEY (DriverlD) REFERENCES dbo.Driver (DriverlD) ON UPDATE SET DEFAULT
可以组合和混合各种级联设置。例如,可以将一个DELETE的级联选项设置为CASCADE,
而将一个UPDATE的级联选项设置为NO ACTION。
4. CHECK 约束
CHECK约束用来确保字段中的数据符合一个已定义的表达式。可以通过如下步骤以
图形方式在之前创建的dbo.Driver表上创建CHECK约束: (1) 在 “对象资源管理器”中展幵dbo.Driver表。 (2) 右 击 “约束”节点,然后单击“新建约束”命令。这时会打开“CHECK约束”
对话框。
(3) 在 “CHECK约束”对话框(如图5-29所示)中,在 “标识”部分将约束名称改为
CK DriverSocialSecurityNumber,并 将 “说明”改为 “ Enforce numeric values for SSN’s”。
(4 )输入下列表达式以编辑约束表达式。
(SocSecNum LIKE •[0-9] [0-9] [0-9] [0-9] [0-9] [0-9] [0-9] [0-9] [0-9]*)
该表达式确保所有添加到表中的社会安全号码会是9个连续的整数,而不带有短划线。
验证SSN(社会安全号码)要比这里使用的表达式复杂得多。例如,SSN的前3 个数字只能
在 001〜772之间,但使用简单的LIKE运算符会很难以处理。
注意,在 “外键约束”或 “CHECK约束”对话框(如图5-29所示)中没有“确定”或
“取消”按钮。如果打开此对话框,约束将自动添加,因此如果决定不添加约束,必须在
关闭对话框前删除它。下列所示为创建同样约束的T-SQL命令:
ALTER TABLE d b o .D r i v e r ADD CONSTRAINT C K _ D r i v e r S o c i a l S e c u r i t y N u m b e r CHECK (S o c S e c N u m L IK E ‘ [ 0 - 9 ] [ 0 - 9 ] [ 0 - 9 ] [ 0 - 9 ] [ 0 - 9 ] [ 0 - 9 ] [ 0 - 9 ] [ 0 - 9 ] [ 0 - 9 ] * )
GO
5 .默认约束
在插入时如果没有指定任何值,默认约束将指定一个值插入到表中。可以在创建或修
改表时对表应用默认约束。要使用图形化工具创建默汄约束,首先选择要对其应用默认约
束的列,然后在 “表设计器”的 “列属性”窗口中指定一个默认值或绑定,如图5-30所示。
绑定是指向数据库默认值或规则的链接,本章后面将会对此进行讨论。
在这个例子中,把字符串’000000000’指定为SocSecNum列的默认值。 完成同样的任务所需的T-SQL命令如下所示:
ALTER TABLE d b o .D r i v e r ADD CONSTRAINT D F _ D riv er_ _ S o cS ecN u m DEFAULT ' 0 0 0 0 0 0 0 0 0 * FOR S ocS ecN um
GO