外键

外来键又称外部键，是指在关联式数据库中，每个资料表都是由关联来连系彼此的关系，父资料表（Parent Entity）的主键（Primary Key）会放在另一个资料表，当做属性以建立彼此的关联，而这个属性就是外来键。

比如，学生跟老师之间是教学的关联，学生资料表会有个属性叫指导老师(FK)，而这个值就是对应到老师资料表的老师代号(PK)，学生的指导老师就是外来键。

外键是表中的一组特性，它引用另一个表的主键。外键链接这两个表。另一种说法：在关系数据库的上下文中，外键是一组受某种包含依赖约束的特性，特别是由一个关系R中的外键特性组成的元组必须也存在于其他一些（不一定是不同的）关系S中，而且这些属性也必须是S中的候选键。简而言之，外键是一组引用候选键的特性。例如，名为TEAM的表可能有一个属性 MEMBER_NAME，它引用PERSON表中的候选键PERSON_NAME作为外键。由于MEMBER_NAME是一个外键，任何在TEAM中作为成员名称存在的值也必须作为人名存在于PERSON表中；换句话说，一个团队的每个成员也是一个人。

## 概述

包含外键的表称为子表(child table)，包含候选键的表称为“被引用表”(referenced )或父表(parent table)。在数据库关系建模和实现中，候选键是一组零个或多个特性，其值对于关系中的每个元组（行）保证是唯一的。任何元组的候选键特性的值或值组合不能与该关系中的任何其他元组重复。

由于外键的目的是标识被引用表的某一行，所以一般要求外键等于主表某行的候选键，否则没有值（NULL）。此规则称为两个表之间的参照完整性约束。因为违反这些约束可能是许多数据库问题的根源，所以大多数数据库管理系统提供了确保每个非空外键对应于引用表的一行的机制。

例如，考虑一个包含两个表的数据库：一个包含所有客户数据的 CUSTOMER 表和一个包含所有客户订单的 ORDER 表。假设业务要求每个订单必须指向一个客户。为了在数据库中反映这一点，在 ORDER 表中添加了一个外键列（例如 CUSTOMERID），它引用了 CUSTOMER 的主键（例如 ID）。因为表的主键必须是唯一的，并且因为 CUSTOMERID 仅包含来自该主键字段的值，所以我们可以假设，当它具有值时，CUSTOMERID 将标识出下订单的特定客户。但是，如果在删除 CUSTOMER 表的行或更改 ID 列时 ORDER 表没有保持最新，则上述约束不再保证成立，并且使用这些表可能会变得更加困难。许多现实世界的数据库通过“停用”而不是物理删除主表外键，或者通过复杂的更新程序来解决这个问题，这些程序在需要更改时修改对外键的所有引用。

外键在数据库设计中起着至关重要的作用。数据库设计的一个重要部分是确保真实世界实体之间的关系通过引用反映在数据库中，使用外键从一个表引用到另一个表。数据库设计的另一个重要部分是数据库规范化，其中表被分解并且外键使得它们可以被重构。

引用表（或子表）中的多行可能引用到被引用表（或父表）中的同一行。在这种情况下，两个表之间的关系称为引用表和被引用表之间的一对多关系。

此外，实际上，子表和父表可能是同一张表，即外键指向同一张表。这种外键在 SQL:2003 中称为自引用(self-referencing)或递归外键(recursive foreign key)。在数据库管理系统中，这通常是通过将第一个和第二个引用链接到同一个表来完成的。

一个表可能有多个外键，每个外键可以有不同的父表。每个外键都由数据库系统独立执行。因此，可以使用外键建立表之间的级联关系。

外键定义为关系中的一个特性或一组特性，其值与另一个关系中的主键匹配。将此类约束添加到现有表的语法在 SQL:2003 中定义，如下所示。

```sql
ALTER TABLE 表名 ADD CONSTRAINT 外键名 FOREIGN KEY(外键字段名)
REFERENCES 外表表名(主键字段名)
[ON DELETE {RESTRICT | CASCADE | SET NULL | NO ACTION | SET DEFAULT}]
[ON UPDATE {RESTRICT | CASCADE | SET NULL | NO ACTION | SET DEFAULT}]
```

在`REFERENCES`子句中省略列列表意味着外键应引用到被引用表的主键。同样，外键可以定义为`CREATE TABLE`语句的一部分。

```sql
CREATE TABLE child_table (
  col1 INTEGER PRIMARY KEY,
  col2 CHARACTER VARYING(20),
  col3 INTEGER,
  col4 INTEGER,
  FOREIGN KEY(col3, col4) REFERENCES parent_table(col1, col2) ON DELETE CASCADE
)
```

如果外键是一个单独列，则可以使用下属语法:

```sql
CREATE TABLE child_table (
  col1 INTEGER PRIMARY KEY,
  col2 CHARACTER VARYING(20),
  col3 INTEGER,
  col4 INTEGER REFERENCES parent_table(col1) ON DELETE CASCADE
)
```

外键也可以用存储过程语句定义：

```sql
sp_foreignkey child_table, parent_table, col3, col4
```

- parent_table: 该候选键必须已经定义了
- col3 和 col4: 外键的名字。包括1列至8列。

## 引用动作

在对主表做行删除或修改时，为确保引用完整性，SQL:2003给出了5种“引用动作（referential actions）：

- CASCADE
- RESTRICT
- NO ACTION
- SET NULL
- SET DEFAULT

### CASCADE

在对主表的行删除或修改时，从表中有匹配的外键值的行也相应被删除或修改。

ON DELETE CASCADE修改了从表的行为，引入了对从表的自动删除或修改的行为。

### RESTRICT

在对主表的行删除或修改时，如果子表中有匹配的外键值的行，则主表中相应的行不能被删除或修改。

ON DELETE RESTRICT是对主表的行为约束，虽然写在了从表的定义中。

RESTRICT未被Microsoft SQL Server 2012及更早的版本支持。

### NO ACTION

NO ACTION和RESTRICT非常像。但NO ACTION在尝试修改主表后检查引用完整性。如果在这种检查之前执行了数据库触发器，可能在检查时就不违背引用完整性。

### SET NULL, SET DEFAULT

从表中的外键列被分别设为NULL或缺省值。

### Triggers

引用动作通常被实现为隐含的触发器（即通常是隐藏的、具有系统生成名称的触发器）。因此，它们受到与用户定义触发器相同的限制，并且它们相对于其他触发器的执行顺序可能需要经过考虑；在某些情况下，可能需要将引用操作替换为其等效的用户定义触发器，以确保正确的执行顺序，或解决变异表(mutating-table)的限制。

另一个重要限制出现在事务隔离时：对行的更改可能无法完全级联，这是因为该行被事务无法“看到”的数据所引用，因此无法级联。一个例子：当事务尝试重新编号客户帐户时，同时另一事务正在尝试为同一客户创建新发票；虽然 CASCADE 规则可能会修改事务可以看到的所有发票行以使其与重新编号的客户行保持一致，但它不会进入另一个事务来修改那里的数据；因为数据库不能保证两个事务提交时数据一致，所以其中一个事务将被强制回滚（通常是先到先得）。

```sql
CREATE TABLE account (acct_num INT, amount DECIMAL(10,2));

CREATE TRIGGER ins_sum BEFORE INSERT ON account
     FOR EACH ROW SET @sum = @sum + NEW.amount;
```

## 例子

一个简单的关系：

```
 Supplier ('''SupplierNumber''', Name, Address)
 Invoice ('''InvoiceNumber''', Text, ''SupplierNumber'')
```

资料定义语言语句为：

```sql
CREATE TABLE Supplier (
  SupplierNumber INTEGER NOT NULL,
  Name           VARCHAR(20) NOT NULL,
  Address        VARCHAR(50) NOT NULL,
  CONSTRAINT supplier_pk PRIMARY KEY(SupplierNumber),
  CONSTRAINT number_value CHECK(SupplierNumber > 0)
)

CREATE TABLE Invoice (
  InvoiceNumber  INTEGER NOT NULL,
  Text           VARCHAR(4096),
  SupplierNumber INTEGER NOT NULL,
  CONSTRAINT invoice_pk PRIMARY KEY(InvoiceNumber),
  CONSTRAINT inumber_value CHECK (InvoiceNumber > 0),
  CONSTRAINT supplier_fk
    FOREIGN KEY(SupplierNumber) REFERENCES Supplier(SupplierNumber)
    ON UPDATE CASCADE ON DELETE RESTRICT
)
```

## 外部链接

- 一个详细介绍外键的网站
- SQL-99 Foreign Keys
- PostgreSQL Foreign Keys
- MySQL Foreign Keys
- FirebirdSQL primary Keys
- SQLite support for Foreign Keys
- Microsoft SQL 2012 table_constraint (Transact-SQL)