5.3. Constraints

数据类型是一种限制可以存储在表中的数据类型的方法。但是,对于许多应用程序,它们提供的约束过于粗糙。例如,包含产品价格的列可能只应接受正值。但是没有标准的数据类型只接受正数。另一个问题是,您可能希望相对于其他列或行约束列数据。例如,在包含产品信息的表中,每个产品编号应该只有一行。

为此,SQL 允许您定义列和表的约束。约束使您可以根据需要尽可能多地控制表中的数据。如果用户尝试将数据存储在违反约束的列中,则会引发错误。即使该值来自默认值定义,也是如此。

5 .3.1. 检查约束

检查约束是最通用的约束类型。它允许您指定某个列中的值必须满足布尔值(真值)表达式。例如,要使产品价格上涨,可以使用:

CREATE TABLE products (
    product_no integer,
    name text,
    price numeric CHECK (price > 0)
);

如您所见,约束定义位于数据类型之后,就像默认值定义一样。默认值和约束可以任何 Sequences 列出。检查约束由关键字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)
);

前两个约束应该看起来很熟悉。第三个使用新语法。它没有附加到特定的列,而是在逗号分隔的列列表中显示为单独的项目。列定义和这些约束定义可以以混合 Sequences 列出。

我们说前两个约束是列约束,而第三个约束是表约束,因为它是与任何一个列定义分开编写的。列约束也可以写为表约束,而反向约束不一定是可能的,因为列约束应该仅指代其所附的列。 (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)
);

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,
    name text,
    price numeric,
    CHECK (price > 0),
    discounted_price numeric,
    CHECK (discounted_price > 0),
    CONSTRAINT valid_discount CHECK (price > discounted_price)
);

应当注意,如果校验表达式的值为真或空值,则满足校验约束。由于大多数表达式在任何操作数为 null 的情况下都会求值为 null,因此它们不会在受约束的列中阻止 null 值。为了确保一列不包含空值,可以使用下一节中描述的非空约束。

Note

PostgreSQL 不支持CHECK约束,该约束引用除检查的新行或更新行以外的表数据。尽管违反此规则的CHECK约束似乎可以在简单测试中起作用,但是它不能保证数据库不会达到约束条件为假(由于所涉及的其他行的后续更改)而导致的状态。这将导致数据库转储和重新加载失败。即使整个数据库状态与约束一致,重装也可能失败,这是因为未按满足约束的 Sequences 加载行。如果可能,请使用UNIQUEEXCLUDEFOREIGN KEY约束来表示跨行和跨表限制。

如果您希望在插入行时一次性检查其他行,而不是连续保持一致性保证,则可以使用自定义trigger来实现这一点。 (这种方法避免了转储/重载问题,因为 pg_dump 直到重载数据后才重新安装触发器,这样在转储/重载过程中不会强制执行检查.)

Note

PostgreSQL 假设CHECK约束的条件是不可变的,也就是说,对于相同的 Importing 行,它们将始终给出相同的结果。该假设证明仅在插入或更新行时检查CHECK约束是合理的,而在其他时候则不行。 (上面关于未引用其他表数据的警告确实是此限制的一种特殊情况.)

break 此假设的常见方法的一个示例是在CHECK表达式中引用用户定义的函数,然后更改该函数的行为。 PostgreSQL 不允许这样做,但是它不会注意到表中是否存在违反CHECK约束的行。这将导致随后的数据库转储和重新加载失败。推荐的处理此类更改的方法是删除约束(使用ALTER TABLE),调整函数定义并重新添加约束,从而针对所有表行对其进行重新检查。

5 .3.2. 非空约束

非空约束仅指定列不得采用空值。语法示例:

CREATE TABLE products (
    product_no integer NOT NULL,
    name text NOT NULL,
    price numeric
);

非空约束始终被写为列约束。非空约束在功能上等同于创建检查约束CHECK (column_name IS NOT NULL),但是在 PostgreSQL 中,创建显式非空约束更为有效。缺点是您不能为以此方式创建的非空约束指定显式名称。

当然,一列可以有多个约束。只需一个接一个地写约束:

CREATE TABLE products (
    product_no integer NOT NULL,
    name text NOT NULL,
    price numeric NOT NULL CHECK (price > 0)
);

Sequences 无关紧要。它不一定确定检查约束的 Sequences。

NOT NULL约束具有相反的含义:NULL约束。这并不意味着该列必须为空,这肯定是无用的。相反,这只是选择默认行为,即列可能为空。 NULL约束在 SQL 标准中不存在,不应在可移植应用程序中使用。 (它被添加到 PostgreSQL 是为了与其他一些数据库系统兼容.)但是,某些用户喜欢它,因为它使切换脚本文件中的约束变得容易。例如,您可以从以下内容开始:

CREATE TABLE products (
    product_no integer NULL,
    name text NULL,
    price numeric NULL
);

然后在所需的位置插入NOT关键字。

Tip

在大多数数据库设计中,大多数列应标记为不为空。

5 .3.3. 独特的约束

唯一性约束可确保一列或一组列中包含的数据在表的所有行中都是唯一的。语法为:

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 树索引。不能将仅覆盖某些行的唯一性限制写为唯一性约束,但是可以通过创建唯一性partial index来实施这种限制。

通常,如果表中有多于一行的行,其中约束中包含的所有列的值均相等,则违反唯一约束。但是,在此比较中,永远不会将两个空值视为相等。这意味着即使在存在唯一约束的情况下,也可以在至少一个约束列中存储包含空值的重复行。此行为符合 SQL 标准,但是我们听说其他 SQL 数据库可能不遵循此规则。因此,在开发可移植的应用程序时要小心。

5 .3.4. 主键

主键约束指示一列或一组列可以用作表中行的唯一标识符。这要求这些值必须是唯一的并且不能为 null。因此,以下两个表定义接受相同的数据:

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 没有强制执行此规则,但是通常最好遵循它。

主键对于文档目的和 Client 端应用程序都是有用的。例如,允许修改行值的 GUI 应用程序可能需要知道表的主键才能唯一地标识行。如果已经声明了主键,则数据库系统还可以通过多种方式使用主键。例如,主键定义了引用其表的外键的默认目标列。

5 .3.5. 外键

外键约束指定一列(或一组列)中的值必须与另一个表的某一行中出现的值匹配。我们说这保持了两个相关表之间的“参照完整性”。

假设您拥有我们已经使用过几次的产品表:

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条目创建订单。

我们说在这种情况下,orders 表是* referencing 表,而 products 表是 referenced *表。同样,也有引用和引用列。

您也可以将以上命令缩短为:

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 也允许您处理该问题。直观地,我们有几种选择:

  • 禁止删除引用的产品

  • 删除订单

  • Something else?

为了说明这一点,让我们在上面的多对多关系示例中实施以下策略:当某人想要删除仍由订单引用的产品(通过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 NULLSET DEFAULT。当删除引用的行时,这将导致引用行中的引用列分别设置为 null 或它们的默认值。请注意,这些不能使您免于遵守任何约束条件。例如,如果某个动作指定SET DEFAULT,但默认值不满足外键约束,则该操作将失败。

类似于ON DELETE,也有ON UPDATE,当更改(更新)引用的列时会调用ON UPDATE。可能的动作是相同的。在这种情况下,CASCADE表示应将引用列的更新值复制到引用行中。

通常,如果引用行的任何引用列为 null,则不必满足外键约束。如果将MATCH FULL添加到外键声明中,则仅当其所有引用列均为 null 时,引用行才会满足约束条件(因此,确保 null 和非 null 值的混合可确保MATCH FULL约束失败)。如果您不希望引用行能够避免满足外键约束,则将引用列声明为NOT NULL

外键必须引用作为主键或形成唯一约束的列。这意味着被引用的列始终具有一个索引(作为主键或唯一约束的基础的索引);因此,检查引用行是否有匹配项将非常有效。由于来自被引用表的行的DELETE或被引用列的UPDATE都将要求对引用表进行扫描以查找与旧值匹配的行,因此通常也建议对引用列进行索引。因为这并不总是需要的,并且在如何构建索引方面有很多选择,所以外键约束的声明不会自动在引用列上创建索引。

有关更新和删除数据的更多信息,请参见Chapter 6。另请参阅参考文档中CREATE TABLE的外键约束语法说明。

5 .3.6. 排除约束

排除约束条件确保,如果使用指定的运算符在指定的列或表达式上比较任何两行,则这些运算符比较中的至少一个将返回 false 或 null。语法为:

CREATE TABLE circles (
    c circle,
    EXCLUDE USING gist (c WITH &&)
);

另请参见创建表...约束...排除

添加排除约束将自动创建约束声明中指定类型的索引。