[译] PostgreSQL 11 - 约束

检查约束

一个检查约束是最普通的约束类型

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

你也可以给约束一个独立的名字,这样可以让错误消息更清晰,也可以在你需要修改这个约束时直接引用它

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

一个检查约束可以同时引用多个列

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

需要注意的是当这种检查表达式的值是ture或者null时,都会被认为是符合约束检查的。由于在大多数表达式中,有一个操作数是null,整个表达式的结果就会被计算成null。所以这种检查约束并不会阻止插入空值。

非空约束

一个非空约束简单的指明某列不为空

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

当然一个列可以有不止一种约束,不同约束之间的顺序是无关紧要的

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

唯一约束

唯一约束确保,一个列或者一组列的值在整个表中是唯一的

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

多个列的唯一约束

CREATE TABLE example (
    a integer,
    b integer,
    c integer,
    UNIQUE (a, c)
);

这要求列a,c的组合在真个表中是唯一的,并不要求列a在表中是唯一的,或者列c在表中是唯一的。

添加一个唯一性约束时,会自动在这个列或这组列上创建B-tree索引。不能用唯一性约束来实现只覆盖某些行的唯一性限制,但是可以创建一个唯一的部分索引来实现这种限制。
两个空值在比较的时候会被认为是不同的,这就意味着在有唯一性约束的列上可以有多个null,这是符合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-tree索引。
一个表最多只能有一个主键,但是可以有多个唯一并且非空的约束,虽然在功能上这两种约束几乎是一样的,但还是只能有一个被标识成主键。关系数据库理论要求每个表都必须要有一个主键,但是PostgreSQL并没有要求这点,但最好还是要遵守它。

外键

一个外键约束表明一个列或者一组列中的值必须匹配另一个表中某些行的值。我们称它维持了两个关系表的引用完整性。

比如说有一个产品表

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
);

这种情况下,我们称订单表是引用表,产品表是被引用表。类似的还有引用列和被引用列。
上面表的定义可以被简化成

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)
);

我们知道外键不允许创建一个订单和任何产品都不相关。但是一个产品在引用它的订单创建之后被删除了又会怎么样呢?直观的,我们会有下面的选项:

  • 不允许被删除一个被引用的产品
  • 同时删除引用改产品的订单
  • 其他
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,这表明当一个被引用的列被删除之后,引用它的列需要被设成空或者默认值,当然这并不表示他们不需要遵守任何约束,比如默认值不满足外键约束一样会导致失败。

和ON DELETE相似还有ON UPDATE,它会在被引用的列被修改时触发。可用的操作和上面相同,在这种情况下CASCADE意味着在被引用列中被更新的值会被复制到引用列中去。

正常情况下,如果引用行中有任意的引用列是null,那它可以不必满足外键约束。如果在外键定义中加入MATCH FULL,那么只有当所有的引用列都是null时才可以不用满足外键约束,如果你不希望引用行能够避开外键约束,可以把所有的引用列都设置成NOT NULL。

一个外键所引用的列必须是主键或者是被唯一性约束的列,这就意味着被引用的列都是有索引的,因此在检查一个引用行是否匹配时是高效的。由于在删除一个被引用列或者更新一个被引用列时需要扫描引用表中的列来进行匹配,所以在引用列上建立索引也很有用。因为这种做法并不是必须的,并且在创建索引是也有很多中选择,所以外键约束并不会自动在引用列上创建索引。

排他约束

排他约束保证如果将任何两行的指定列或表达式使用指定操作符进行比较,至少其中一个操作符比较将会返回否或空值。

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

增加一个排他约束将在约束声明所指定的类型上自动创建索引。

阅读 212

推荐阅读