# 5.4.约束条件
数据类型是限制可存储在表中的数据类型的一种方法。然而,对于许多应用程序,它们提供的约束过于粗糙。例如,包含产品价格的列可能只接受正值。但没有只接受正数的标准数据类型。另一个问题是,您可能希望相对于其他列或行约束列数据。例如,在包含产品信息的表中,每个产品编号只能有一行。
为此,SQL允许您在列和表上定义约束。约束使您可以随心所欲地控制表中的数据。如果用户试图在违反约束的列中存储数据,则会引发错误。即使该值来自默认值定义,这也适用。
# 5.4.1.检查约束
检查约束是最通用的约束类型。它允许您指定特定列中的值必须满足布尔(真值)表达式。例如,要要求正面的产品价格,您可以使用:
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)
);
因此,要指定命名约束,请使用关键字限制
后跟标识符,后跟约束定义。(如果不以这种方式指定约束名称,系统将为您选择一个名称。)
检查约束也可以引用多个列。假设您存储了正常价格和折扣价格,并且希望确保折扣价格低于正常价格:
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)
);
应该注意的是,如果check表达式的计算结果为true或null值,则满足check约束。由于大多数表达式在任何操作数为null时都将计算为null值,因此它们不会阻止受约束列中的null值。为了确保列不包含空值,可以使用下一节中描述的not null约束。
# 笔记
PostgreSQL不支持检查
引用表数据而不是正在检查的新行或更新行的约束。一会儿检查
违反此规则的约束在简单测试中可能会起作用,但它不能保证数据库不会达到约束条件为false的状态(由于涉及的其他行的后续更改)。这将导致数据库转储和重新加载失败。即使整个数据库状态与约束一致,重新加载也可能失败,因为没有按照满足约束的顺序加载行。如果可能,使用唯一的
, 排除
或外键
用于表示跨行和跨表限制的约束。
如果您希望在插入行时对其他行进行一次性检查,而不是持续维护一致性保证,那么可以使用自定义触发可以用来实现这一点。(这种方法避免了卸载/重新加载问题,因为_转储在重新加载数据之前不会重新安装触发器,因此在转储/重新加载期间不会强制执行检查。)
# 笔记
PostgreSQL假设检查
约束的条件是不变的,也就是说,对于同一输入行,它们总是给出相同的结果。这一假设正是检验的理由检查
仅当插入或更新行时才使用约束,而不是在其他时间。(上面关于不引用其他表数据的警告实际上是这种限制的特例。)
打破这种假设的一个常见方法是在检查
表达式,然后更改该函数的行为。PostgreSQL不允许这样做,但它不会注意到表中是否有违反检查
限制这将导致后续的数据库转储和重新加载失败。处理此类更改的建议方法是删除约束(使用改变桌子
),调整函数定义,并重新添加约束,从而根据所有表行重新检查约束。
# 5.4.2.非空约束
NOTNULL约束只指定列不能采用null值。语法示例:
CREATE TABLE products (
product_no integer NOT NULL,
name text NOT NULL,
price numeric
);
not null约束始终作为列约束编写。NOTNULL约束在功能上等同于创建check约束检查(*
列名*(不为空)
,但在PostgreSQL中,创建显式非空约束更有效。缺点是,不能为以这种方式创建的非空约束指定显式名称。
当然,一个列可以有多个约束。只需一个接一个地写下约束:
CREATE TABLE products (
product_no integer NOT NULL,
name text NOT NULL,
price numeric NOT NULL CHECK (price > 0)
);
顺序无关紧要。它不一定确定约束的检查顺序。
这个非空
约束有一个逆项:无效的
限制这并不意味着列必须为null,这肯定是无用的。相反,这只是选择列可能为null的默认行为。这个无效的
SQL标准中不存在约束,不应在可移植应用程序中使用。(添加到PostgreSQL中只是为了与其他一些数据库系统兼容。)然而,一些用户喜欢它,因为它可以很容易地在脚本文件中切换约束。例如,您可以从以下内容开始:
CREATE TABLE products (
product_no integer NULL,
name text NULL,
price numeric NULL
);
然后插入不
关键词在需要的地方。
# 提示
在大多数数据库设计中,大多数列都应该标记为NOTNULL。
# 5.4.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树索引。仅覆盖某些行的唯一性限制不能写为唯一性限制,但可以通过创建唯一性限制来强制执行此类限制部分索引.
通常,如果表中有多行约束中包含的所有列的值相等,则违反了唯一约束。然而,在这种比较中,两个空值永远不会被认为相等。这意味着,即使存在唯一约束,也可以在至少一个受约束列中存储包含空值的重复行。此行为符合SQL标准,但我们听说其他SQL数据库可能不遵循此规则。因此,在开发可移植的应用程序时要小心。
# 5.4.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树索引,并将强制标记列非空
.
一个表最多只能有一个主键。(可以有任意数量的唯一约束和非空约束,它们在功能上几乎相同,但只能将其中一个标识为主键。)关系数据库理论规定每个表必须有一个主键。PostgreSQL不强制执行此规则,但通常最好遵循此规则。
主键对于文档和客户端应用程序都很有用。例如,允许修改行值的GUI应用程序可能需要知道表的主键,才能唯一地标识行。如果主键已被声明,数据库系统还可以通过多种方式使用主键;例如,主键为引用其表的外键定义默认目标列。
# 5.4.5.外键
外键约束指定一列(或一组列)中的值必须与另一个表的某一行中出现的值匹配。我们说这保持了参照完整性在两个相关的表之间。
假设您有我们已经使用过多次的产品表:
CREATE TABLE products (
product_no integer PRIMARY KEY,
name text,
price numeric
);
我们还假设您有一个存储这些产品订单的表。我们希望确保orders表只包含实际存在的产品订单。因此,我们在orders表中定义了一个外键约束,它引用products表:
CREATE TABLE orders (
order_id integer PRIMARY KEY,
product_no integer REFERENCES products (product_no),
quantity integer
);
现在不可能创建非空的订单产品编号
未出现在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 tree (
node_id integer PRIMARY KEY,
parent_id integer REFERENCES tree,
name text,
...
);
顶级节点将具有空值家长id
,而非空家长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也允许您处理这个问题。凭直觉,我们有几个选择:
不允许删除引用的产品
同时删除订单
还有别的吗?
为了说明这一点,让我们在上面的多对多关系示例中实现以下策略:当有人想要删除仍由订单引用的产品时(通过
订购商品
),我们不允许。如果有人删除订单,订单项也会被删除:
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)
);
限制和级联删除是两种最常见的选项。限制
防止删除引用的行。不采取行动
这意味着,如果在检查约束时仍存在任何引用行,则会引发错误;如果不指定任何内容,这是默认行为。(这两种选择的本质区别在于不采取行动
允许将支票推迟到交易的晚些时候,而限制
没有。)大量
指定删除被引用行时,也应自动删除引用该行的行。还有两种选择:设为空
和设置默认值
。这会导致删除引用行时,引用行中的引用列分别设置为null或其默认值。请注意,这些并不能免除您遵守任何约束的责任。例如,如果某个操作指定设置默认值
但是默认值不满足外键约束,操作将失败。
类似于删除时
还有更新
当引用的列被更改(更新)时调用。可能的行动是相同的。在这种情况下,大量
表示引用列的更新值应复制到引用行中。
通常,如果引用行的任何引用列为空,则引用行不需要满足外键约束。如果完全匹配
被添加到外键声明中时,只有当引用行的所有引用列都为null时,引用行才会转义以满足约束(因此,null值和非null值的混合肯定会导致调用失败)完全匹配
限制)。如果不希望引用行能够避免满足外键约束,请将引用列声明为非空
.
外键必须引用作为主键或形成唯一约束的列。这意味着引用的列总是有一个索引(主键或唯一约束的基础索引);因此,检查引用行是否匹配将是有效的。自从删去
从引用的表或使现代化
引用列的索引将需要扫描引用表中与旧值匹配的行,通常最好也为引用列编制索引。因为这并不总是需要的,而且关于如何索引有很多选择,所以外键约束的声明不会自动在引用列上创建索引。
有关更新和删除数据的更多信息,请参阅第六章。有关详细信息,请参见参考文档中外键约束语法的说明创建表格.
# 5.4.6.排除约束
排除约束确保,如果使用指定的运算符对指定列或表达式上的任意两行进行比较,这些运算符比较中至少有一个将返回false或null。语法是:
CREATE TABLE circles (
c circle,
EXCLUDE USING gist (c WITH &&)
);
另见创建表格。。。限制排除
详细信息。
添加排除约束将自动创建约束声明中指定类型的索引。