数据类型是限制可以存储在表中的数据类型的途径。但是,对于许多应用程序而言,它们提供的约束过于宽泛。例如,包含产品价格的列可能应该只接受正值。但是,没有标准的数据类型只接受正数。另一个问题是,您可能希望根据其他列或行来约束列数据。例如,在包含产品信息的表中,每个产品编号应该只有一行。
为此,SQL允许您在列和表上定义约束。约束使您可以根据需要完全控制表中的数据。如果用户尝试将违反约束的数据存储在列中,则会引发错误。即使该值来自默认值定义,也适用此规则。
检查约束是最通用的约束类型。它允许您指定特定列中的值必须满足布尔(真值)表达式。例如,要要求产品价格为正数,您可以使用
CREATE TABLE products (
product_no integer,
name text,
price numeric CHECK (price > 0)
);
如您所见,约束定义位于数据类型之后,就像默认值定义一样。默认值和约束可以按任何顺序列出。检查约束由关键字CHECK
后跟括号中的表达式组成。检查约束表达式应包含由此约束的列,否则约束将毫无意义。
您还可以为约束指定一个单独的名称。这可以阐明错误消息,并允许您在需要更改约束时引用它。语法如下:
CREATE TABLE products (
product_no integer,
name text,
price numeric CONSTRAINT positive_price CHECK (price > 0)
);
因此,要指定命名约束,请使用关键字CONSTRAINT
后跟标识符,然后是约束定义。(如果您不以这种方式指定约束名称,则系统会为您选择一个名称。)
检查约束还可以引用多个列。假设您存储常规价格和折扣价格,并且您想确保折扣价格低于常规价格
CREATE TABLE products (
product_no integer,
name text,
price numeric CHECK (price > 0),
discounted_price numeric CHECK (discounted_price > 0),
CHECK (price > discounted_price)
);
前两个约束看起来应该很熟悉。第三个使用了一种新的语法。它不附加到特定列,而是作为逗号分隔的列列表中的一个单独项目出现。列定义和这些约束定义可以按混合顺序列出。
我们说前两个约束是列约束,而第三个是表约束,因为它与任何一个列定义分开编写。列约束也可以写成表约束,反之则不一定成立,因为列约束应该只引用它所附加的列。(PostgreSQL不强制执行该规则,但如果您希望您的表定义与其他数据库系统一起使用,则应该遵循它。)以上示例也可以写成
CREATE TABLE products ( product_no integer, name text, price numeric, CHECK (price > 0), discounted_price numeric, CHECK (discounted_price > 0), CHECK (price > discounted_price) );
甚至
CREATE TABLE products ( product_no integer, name text, price numeric CHECK (price > 0), discounted_price numeric, CHECK (discounted_price > 0 AND price > discounted_price) );
这取决于个人喜好。
可以像列约束一样为表约束分配名称
CREATE TABLE products (
product_no integer,
name text,
price numeric,
CHECK (price > 0),
discounted_price numeric,
CHECK (discounted_price > 0),
CONSTRAINT valid_discount CHECK (price > discounted_price)
);
需要注意的是,如果检查表达式计算结果为真或空值,则检查约束即满足。由于大多数表达式如果任何操作数为空,则计算结果将为空值,因此它们不会阻止受约束列中的空值。要确保列不包含空值,可以使用下一节中描述的非空约束。
PostgreSQL不支持引用除正在检查的新行或更新行以外的表数据的CHECK
约束。虽然违反此规则的CHECK
约束在简单的测试中似乎可以工作,但它不能保证数据库不会进入约束条件为假的状态(由于随后涉及的其他行(s)的更改)。这会导致数据库转储和恢复失败。即使数据库状态完全与约束一致,恢复也可能失败,因为行并非按满足约束的顺序加载。如果可能,请使用UNIQUE
、EXCLUDE
或FOREIGN KEY
约束来表达跨行和跨表的限制。
如果您想要在行插入时对其他行进行一次性检查,而不是持续维护一致性保证,则可以使用自定义的触发器来实现。(这种方法避免了转储/恢复问题,因为pg_dump不会在恢复数据后才重新安装触发器,因此在转储/恢复期间不会强制执行检查。)
PostgreSQL假设CHECK
约束的条件是不可变的,也就是说,对于相同的输入行,它们将始终给出相同的结果。这个假设是证明只有在插入或更新行时才检查CHECK
约束,而不是在其他时间。(上面关于不引用其他表数据的警告实际上是此限制的一个特例。)
违反此假设的一种常见方法的示例是在CHECK
表达式中引用用户定义函数,然后更改该函数的行为。PostgreSQL不允许这样做,但如果表中存在现在违反CHECK
约束的行,它不会注意到。这会导致后续的数据库转储和恢复失败。处理此类更改的推荐方法是删除约束(使用ALTER TABLE
),调整函数定义,然后重新添加约束,从而针对所有表行重新检查约束。
非空约束只是指定列不能假定空值。语法示例
CREATE TABLE products ( product_no integer NOT NULL, name text NOT NULL, price numeric );
非空约束始终写成列约束。非空约束在功能上等效于创建检查约束CHECK (
,但在PostgreSQL中创建显式非空约束效率更高。缺点是您无法为此方式创建的非空约束指定显式名称。column_name
IS NOT NULL)
当然,一个列可以有多个约束。只需将约束一个接一个地写入即可
CREATE TABLE products ( product_no integer NOT NULL, name text NOT NULL, price numeric NOT NULL CHECK (price > 0) );
顺序无关紧要。它不一定决定检查约束的顺序。
NOT NULL
约束有一个逆:NULL
约束。这并不意味着列必须为空,这当然毫无用处。相反,这只是选择列可能为空的默认行为。NULL
约束在 SQL 标准中不存在,不应在可移植应用程序中使用。(它只是添加到PostgreSQL中以与其他一些数据库系统兼容。)但是,一些用户喜欢它,因为它使在脚本文件中切换约束变得容易。例如,您可以从
CREATE TABLE products ( product_no integer NULL, name text NULL, price numeric NULL );
然后在需要的地方插入NOT
关键字。
在大多数数据库设计中,大多数列都应标记为非空。
唯一约束确保列或一组列中包含的数据在表中的所有行中都是唯一的。语法如下:
CREATE TABLE products (
product_no integer UNIQUE,
name text,
price numeric
);
当写成列约束时,以及
CREATE TABLE products (
product_no integer,
name text,
price numeric,
UNIQUE (product_no)
);
当写成表约束时。
要为一组列定义唯一约束,请将其写成表约束,并用逗号分隔列名
CREATE TABLE example (
a integer,
b integer,
c integer,
UNIQUE (a, c)
);
这指定指示的列中值的组合在整个表中是唯一的,尽管任何一个列不必(并且通常不)唯一。
您可以为唯一约束分配自己的名称,方法与往常一样
CREATE TABLE products (
product_no integer CONSTRAINT must_be_different UNIQUE,
name text,
price numeric
);
添加唯一约束将自动在列或约束中列出的列组上创建唯一的 B 树索引。仅涵盖某些行的唯一性限制不能写成唯一约束,但可以通过创建唯一的部分索引来强制执行此类限制。
通常,如果表中有多行,其中约束中包含的所有列的值都相等,则会违反唯一约束。默认情况下,在此比较中,两个空值不被视为相等。这意味着即使存在唯一约束,也可以存储在受约束列中至少一个包含空值的重复行。可以通过添加子句NULLS NOT DISTINCT
来更改此行为,例如
CREATE TABLE products (
product_no integer UNIQUE NULLS NOT DISTINCT,
name text,
price numeric
);
或
CREATE TABLE products (
product_no integer,
name text,
price numeric,
UNIQUE NULLS NOT DISTINCT (product_no)
);
可以使用NULLS DISTINCT
显式指定默认行为。根据 SQL 标准,唯一约束中的默认空值处理是实现定义的,其他实现具有不同的行为。因此,在开发旨在可移植的应用程序时要小心。
主键约束指示某一列或一组列可以作为表中行的唯一标识符。这要求这些值既唯一又非空。因此,以下两个表定义接受相同的数据
CREATE TABLE products ( product_no integer UNIQUE NOT NULL, name text, price numeric );
CREATE TABLE products (
product_no integer PRIMARY KEY,
name text,
price numeric
);
主键可以跨越多列;语法类似于唯一约束
CREATE TABLE example (
a integer,
b integer,
c integer,
PRIMARY KEY (a, c)
);
添加主键会自动在主键中列出的列或列组上创建一个唯一的 B 树索引,并将强制标记列为NOT NULL
。
一个表最多只能有一个主键。(可以有任意数量的唯一且非空约束,它们的功能几乎相同,但只有一个可以被识别为主键。)关系数据库理论规定每个表都必须有一个主键。此规则未由PostgreSQL强制执行,但通常最好遵循它。
主键对于文档目的和客户端应用程序都很有用。例如,允许修改行值的 GUI 应用程序可能需要知道表的首要键才能唯一地识别行。如果已声明主键,数据库系统还有多种使用主键的方式;例如,主键定义了引用其表的外部键的默认目标列。
外键约束指定列(或一组列)中的值必须与另一个表中某一行的值匹配。我们说这维护了两个相关表之间的参照完整性。
假设您有我们已经使用过多次的产品表
CREATE TABLE products ( product_no integer PRIMARY KEY, name text, price numeric );
我们还假设您有一个表存储这些产品的订单。我们希望确保订单表仅包含实际存在的产品的订单。因此,我们在订单表中定义了一个引用产品表的外部键约束
CREATE TABLE orders (
order_id integer PRIMARY KEY,
product_no integer REFERENCES products (product_no),
quantity integer
);
现在无法创建具有非 NULL product_no
条目的订单,这些条目未出现在产品表中。
我们说在这种情况下,订单表是引用表,产品表是被引用表。类似地,存在引用列和被引用列。
您还可以将上述命令缩短为
CREATE TABLE orders (
order_id integer PRIMARY KEY,
product_no integer REFERENCES products,
quantity integer
);
因为在没有列列表的情况下,被引用表的首要键用作被引用列。
您可以按照通常的方式为外键约束分配自己的名称。
外键还可以约束和引用一组列。像往常一样,它需要以表约束的形式编写。这是一个人为的语法示例
CREATE TABLE t1 (
a integer PRIMARY KEY,
b integer,
c integer,
FOREIGN KEY (b, c) REFERENCES other_table (c1, c2)
);
当然,受约束列的数量和类型需要与被引用列的数量和类型匹配。
有时,对于外键约束的“另一个表”来说,它是同一个表很有用;这称为自引用外键。例如,如果您希望表的行表示树结构的节点,您可以编写
CREATE TABLE tree ( node_id integer PRIMARY KEY, parent_id integer REFERENCES tree, name text, ... );
顶级节点将具有 NULL parent_id
,而非 NULL parent_id
条目将被约束为引用表的有效行。
一个表可以有多个外键约束。这用于实现表之间的多对多关系。假设您有关于产品和订单的表,但现在您想允许一个订单包含可能多个产品(上述结构不允许)。您可以使用此表结构
CREATE TABLE products ( product_no integer PRIMARY KEY, name text, price numeric ); CREATE TABLE orders ( order_id integer PRIMARY KEY, shipping_address text, ... ); CREATE TABLE order_items ( product_no integer REFERENCES products, order_id integer REFERENCES orders, quantity integer, PRIMARY KEY (product_no, order_id) );
请注意,主键与最后一个表中的外键重叠。
我们知道外键不允许创建与任何产品无关的订单。但是,如果在创建引用它的订单后删除了产品会怎样?SQL 也允许您处理这种情况。直观地,我们有几个选项
不允许删除被引用的产品
也删除订单
其他什么?
为了说明这一点,让我们在上面的多对多关系示例上实现以下策略:当有人想要删除仍由订单(通过 order_items
)引用的产品时,我们不允许这样做。如果有人删除订单,订单项也将被删除
CREATE TABLE products ( product_no integer PRIMARY KEY, name text, price numeric ); CREATE TABLE orders ( order_id integer PRIMARY KEY, shipping_address text, ... ); CREATE TABLE order_items ( product_no integer REFERENCES products ON DELETE RESTRICT, order_id integer REFERENCES orders ON DELETE CASCADE, quantity integer, PRIMARY KEY (product_no, order_id) );
限制和级联删除是两种最常见的选项。RESTRICT
阻止删除被引用的行。NO ACTION
表示如果在检查约束时仍存在任何引用行,则会引发错误;如果未指定任何内容,则这是默认行为。(这两个选项之间的本质区别在于 NO ACTION
允许将检查延迟到事务的后面,而 RESTRICT
不允许。)CASCADE
指定当删除被引用行时,引用它的行也应自动删除。还有另外两个选项:SET NULL
和 SET DEFAULT
。当删除被引用行时,这些会分别导致引用行中引用列设置为 null 或其默认值。请注意,这些不会免除您遵守任何约束的义务。例如,如果操作指定 SET DEFAULT
但默认值不满足外键约束,则操作将失败。
适当选择 ON DELETE
操作取决于相关表表示的对象类型。当引用表表示被引用表所表示内容的组成部分并且不能独立存在时,则 CASCADE
可能适用。如果两个表表示独立的对象,则 RESTRICT
或 NO ACTION
更合适;实际上想要删除两个对象的应用程序必须明确说明这一点并运行两个删除命令。在上面的示例中,订单项是订单的一部分,如果删除订单,则自动删除它们很方便。但产品和订单是不同的东西,因此使产品的删除自动导致某些订单项的删除可能会被认为是有问题的。如果外键关系表示可选信息,则操作 SET NULL
或 SET DEFAULT
可能适用。例如,如果产品表包含对产品经理的引用,并且产品经理条目被删除,则将产品的产品经理设置为 null 或默认值可能很有用。
操作 SET NULL
和 SET DEFAULT
可以采用列列表来指定要设置的列。通常,会设置外键约束的所有列;仅设置子集在某些特殊情况下很有用。考虑以下示例
CREATE TABLE tenants (
tenant_id integer PRIMARY KEY
);
CREATE TABLE users (
tenant_id integer REFERENCES tenants ON DELETE CASCADE,
user_id integer NOT NULL,
PRIMARY KEY (tenant_id, user_id)
);
CREATE TABLE posts (
tenant_id integer REFERENCES tenants ON DELETE CASCADE,
post_id integer NOT NULL,
author_id integer,
PRIMARY KEY (tenant_id, post_id),
FOREIGN KEY (tenant_id, author_id) REFERENCES users ON DELETE SET NULL (author_id)
);
如果没有列的规范,外键还会将列 tenant_id
设置为 null,但该列仍然是主键的一部分。
类似于 ON DELETE
,还有 ON UPDATE
,它在被引用列更改(更新)时被调用。可能的操作相同,除了 SET NULL
和 SET DEFAULT
不能指定列列表。在这种情况下,CASCADE
表示被引用列的更新值应复制到引用行中。
通常,如果任何引用列为 null,则引用行无需满足外键约束。如果将 MATCH FULL
添加到外键声明中,则只有当所有引用列都为 null 时,引用行才能逃脱满足约束(因此,null 和非 null 值的混合将保证会违反 MATCH FULL
约束)。如果您不希望引用行能够避免满足外键约束,请将引用列声明为 NOT NULL
。
外键必须引用作为主键或构成唯一约束的列,或者是非部分唯一索引中的列。这意味着被引用列始终具有索引,以允许有效地查找引用行是否匹配。由于从被引用表中 DELETE
行或 UPDATE
被引用列将需要扫描引用表以查找与旧值匹配的行,因此通常最好也索引引用列。因为这并不总是需要的,并且有许多关于如何索引的选择,所以外键约束的声明不会自动在引用列上创建索引。
有关更新和删除数据的更多信息,请参见第 6 章。另请参见参考文档中外键约束语法的描述 CREATE TABLE。
排除约束确保如果使用指定的运算符在指定的列或表达式上比较任意两行,则这些运算符比较中的至少一个将返回 false 或 null。语法是
CREATE TABLE circles ( c circle, EXCLUDE USING gist (c WITH &&) );
另请参阅CREATE TABLE ... CONSTRAINT ... EXCLUDE
以获取详细信息。
添加排除约束会自动创建约束声明中指定的类型的索引。
如果您在文档中看到任何不正确的内容、与您对特定功能的体验不符或需要进一步澄清的内容,请使用此表单 报告文档问题。