PARTI-Oracle关系数据结构-数据完整性
5. 数据完整性
这一章节解释了完整性约束如何实施与数据库相关的业务规则,并防止无效信息录入表格。
本章包含以下小节:
■ 数据完整性简介
■ 完整性约束的类型
■ 完整性约束的状态
5.1. 数据完整性简介
业务规则指定了必须始终为真或始终为假的条件和关系。例如,每个公司都会定义关于薪资、员工编号、库存跟踪等方面的政策。数据维护数据完整性非常重要,这是指遵守由数据库管理员或应用程序开发者确定的这些规则。
5.1.1. 保证数据完整性的技术
在设计数据库应用程序时,开发人员有多种选项来保证存储在数据库中的数据的完整性。这些选项包括:
■ 使用触发存储数据库过程来实施业务规则,如第8-16页的“触发器概述”中所述
■ 使用存储过程完全控制对数据的访问,如第8-1页的“服务器端编程简介”中所述
■ 在数据库应用程序的代码中实施业务规则
■ 使用Oracle数据库完整性约束,这些是在列或对象级别定义的规则,用于限制数据库中的值
本章解释了完整性约束的基本概念。
5.1.2. 完整性约束的优势
完整性约束是使用SQL创建和删除的模式对象。为了强制执行数据完整性,除非不可能,否则请使用完整性约束。完整性约束相对于强制执行数据完整性的其他替代方案的优势包括:
■ 声明式简便性
因为您使用SQL语句定义完整性约束,所以在定义或修改表时不需要额外的编程。SQL语句易于编写,并且可以消除编程错误。
■ 集中化规则
完整性约束是为表定义的,并存储在数据字典中(参见第6-1页的“数据字典概述”)。因此,所有应用程序输入的数据都必须遵守相同的完整性约束。如果表级别的规则发生变化,则应用程序无需更改。此外,应用程序可以使用数据字典中的元数据,即使在数据库检查SQL语句之前,也能立即通知用户违规情况。
■ 加载数据时的灵活性
您可以临时禁用完整性约束,以避免在加载大量数据时产生性能开销。当数据加载完成后,您可以重新启用完整性约束。
5.2. 完整性约束的类型
Oracle数据库允许您在表和列级别应用约束。作为列或属性定义的一部分指定的约束称为内联指定。作为表定义的一部分指定的约束称为外联指定。
在定义几种类型的完整性约束时使用了“键”这个术语。键是包含在某些类型的完整性约束定义中的列或一组列。键描述了关系数据库中表和列之间的关系。键中的个别值称为键值。
表5-1描述了约束的类型。每种都可以内联或外联指定,除了非空(NOT NULL)约束,它必须是内联的。
5.2.1. 非空完整性约束
非空(NOT NULL)约束要求表中的某一列不能包含空值。空值表示没有值。默认情况下,表中的所有列都允许为空。非空约束适用于那些必须有值的列。例如,hr.employees表要求last_name列必须有值。尝试插入没有姓氏的员工行将产生错误:
SQL> INSERT INTO hr.employees (employee_id, last_name) values (999, 'Smith'); . . .
ERROR at line 1: ORA-01400: cannot insert NULL into ("HR"."EMPLOYEES"."LAST_NAME")
您只能在表中没有包含任何行或者您指定了一个默认值的情况下添加具有非空(NOT NULL)约束的列。
5.2.2. 唯一性约束
唯一键约束要求列或一组列中的每个值都是唯一的。在具有唯一键约束的列(唯一键)或一组列(复合唯一键)中,表的任何行都不可以有重复值。
注意:术语“键”仅指在完整性约束中定义的列。由于数据库通过隐式创建或重用键列上的索引来强制执行唯一约束,因此“唯一键”这个术语有时被错误地用作唯一键约束或唯一索引的同义词。
唯一键约束适用于不允许重复值的任何列。唯一约束与主键约束不同,主键约束的目的是唯一标识表的每一行,通常包含的值除了唯一之外没有其他意义。唯一键的例子包括:
■ 客户电话号码,其中主键是客户号码
■ 部门名称,其中主键是部门号码
如第2-8页示例2-1所示,hr.employees表的email列上存在一个唯一键约束。相关语句部分如下:
CREATE TABLE employees
( ... ,
email VARCHAR2(25) CONSTRAINT emp_email_nn NOT NULL
...
, CONSTRAINT emp_email_uk
UNIQUE (email) ... );
emp_email_uk约束确保没有两个员工有相同的电子邮件地址,如示例5-1所示。
Example 5–1 Unique Constraint
SQL> SELECT employee_id, last_name, email FROM employees WHERE email = 'PFAY';
EMPLOYEE_ID LAST_NAME EMAIL
----------- ------------------------- ------------------------
202 Fay PFAY
SQL> INSERT INTO employees (employee_id, last_name, email, hire_date, job_id) 1 VALUES (999,'Fay','PFAY',SYSDATE,'ST_CLERK');
. . .
ERROR at line 1: ORA-00001: unique constraint (HR.EMP_EMAIL_UK) violated
除非同时定义了非空(NOT NULL)约束,否则空值总是满足唯一键约束。因此,具有唯一键约束和非空约束的列是典型的。这种组合强制用户输入唯一键的值,并消除了新行数据与现有行数据冲突的可能性。
注意:由于在多个列上的唯一键约束的搜索机制,您不能在部分空的复合唯一键约束的非空列中拥有相同的值。
5.2.3. 主键约束
在主键约束中,受约束的一个或多个列中的值唯一地标识了行。每个表可以有一个主键,实际上它命名了行并确保不存在重复的行。主键可以是自然键或代理键。自然键是由表中现有属性构成的有意义的标识符。例如,自然键可以是查找表中的邮政编码。相比之下,代理键是系统生成的递增标识符,确保在表内的唯一性。通常,代理键是由序列生成的。Oracle数据库实现的主键约束保证了以下陈述是真实的:
■ 没有两行在指定的列或一组列中有重复的值。
■ 主键列不允许有空值。
典型的需要主键的情况是员工的数字标识符。每个员工必须有一个唯一的ID。一个员工必须由员工表中的一行且仅一行来描述。
示例5-1表明,一个现有的员工有员工ID 202,其中员工ID是主键。以下示例显示了尝试添加一个具有相同员工ID的员工和一个没有ID的员工的尝试:
SQL> INSERT INTO employees (employee_id, last_name, email, hire_date, job_id)
1 VALUES (202,'Chan','ICHAN',SYSDATE,'ST_CLERK');
. . .
ERROR at line 1: ORA-00001: unique constraint (HR.EMP_EMP_ID_PK) violated
SQL> INSERT INTO employees (last_name) VALUES ('Chan'); . . .
ERROR at line 1: ORA-01400: cannot insert NULL into ("HR"."EMPLOYEES"."EMPLOYEE_ID")
数据库通过索引强制执行主键约束。通常,为列创建的主键约束隐式地创建了一个唯一索引和一个非空(NOT NULL)约束。注意以下规则的例外情况:
■ 在某些情况下,例如当您创建一个可延迟的主键约束时,生成的索引不是唯一的。
注意:您可以使用CREATE UNIQUE INDEX语句显式创建唯一索引。
■ 如果在创建主键约束时存在可用的索引,则约束会重用此索引,而不会隐式创建一个新的索引。
默认情况下,隐式创建的索引的名称是主键约束的名称。您也可以为索引指定一个用户定义的名称。您可以通过在用于创建约束的CREATE TABLE或ALTER TABLE语句中包含ENABLE子句来为索引指定存储选项。
5.2.4. 外键约束
每当两个表包含一个或多个共同的列时,Oracle数据库可以通过外键约束(也称为参照完整性约束)强制执行这两个表之间的关系。该约束要求对于定义了约束的列中的每个值,在另一个指定的表和列中的值必须匹配。参照完整性规则的一个例子是,一个员工只能为一个已存在的部门工作。
表5-2列出了与参照完整性约束相关的术语。
图5-1显示了employees.department_id列上的外键。它保证这一列中的每个值必须与departments.department_id列中的值相匹配。因此,employees.department_id列中不可能出现错误的部门编号。
5.2.4.1. 自参照完整性约束
图5-2展示了一个自参照完整性约束。在这种情况下,外键引用了同一表中的父键。在图5-2中,参照完整性约束确保employees.manager_id列中的每个值都对应于employees.employee_id列中的现有值。例如,员工102的经理必须存在于employees表中。这个约束消除了manager_id列中出现错误员工编号的可能性。
5.2.4.2. 空值与外键
关系模型允许外键的值与引用的主键或唯一键值相匹配,或者为空。例如,用户可以在不指定部门ID的情况下向hr.employees表中插入一行。如果复合外键的任何列是空的,那么键的非空部分不必与父键的任何相应部分相匹配。
5.2.4.3. 父键修改与外键
外键与父键之间的关系对父键的删除有影响。例如,如果用户尝试删除这个部门的记录,那么这个部门的员工记录会发生什么情况?
当父键被修改时,参照完整性约束可以指定对子表中依赖行执行以下操作:
■ 删除或更新时不采取行动
在正常情况下,如果结果会违反参照完整性,用户不能修改引用的键值。例如,如果employees.department_id是指向departments的外键,并且如果员工属于特定部门,那么尝试删除这个部门的行将违反约束。
■ 级联删除
当包含引用键值的行被删除时,删除会级联(DELETE CASCADE),导致所有子表中具有依赖外键值的行也被删除。例如,删除departments表中的一行会导致该部门所有员工的行被删除。
■ 设置为空的删除
当包含引用键值的行被删除时,设置为空的删除(DELETE SET NULL)会将所有子表中具有依赖外键值的行中的这些值设置为空。例如,删除一个部门行将把该部门员工的department_id列的值设置为空。
表5-3概述了在父表的键值和子表的外键值上不同的参照动作所允许的DML(数据操作语言)语句。
注意:Oracle数据库的外键完整性约束不支持的其他参照动作可以通过使用数据库触发器来强制执行。参见第8-16页的“触发器概述”。
5.2.4.4. 索引与外键
作为一般规则,Oracle建议对堆组织表中的外键进行索引。非分区表的例外情况是,当匹配的唯一键或主键从不更新或删除时创建索引。
注意:对于非堆数据结构(如索引组织表和表簇)还适用其他考虑因素。
在子表中对外键进行索引可以提供以下好处:
■ 避免对子表进行全表锁定。相反,数据库会在索引上获取行锁。
■ 消除了对子表进行全表扫描的需要。例如,假设用户从departments表中删除了部门10的记录。如果employees.department_id没有被索引,那么数据库必须扫描employees表以确定是否存在属于部门10的员工。
5.2.5. 检查约束
列或一组列上的检查约束要求指定的条件对每一行都必须为真或未知。如果DML操作导致约束条件评估为假,则SQL语句将回滚。检查约束的主要好处是能够强制执行非常具体的完整性规则。例如,您可以使用检查约束在hr.employees表中强制执行以下规则:
■ 薪资列不能有大于10000的值。
■ 佣金列的值不能大于薪资。
以下示例创建了员工薪资的最大值约束,并展示了当尝试插入包含超过最大薪资的行时会发生什么:
SQL> ALTER TABLE employees ADD CONSTRAINT max_emp_sal CHECK (salary < 10001);
SQL> INSERT INTO employees (employee_id, last_name, email, hire_date, job_id, salary)
VALUES (999, 'Green', 'BGREEN', SYSDATE, 'ST_CLERK', 20000);
...
ERROR at line 1: ORA-02290: check constraint (HR.MAX_EMP_SAL) violated
单个列可以有多个检查约束,这些约束在其定义中引用该列。例如,薪资列可以有一个约束防止值超过10000,并且有一个单独的约束防止值小于500。如果一个列存在多个检查约束,则必须设计它们,以确保它们的目标不会冲突。不能假设条件的评估顺序。数据库不会验证检查条件是否互斥。
5.3. 完整性约束状态
作为约束定义的一部分,您可以指定Oracle数据库应如何以及何时强制执行该约束,从而确定约束的状态。
5.3.1. 对已修改和现有数据的检查
数据库允许您指定约束是适用于现有数据还是未来数据。如果启用了约束,则数据库会在输入或更新新数据时进行检查。不符合约束的数据不能进入数据库。例如,启用employees.department_id上的非空(NOT NULL)约束,确保每一行未来的记录都有部门ID。如果约束被禁用,则表可以包含违反约束的行。
您可以将约束设置为验证(VALIDATE)或不验证(NOVALIDATE)现有数据。如果指定了VALIDATE,则现有数据必须符合约束。例如,启用employees.department_id上的非空约束并将其设置为VALIDATE,会检查每一行现有记录都有部门ID。如果指定了NOVALIDATE,则现有数据不需要符合约束。
VALIDATE和NOVALIDATE的行为始终取决于约束是启用还是禁用。表5-4总结了这些关系。
5.3.2. 可延迟的约束
每个约束都处于不可延迟(默认)或可延迟状态。这种状态决定了Oracle数据库何时检查约束的有效性。下图显示了可延迟约束的选项。
5.3.2.1. 不可延迟的约束
如果约束不是可延迟的,那么Oracle数据库永远不会将约束的有效性检查推迟到事务的末尾。相反,数据库在每个语句的末尾检查约束。如果违反了约束,则语句将回滚。
例如,假设您为employees.last_name列创建了一个不可延迟的非空(NOT NULL)约束。如果用户尝试插入一个没有姓氏的行,则数据库会立即回滚该语句,因为违反了非空约束。不会插入任何行。
5.3.2.2. 可延迟的约束
可延迟约束允许事务使用SET CONSTRAINT子句将此约束的检查推迟到发出COMMIT语句时。如果您对数据库进行了可能违反约束的更改,那么此设置有效地允许您在所有更改完成之前禁用约束。您可以设置数据库检查可延迟约束时的默认行为。您可以指定以下属性之一:
■ INITIALLY IMMEDIATE
数据库在每个语句执行后立即检查约束。如果违反了约束,数据库将回滚该语句。
■ INITIALLY DEFERRED
当发出COMMIT时,数据库检查约束。如果违反了约束,数据库将回滚事务。
假设employees.last_name上的可延迟非空(NOT NULL)约束设置为INITIALLY DEFERRED。用户创建了一个包含100条INSERT语句的事务,其中一些语句的last_name值为空。当用户尝试提交时,数据库回滚了所有100条语句。然而,如果此约束设置为INITIALLY IMMEDIATE,那么数据库就不会回滚事务。
如果约束导致采取行动,那么无论约束是延迟还是立即的,数据库都会将此行动视为导致它的语句的一部分。例如,删除departments表中的一行会导致删除employees表中引用已删除部门行的所有行。在这种情况下,从employees表中的删除被视为对departments表执行的DELETE语句的一部分。
5.3.3. 约束检查的示例
一些示例可能有助于说明Oracle数据库何时执行约束检查。假设如下:
■ employees表具有第5-8页图5-2所示的结构。
■ 自参照约束使得manager_id列中的条目依赖于employee_id列的值。
- 当不存在父键值时,在外键列中插入值
考虑向employees表中插入第一行的情况。目前不存在任何行,那么如果manager_id列中的值不能引用employee_id列中的任何现有值,如何能够输入行呢?一些可能性包括:
■ 如果manager_id列没有定义非空(NOT NULL)约束,可以为第一行的manager_id列输入空值。
因为外键允许为空,所以这行被插入到表中。
■ 可以在employee_id和manager_id列中输入相同的值,指定员工是自己的经理。这种情况揭示了Oracle数据库在语句完全运行后才执行其约束检查。要允许输入具有相同父键和外键值的行,数据库必须首先运行语句(即,插入新行),然后确定表中是否有任何行的employee_id与新行的manager_id相对应。
■ 可以使用多行INSERT语句,例如带有嵌套SELECT语句的INSERT语句,插入相互引用的行。
例如,第一行可能有200作为员工ID和300作为经理ID,而第二行有300作为员工ID和200作为经理ID。约束检查推迟到语句的完整执行之后。首先插入所有行,然后检查所有行是否违反约束。
默认值在解析INSERT语句之前作为该语句的一部分包含在内。因此,默认列值受所有完整性约束检查的约束。
- 更新所有外键和父键值
考虑在不同场景下相同的自参照完整性约束。公司已经被出售。由于这次出售,所有员工编号必须更新为当前值加5000,以与新公司的员工编号协调。因为经理编号实际上是员工编号(见图5-3),经理编号也必须增加5000。
您可以执行以下SQL语句来更新值:
UPDATE employees SET employee_id = employee_id + 5000, manager_id = manager_id + 5000;
尽管定义了约束以验证每个manager_id值是否匹配employee_id值,但前面的语句是合法的,因为数据库在语句完成后才有效检查约束。图5-4显示数据库在检查约束之前执行整个SQL语句的操作。
本节中的示例说明了在INSERT和UPDATE语句期间的约束检查机制,但数据库对所有类型的DML语句都使用相同的机制。相同的机制用于所有类型的约束,不仅仅是自参照约束。
注意:对视图或同义词的操作受基础表上定义的完整性约束的约束。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南