pgsql.cc 提供对 postgresql.org 官网内容的中文翻译,由 Pigsty 团队维护。
数据类型是一种限制能够存储在表中的数据类别的方法。但是对于很多应用来说,它们提供的约束太粗糙。例如,一个包含产品价格的列应该只接受正值。但是没有任何一种数据类型只接受正值。另一个问题是我们可能需要根据其他列或行来约束一个列中的数据。例如,在一个包含产品信息的表中,对于每个产品编号应该只有一行。
为此,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)
);
前两个约束看起来很相似。第三个则使用了一种新语法。它并没有依附在一个特定的列,而是作为一个独立的项出现在逗号分隔的列列表中。列定义和这种约束定义可以以混合的顺序出现在列表中。
我们说前两个约束是列约束,而 第三个是表约束,因为它与列定义分开 书写。列约束也可以 写成表约束,反之则不一定 可行。上面的例子也可以写成
CREATE TABLE products (
product_no integer,
name text,
price numeric,
CHECK (price > 0),
discounted_price numeric,
CHECK (discounted_price > 0),
CHECK (price > discounted_price)
);
or even
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 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
);
一般而言,当表中(至少)有两行在作为约束 一部分的各对应列上的值都相等时,唯一约束 就被违反。但在这种 判断中空值不被视为相等。这意味着即使存在 唯一约束,也可以存储不限数量的 至少一个受约束列包含空值的行。这种 行为符合 SQL 标准,但我们 听说其他 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)
);
主键表示一个列或一组列可以用作表中行的唯一标识符。(这是主键定义的直接结果。注意,唯一约束本身并不能提供唯一标识符,因为它不排除空值。)这对于文档目的和客户端应用都很有用。例如,一个允许修改行值的 GUI 应用可能需要知道一个表的主键,以便能唯一地标识行。
一个表最多只能有一个主键(但它可以有多个唯一和非空约束)。关系数据库理论要求每一个表都要有一个主键。PostgreSQL 并未强制要求这一点,但通常最好遵循它。
一个外键约束指定一列(或一组列)中的值必须匹配出现在另一个表中某些行的值。我们说这维持了两个关联表之间的引用完整性。
假设你有我们已经用过多次的产品表:
CREATE TABLE products (
product_no integer PRIMARY KEY,
name text,
price numeric
);
再假设你有一个存储这些产品 订单的表。我们想要确保订单表只包含 实际存在的产品的订单。于是我们在订单表中定义一个 引用 products 表的 外键约束:
CREATE TABLE orders (
order_id integer PRIMARY KEY,
product_no integer REFERENCES products (product_no),
quantity integer
);
现在已经不可能创建 product_no 条目不出现在 products 表中的 订单了。
我们说在这种情况下,订单表是引用表而产品表是被引用表。相应地,也有引用和被引用列的说法。
你也可以把上面的命令简写成
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 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,而且在你什么都没有指定时它也是 默认行为。对于主键被删除时外键列 应当发生什么,还有另外两个选项: SET NULL 和 SET DEFAULT。 注意这些并不能免除你遵守约束的义务。 例如,如果某个动作指定了 SET DEFAULT 而默认值不满足外键,主键的删除 将失败。
与 ON DELETE 类似,还有 ON UPDATE,它在主键被更改(更新)时 被调用。可能的动作是相同的。
更多关于更新和删除数据的信息请见第 6 章。
最后,我们要提到外键必须引用 是主键或构成唯一约束的列。 如果外键引用唯一约束,关于空值如何匹配有 一些额外的可能性。 这些在 CREATE TABLE 的参考文档中有说明。
译文有误、术语不当或页面显示问题,请到译文仓库 pgsty/pgdoc 报告译文问题。 英文原文本身的问题,请在当前版本的对应页面向上游反馈;上游不再修订已结束维护的版本。