PostgreSQL的学习心得和知识总结(九)|PostgreSQL约束的定义创建及特性说明


注:因为是最近我要开发 数据库约束相关的新特性,下面这篇博客先完全讲一下在PostgreSQL数据库里面的约束在使用方面相关的知识。因为本人能力和经验上的不足或许有写的不对的地方,但也本着开源共享的精神,拿出来请大家多多指正和交流!

约束定义

在数据库中,数据类型是一种可以限制存储在表中数据类别的方法。比如int 代表存储的是整数 但是对于很多使用场景来说,这些定义数据的数据类型提供的约束太粗糙。
例如:

1、一个包含产品价格的列(字段)应该只接受正值(一个int是无法保证的)。但是没有任何一种标准数据类型只接受正值
2、另一个问题是我们可能需要根据其他列或行来约束一个列中的数据。例如,在一个包含产品信息的表中,对于每个产品编号应该只有一行。

不过好在现行的SQL允许在 一个列or一个表 上定义和使用约束。那么约束是什么呢?约束是:能够让我们根据自己的意愿来控制表里面的数据信息(某一行还是全表) 有了约束之后,当用户试图去在 有约束的某一列上存储一个不合约束的数据时,就会抛出异常,即存储失败。(当然即使这个值来自于默认值定义,这个规则也是依旧适用的)

注:我们这里使用的学习环境为 Linux操作系统+PostgreSQL数据库

检查约束

检查约束是最常见的约束类型,即:允许用户来指定某一列里面的值必须要满足一个bool expression。比如:一个人的年龄或者一件商品的价格只可能为正值

作用在某一列上
uxdb=# \d
Did not find any relations.
uxdb=# CREATE TABLE products (
    product_no integer,
    product_date timestamptz,
    price numeric CHECK (price > 0)
);
CREATE TABLE
uxdb=# insert into products values (1,now(),5);
INSERT 0 1
uxdb=# select * from products;
 product_no |         product_date          | price 
------------+-------------------------------+-------
          1 | 2020-06-03 10:05:44.437313+08 |     5
(1 row)

uxdb=# insert into products values (2,now(),-6);
ERROR:  new row for relation "products" violates check constraint "products_price_check"
DETAIL:  Failing row contains (2, 2020-06-03 10:06:21.487145+08, -6).
uxdb=# 

如上的错误信息:往products表里面插入的新行违反了检查约束"products_price_check"
下面我们来小结一下检查约束

1、约束的定义类似于默认值定义一样:跟在数据类型之后。(默认值和约束的定义没有先后顺序)
2、检查约束的定义:由关键字CHECK以及其后面圆括号包裹起来的布尔表达式组成
3、一个检查约束的表达式若是没有涉及到其中一列那是毫无意义的,而且也通不过(如下所示)

uxdb=# CREATE TABLE products2 (
    product_no integer,
    product_date timestamptz,
    price numeric CHECK (money > 0)
);
ERROR:  column "money" does not exist
uxdb=#

注:在上面的例子中,细心的小伙伴们已经发现它提示的错误信息为:

ERROR:  new row for relation "products" violates check constraint "products_price_check"

这里面的约束名字是PostgreSQL数据库自动为我们拼接成的(表名_字段名_check),当然我们也可以指定这个约束叫什么名字:my_check_name (一个确切的约束名字将有利于阅读和后续的引用)

uxdb=# CREATE TABLE products2 (
    product_no integer,
    product_date timestamptz,
    price numeric constraint my_check_name CHECK (price > 0)
);
CREATE TABLE
uxdb=# insert into products2 values (1,now(),-6);
ERROR:  new row for relation "products2" violates check constraint "my_check_name"
DETAIL:  Failing row contains (1, 2020-06-03 10:21:42.538297+08, -6).
uxdb=# 

注:在我们这个指定一个约束名字的时候,是在约束名字之前使用了关键字constraint 然后在约束名字之后是约束的定义。(当然这种情况下,约束的名字是不能省略的)

uxdb=# CREATE TABLE products3 (
    product_no integer,
    product_date timestamptz,
    price numeric constraint  CHECK (price > 0)
);#我把约束的名字删了,建表失败
ERROR:  syntax error at or near "CHECK"
LINE 4:     price numeric constraint  CHECK (price > 0)
                                      ^
uxdb=#
作用在多个列上

例如使用场景:我们存储一个普通价格和一个打折后的价格,而我们希望保证打折后的价格低于普通价格。就可以如下:

uxdb=# CREATE TABLE products (
    product_no integer,
    product_date timestamptz,
    price numeric CHECK (price > 0),
    discounted_price numeric CHECK (discounted_price > 0),
    CONSTRAINT mycheck_between_two CHECK (price > discounted_price)
);
CREATE TABLE
uxdb=# \d products 
                           Table "public.products"
      Column      |           Type           | Collation | Nullable | Default 
------------------+--------------------------+-----------+----------+---------
 product_no       | integer                  |           |          | 
 product_date     | timestamp with time zone |           |          | 
 price            | numeric                  |           |          | 
 discounted_price | numeric                  |           |          | 
Check constraints:
    "mycheck_between_two" CHECK (price > discounted_price)
    "products_discounted_price_check" CHECK (discounted_price > 0::numeric)
    "products_price_check" CHECK (price > 0::numeric)

uxdb=# insert into products values (1,now(),6,7);
ERROR:  new row for relation "products" violates check constraint "mycheck_between_two"
DETAIL:  Failing row contains (1, 2020-06-03 10:36:57.542604+08, 6, 7).
uxdb=#

如上:打折价格大于原价,也因此报错:违反检查约束mycheck_between_two。我们来分析一下:products_discounted_price_checkproducts_price_check的定义跟之前一样,而约束mycheck_between_two则使用了一种新语法:并没有依附在一个特定的列,而是作为一个独立的项出现在逗号分隔的列列表之中。如果我们没有使用自命名的方式,则该约束的名字为:

uxdb=# \d products 
                           Table "public.products"
      Column      |           Type           | Collation | Nullable | Default 
------------------+--------------------------+-----------+----------+---------
 product_no       | integer                  |           |          | 
 product_date     | timestamp with time zone |           |          | 
 price            | numeric                  |           |          | 
 discounted_price | numeric                  |           |          | 
Check constraints:
    "products_check" CHECK (price > discounted_price)
    "products_discounted_price_check" CHECK (discounted_price > 0::numeric)
    "products_price_check" CHECK (price > 0::numeric)

如上我们就看到了在PostgreSQL数据库里面是可以出现:列定义和这种约束定义以混合的顺序存在。

作用在整个表上

我们前面也说到了检查约束是可以建立在整张表上面的,它将独立于任何一个列定义。上面的"products_check"就是表约束。 列约束是可以写成表约束的,反之不行。因为一个列约束只能引用它所依赖的那一列。 在PostgreSQL中,并不会强制要求这个规则,但是如果我们希望表定义能够在其他数据库系统中工作,那就应该遵循它。示例如下:

uxdb=# CREATE TABLE products (
    product_no integer,
    product_date timestamptz,
    price numeric,
    CHECK (price > 0),
    discounted_price numeric,
    CHECK (discounted_price > 0),
    CHECK (price > discounted_price)
);
CREATE TABLE
uxdb=# \d products 
                           Table "public.products"
      Column      |           Type           | Collation | Nullable | Default 
------------------+--------------------------+-----------+----------+---------
 product_no       | integer                  |           |          | 
 product_date     | timestamp with time zone |           |          | 
 price            | numeric                  |           |          | 
 discounted_price | numeric                  |           |          | 
Check constraints:
    "products_check" CHECK (price > discounted_price)
    "products_discounted_price_check" CHECK (discounted_price > 0::numeric)
    "products_price_check" CHECK (price > 0::numeric)

uxdb=#

如上,这样的定义方式是将列约束也拆分成以逗号相隔的方式。上面之所以说是非强制的,大家看一下这个例子:(我将CHECK (price > discounted_price)依附在discounted_price 列定义之后)

uxdb=# CREATE TABLE products2 (
    product_no integer,
    product_date timestamptz,
    price numeric,
    CHECK (price > 0),
    discounted_price numeric CHECK (price > discounted_price),
    CHECK (discounted_price > 0) 
);                                  
CREATE TABLE
uxdb=# \d products2
                           Table "public.products2"
      Column      |           Type           | Collation | Nullable | Default 
------------------+--------------------------+-----------+----------+---------
 product_no       | integer                  |           |          | 
 product_date     | timestamp with time zone |           |          | 
 price            | numeric                  |           |          | 
 discounted_price | numeric                  |           |          | 
Check constraints:
    "products2_check" CHECK (price > discounted_price)
    "products2_discounted_price_check" CHECK (discounted_price > 0::numeric)
    "products2_price_check" CHECK (price > 0::numeric)

uxdb=#

当然表约束也是可以用and等和独立的列约束进行联立:(这样的话就会约束 合二为一)

uxdb=# CREATE TABLE products (
    product_no integer,
    product_date timestamptz,
    price numeric CHECK (price > 0),
    discounted_price numeric,
    CHECK (discounted_price > 0 AND price > discounted_price)
);
CREATE TABLE
uxdb=# \d products
                           Table "public.products"
      Column      |           Type           | Collation | Nullable | Default 
------------------+--------------------------+-----------+----------+---------
 product_no       | integer                  |           |          | 
 product_date     | timestamp with time zone |           |          | 
 price            | numeric                  |           |          | 
 discounted_price | numeric                  |           |          | 
Check constraints:
    "products_check" CHECK (discounted_price > 0::numeric AND price > discounted_price)
    "products_price_check" CHECK (price > 0::numeric)

uxdb=# insert into products values (1,now(),6,7);
ERROR:  new row for relation "products" violates check constraint "products_check"
DETAIL:  Failing row contains (1, 2020-06-03 11:09:11.071108+08, 6, 7).
uxdb=# insert into products values (1,now(),6,-7);
ERROR:  new row for relation "products" violates check constraint "products_check"
DETAIL:  Failing row contains (1, 2020-06-03 11:09:30.007235+08, 6, -7).
uxdb=# insert into products values (1,now(),-6,-7);
ERROR:  new row for relation "products" violates check constraint "products_check"
DETAIL:  Failing row contains (1, 2020-06-03 11:09:53.912531+08, -6, -7).
uxdb=#

当然我们也可以使用constraint关键字来为表约束(可以用列约束相同的方法)指定名称:

uxdb=# CREATE TABLE products2 (
    product_no integer,
    product_date timestamptz,
    price numeric CHECK (price > 0),
    discounted_price numeric,
    constraint my_check_of_table CHECK (discounted_price > 0 AND price > discounted_price)
);
CREATE TABLE
uxdb=# \d products2
                           Table "public.products2"
      Column      |           Type           | Collation | Nullable | Default 
------------------+--------------------------+-----------+----------+---------
 product_no       | integer                  |           |          | 
 product_date     | timestamp with time zone |           |          | 
 price            | numeric                  |           |          | 
 discounted_price | numeric                  |           |          | 
Check constraints:
    "my_check_of_table" CHECK (discounted_price > 0::numeric AND price > discounted_price)
    "products2_price_check" CHECK (price > 0::numeric)

uxdb=#

一个检查约束的有效在于:其定义时的布尔表达式的值为 真or空值。

uxdb=# \d products2 
                           Table "public.products2"
      Column      |           Type           | Collation | Nullable | Default 
------------------+--------------------------+-----------+----------+---------
 product_no       | integer                  |           |          | 
 product_date     | timestamp with time zone |           |          | 
 price            | numeric                  |           |          | 
 discounted_price | numeric                  |           |          | 
Check constraints:
    "my_check_of_table" CHECK (discounted_price > 0::numeric AND price > discounted_price)
    "products2_price_check" CHECK (price > 0::numeric)

uxdb=# insert into products2 values (1,now(),3,NULL);
INSERT 0 1
uxdb=# select * from products2 ;
 product_no |         product_date          | price | discounted_price 
------------+-------------------------------+-------+------------------
          1 | 2020-06-03 11:30:33.637216+08 |     3 |                 
(1 row)
uxdb=# insert into products2 values (2,now(),NULL,NULL);
INSERT 0 1
uxdb=# select * from products2 ;
 product_no |         product_date          | price | discounted_price 
------------+-------------------------------+-------+------------------
          1 | 2020-06-03 11:30:33.637216+08 |     3 |                 
          2 | 2020-06-03 11:32:16.590302+08 |       |                 
(2 rows)

uxdb=#

如上所示:当任何操作数为空的时候,大部分表达式将计算为空值,因此他们不会阻止该被约束的列的空值。倘若我们为了保证某一列中不包含空值,那么请使用接下来的 非空约束

非空约束

非空约束比较简单:一个非空约束仅仅保证某一列中不会有空值。示例如下:

uxdb=# CREATE TABLE products (
    product_no integer NOT NULL,
    product_date timestamptz,
    price numeric CHECK (price > 0) NOT NULL,
    discounted_price numeric NOT NULL,
    constraint my_check_of_table CHECK (discounted_price > 0 AND price > discounted_price)
);
CREATE TABLE
uxdb=# \d products 
                           Table "public.products"
      Column      |           Type           | Collation | Nullable | Default 
------------------+--------------------------+-----------+----------+---------
 product_no       | integer                  |           | not null | 
 product_date     | timestamp with time zone |           |          | 
 price            | numeric                  |           | not null | 
 discounted_price | numeric                  |           | not null | 
Check constraints:
    "my_check_of_table" CHECK (discounted_price > 0::numeric AND price > discounted_price)
    "products_price_check" CHECK (price > 0::numeric)

uxdb=# insert into products values (1,now(),5,NULL);
ERROR:  null value in column "discounted_price" violates not-null constraint
DETAIL:  Failing row contains (1, 2020-06-03 11:34:54.845962+08, 5, null).
uxdb=# insert into products values (1,now(),5,6);
ERROR:  new row for relation "products" violates check constraint "my_check_of_table"
DETAIL:  Failing row contains (1, 2020-06-03 11:35:26.334107+08, 5, 6).
uxdb=#

如上,我给非空列传入空值之后的错误(空值违反了非空约束):null value in column "discounted_price" violates not-null constraint

一个非空约束总是被写成一个列约束。一个非空约束等价于创建一个检查约束(如下所示),但在PostgreSQL中创建一个显式的非空约束更高效。

uxdb=# \d products #原来的 NOT NULL的表
                           Table "public.products"
      Column      |           Type           | Collation | Nullable | Default 
------------------+--------------------------+-----------+----------+---------
 product_no       | integer                  |           | not null | 
 product_date     | timestamp with time zone |           |          | 
 price            | numeric                  |           | not null | 
 discounted_price | numeric                  |           | not null | 
Check constraints:
    "my_check_of_table" CHECK (discounted_price > 0::numeric AND price > discounted_price)
    "products_price_check" CHECK (price > 0::numeric)

uxdb=# CREATE TABLE products2 ( #这里是使用检查约束的建表
    product_no integer NOT NULL,
    product_date timestamptz,
    price numeric CHECK (price > 0) constraint my_not_null_of_price check (price is NOT NULL),
    discounted_price numeric NOT NULL,
    constraint my_check_of_table CHECK (discounted_price > 0 AND price > discounted_price)
);
CREATE TABLE
uxdb=# \d products2 
                           Table "public.products2"
      Column      |           Type           | Collation | Nullable | Default 
------------------+--------------------------+-----------+----------+---------
 product_no       | integer                  |           | not null | 
 product_date     | timestamp with time zone |           |          | 
 price            | numeric                  |           |          | 
 discounted_price | numeric                  |           | not null | 
Check constraints:
    "my_check_of_table" CHECK (discounted_price > 0::numeric AND price > discounted_price)
    "my_not_null_of_price" CHECK (price IS NOT NULL)
    "products2_price_check" CHECK (price > 0::numeric)

uxdb=#
uxdb=# insert into products2 values (1,now(),NULL,5);
ERROR:  new row for relation "products2" violates check constraint "my_not_null_of_price"
DETAIL:  Failing row contains (1, 2020-06-03 11:52:02.116101+08, null, 5).
uxdb=# 

如上,我们可以得到如下结论:

1、一个非空约束等价于创建一个检查约束CHECK (column_name IS NOT NULL)
2、使用检查约束的方式 我们可以为这个非空约束取一个显示的名字:my_not_null_of_price
3、看price这一行:约束的顺序没有关系,因为并不需要决定约束被检查的顺序

说起非空约束,我们大家会问 有没有什么空约束(NULL 约束)。在SQL标准里面是没有空约束的(不能被用于可移植的应用),而在PostgreSQL里面有(目的在于为了兼容某些数据库系统)。OK,下面来简单看一下这个“空约束”,它的存在并不是说:这一列必须为空。而是仅仅选择了列可能为空的默认行为。示例如下:

uxdb=# CREATE TABLE products3 (
    product_no integer NULL,
    product_date timestamptz,
    price numeric CHECK (price > 0) NULL,
    discounted_price numeric NULL,
    constraint my_check_of_table CHECK (discounted_price > 0 AND price > discounted_price)
);
CREATE TABLE
uxdb=# \d products3
                           Table "public.products3"
      Column      |           Type           | Collation | Nullable | Default 
------------------+--------------------------+-----------+----------+---------
 product_no       | integer                  |           |          | 
 product_date     | timestamp with time zone |           |          | 
 price            | numeric                  |           |          | 
 discounted_price | numeric                  |           |          | 
Check constraints:
    "my_check_of_table" CHECK (discounted_price > 0::numeric AND price > discounted_price)
    "products3_price_check" CHECK (price > 0::numeric)

uxdb=# insert into products3 values (1,now(),6,5);
INSERT 0 1
uxdb=# select * from products3;
 product_no |         product_date          | price | discounted_price 
------------+-------------------------------+-------+------------------
          1 | 2020-06-03 11:54:52.124293+08 |     6 |                5
(1 row)

uxdb=# insert into products3 values (1,now(),NULL,5);
INSERT 0 1
uxdb=# insert into products3 values (1,now(),NULL,NULL);
INSERT 0 1
uxdb=# select * from products3;
 product_no |         product_date          | price | discounted_price 
------------+-------------------------------+-------+------------------
          1 | 2020-06-03 11:54:52.124293+08 |     6 |                5
          1 | 2020-06-03 12:01:37.255415+08 |       |                5
          1 | 2020-06-03 12:01:43.48572+08  |       |                 
(3 rows)

uxdb=#

根据存在即合理的观点:空约束可以使得在一个脚本文件中可以很容易的进行约束切换,初始化(建表)完成之后就可以在需要的地方插入NOT关键词。

注:在大部分数据库中多数列还是应该被标记为非空。

唯一约束

唯一约束保证在某一列中或者某一组列中保存的数据在表中所有的行(记录)上是唯一的。

采用列约束的格式
uxdb=# CREATE TABLE products (
    product_no integer unique,
    product_date timestamptz,
    price numeric CHECK (price > 0) NOT NULL,
    discounted_price numeric,
    constraint my_check_of_table CHECK (discounted_price > 0 AND price > discounted_price)
);
CREATE TABLE
uxdb=# \d products
                           Table "public.products"
      Column      |           Type           | Collation | Nullable | Default 
------------------+--------------------------+-----------+----------+---------
 product_no       | integer                  |           |          | 
 product_date     | timestamp with time zone |           |          | 
 price            | numeric                  |           | not null | 
 discounted_price | numeric                  |           |          | 
Indexes:
    "products_product_no_key" UNIQUE CONSTRAINT, btree (product_no)
Check constraints:
    "my_check_of_table" CHECK (discounted_price > 0::numeric AND price > discounted_price)
    "products_price_check" CHECK (price > 0::numeric)

uxdb=# insert into products values (1,now(),NULL,NULL);
ERROR:  null value in column "price" violates not-null constraint
DETAIL:  Failing row contains (1, 2020-06-03 13:18:12.46008+08, null, null).
uxdb=# insert into products values (1,now(),5,NULL);
INSERT 0 1
uxdb=# insert into products values (1,now(),6,NULL);
ERROR:  duplicate key value violates unique constraint "products_product_no_key"
DETAIL:  Key (product_no)=(1) already exists.
uxdb=#

上面为product_no 字段建立了一个唯一性约束,若是违反唯一约束则:重复的键值违反了唯一约束 products_product_no_key (但是我们可以看到products_product_no_key属于一个索引)

下面是使用CONSTRAINT关键字为我们的唯一索引起一个名字,如下:

uxdb=# CREATE TABLE products (
    product_no integer CONSTRAINT must_be_different unique,
    product_date timestamptz,
    price numeric CHECK (price > 0) NOT NULL,
    discounted_price numeric,
    constraint my_check_of_table CHECK (discounted_price > 0 AND price > discounted_price)
);
CREATE TABLE
uxdb=# \d products
                           Table "public.products"
      Column      |           Type           | Collation | Nullable | Default 
------------------+--------------------------+-----------+----------+---------
 product_no       | integer                  |           |          | 
 product_date     | timestamp with time zone |           |          | 
 price            | numeric                  |           | not null | 
 discounted_price | numeric                  |           |          | 
Indexes:
    "must_be_different" UNIQUE CONSTRAINT, btree (product_no)
Check constraints:
    "my_check_of_table" CHECK (discounted_price > 0::numeric AND price > discounted_price)
    "products_price_check" CHECK (price > 0::numeric)

uxdb=# insert into products values (1,now(),6,4);
INSERT 0 1
uxdb=# insert into products values (1,now(),5,4);
ERROR:  duplicate key value violates unique constraint "must_be_different"
DETAIL:  Key (product_no)=(1) already exists.
uxdb=# insert into products values (2,now(),6,4);
INSERT 0 1
uxdb=# select * from products ;
 product_no |         product_date          | price | discounted_price 
------------+-------------------------------+-------+------------------
          1 | 2020-06-03 14:16:21.836766+08 |     6 |                4
          2 | 2020-06-03 14:17:09.477303+08 |     6 |                4
(2 rows)

uxdb=#
采用表约束的格式
uxdb=# CREATE TABLE products (
    product_no integer,
    product_date timestamptz,
    price numeric CHECK (price > 0) NOT NULL,
    discounted_price numeric,
    constraint my_check_of_table CHECK (discounted_price > 0 AND price > discounted_price),
uxdb(# unique(product_no));#这里就是用的表约束
CREATE TABLE
uxdb=# \d products
                           Table "public.products"
      Column      |           Type           | Collation | Nullable | Default 
------------------+--------------------------+-----------+----------+---------
 product_no       | integer                  |           |          | 
 product_date     | timestamp with time zone |           |          | 
 price            | numeric                  |           | not null | 
 discounted_price | numeric                  |           |          | 
Indexes:
    "products_product_no_key" UNIQUE CONSTRAINT, btree (product_no)
Check constraints:
    "my_check_of_table" CHECK (discounted_price > 0::numeric AND price > discounted_price)
    "products_price_check" CHECK (price > 0::numeric)

uxdb=# insert into products values (1,now(),6,NULL);
INSERT 0 1
uxdb=# insert into products values (1,now(),6,NULL);
ERROR:  duplicate key value violates unique constraint "products_product_no_key"
DETAIL:  Key (product_no)=(1) already exists.
uxdb=#
采用表约束的格式(为一组列)

若是要为一组列定义一个唯一约束,把它写作一个表级约束,列名用逗号分隔。示例如下:

uxdb=# CREATE TABLE products (
    product_no integer,
    product_date timestamptz,
    price numeric CHECK (price > 0) NOT NULL,
    discounted_price numeric,
    constraint my_check_of_table CHECK (discounted_price > 0 AND price > discounted_price),unique(product_no,price));
CREATE TABLE
uxdb=# \d products
                           Table "public.products"
      Column      |           Type           | Collation | Nullable | Default 
------------------+--------------------------+-----------+----------+---------
 product_no       | integer                  |           |          | 
 product_date     | timestamp with time zone |           |          | 
 price            | numeric                  |           | not null | 
 discounted_price | numeric                  |           |          | 
Indexes:
    "products_product_no_price_key" UNIQUE CONSTRAINT, btree (product_no, price)
Check constraints:
    "my_check_of_table" CHECK (discounted_price > 0::numeric AND price > discounted_price)
    "products_price_check" CHECK (price > 0::numeric)

uxdb=# insert into products values (1,now(),6,NULL);
INSERT 0 1
uxdb=# insert into products values (2,now(),5,NULL);
INSERT 0 1
uxdb=# insert into products values (2,now(),5,4);
ERROR:  duplicate key value violates unique constraint "products_product_no_price_key"
DETAIL:  Key (product_no, price)=(2, 5) already exists.
uxdb=# insert into products values (2,now(),6,4);
INSERT 0 1
uxdb=#

如上,对于值对(2,5)这个组合的值在整个表中是唯一的,但是具体的任何一列并没有要求是唯一的。

我们看上面这几种表结构,唯一约束都对应一个B-tree索引。即:增加一个唯一约束就会在约束中依赖的列或列组上自动创建一个唯一B-tree索引。只覆盖某些行的唯一性限制不能被写为一个唯一约束,但可以通过创建一个唯一的部分索引来强制这种限制。

但是我们下面来看一下特殊情况:(使用上面创建的最新表)

uxdb=# \d products
                           Table "public.products"
      Column      |           Type           | Collation | Nullable | Default 
------------------+--------------------------+-----------+----------+---------
 product_no       | integer                  |           |          | 
 product_date     | timestamp with time zone |           |          | 
 price            | numeric                  |           | not null | 
 discounted_price | numeric                  |           |          | 
Indexes:
    "must_be_different" UNIQUE CONSTRAINT, btree (product_no)
Check constraints:
    "my_check_of_table" CHECK (discounted_price > 0::numeric AND price > discounted_price)
    "products_price_check" CHECK (price > 0::numeric)

uxdb=# select * from products ;
 product_no |         product_date          | price | discounted_price 
------------+-------------------------------+-------+------------------
          1 | 2020-06-03 14:16:21.836766+08 |     6 |                4
          2 | 2020-06-03 14:17:09.477303+08 |     6 |                4
(2 rows)

uxdb=# insert into products values (NULL,now(),6,4);
INSERT 0 1
uxdb=# insert into products values (NULL,now(),6,4);
INSERT 0 1
uxdb=# select * from products ;
 product_no |         product_date          | price | discounted_price 
------------+-------------------------------+-------+------------------
          1 | 2020-06-03 14:16:21.836766+08 |     6 |                4
          2 | 2020-06-03 14:17:09.477303+08 |     6 |                4
            | 2020-06-03 14:32:41.561166+08 |     6 |                4
            | 2020-06-03 14:32:45.44963+08  |     6 |                4
(4 rows)

uxdb=#

如上,该表的product_no 有一个自命名的唯一索引must_be_different,但是这个字段的值可以是空值NULL。“此时的唯一约束看起来好像失效了!”

这里需要说明一下:通常情况下,如果表中有超过一行在约束所包括列上的值相同,将会违反唯一约束。但是在这种比较中,两个空值NULL被认为是不同的。这意味着即便存在一个唯一约束,也可以存储多个在至少一个被约束的列上包含空值的行/记录(如上)。这种行为符合SQL标准,但我们听说一些其他SQL数据库可能不遵循这个规则。所以在开发需要可移植的应用时应注意这一点。

主键约束

主键约束一个主键约束可以使用在 表中的行的唯一标识符的一个列或者一组列;并且要求这些值都是唯一且非空

个人观点:简单地来看是非空约束和唯一约束的共同作用。如下:

作用在一个列上
uxdb=# CREATE TABLE products (
    product_no integer CONSTRAINT must_be_different unique NOT NULL,
    product_date timestamptz,
    price numeric CHECK (price > 0),
    discounted_price numeric,
    constraint my_check_of_table CHECK (discounted_price > 0 AND price > discounted_price)
);
CREATE TABLE
uxdb=# \d products 
                           Table "public.products"
      Column      |           Type           | Collation | Nullable | Default 
------------------+--------------------------+-----------+----------+---------
 product_no       | integer                  |           | not null | 
 product_date     | timestamp with time zone |           |          | 
 price            | numeric                  |           |          | 
 discounted_price | numeric                  |           |          | 
Indexes:
    "must_be_different" UNIQUE CONSTRAINT, btree (product_no)
Check constraints:
    "my_check_of_table" CHECK (discounted_price > 0::numeric AND price > discounted_price)
    "products_price_check" CHECK (price > 0::numeric)
uxdb=# CREATE TABLE products2 (
    product_no integer PRIMARY KEY,
    product_date timestamptz,
    price numeric CHECK (price > 0),
    discounted_price numeric,
    constraint my_check_of_table CHECK (discounted_price > 0 AND price > discounted_price)
);
CREATE TABLE
uxdb=# \d products2
                           Table "public.products2"
      Column      |           Type           | Collation | Nullable | Default 
------------------+--------------------------+-----------+----------+---------
 product_no       | integer                  |           | not null | 
 product_date     | timestamp with time zone |           |          | 
 price            | numeric                  |           |          | 
 discounted_price | numeric                  |           |          | 
Indexes:
    "products2_pkey" PRIMARY KEY, btree (product_no)
Check constraints:
    "my_check_of_table" CHECK (discounted_price > 0::numeric AND price > discounted_price)
    "products2_price_check" CHECK (price > 0::numeric)

uxdb=#

以上两个表在接受数据的时候是一致的,如下:

uxdb=# insert into products values (NULL,now(),6,4);
ERROR:  null value in column "product_no" violates not-null constraint
DETAIL:  Failing row contains (null, 2020-06-03 14:49:59.478502+08, 6, 4).
uxdb=# insert into products2 values (NULL,now(),6,4);
ERROR:  null value in column "product_no" violates not-null constraint
DETAIL:  Failing row contains (null, 2020-06-03 14:50:17.527524+08, 6, 4).
uxdb=# 
uxdb=# insert into products values (1,now(),6,4);
INSERT 0 1
uxdb=# insert into products values (1,now(),5,4);
ERROR:  duplicate key value violates unique constraint "must_be_different"
DETAIL:  Key (product_no)=(1) already exists.
uxdb=# insert into products2 values (1,now(),5,4);
INSERT 0 1
uxdb=# insert into products2 values (1,now(),6,4);
ERROR:  duplicate key value violates unique constraint "products2_pkey"
DETAIL:  Key (product_no)=(1) already exists.
uxdb=# select * from products ;
 product_no |         product_date          | price | discounted_price 
------------+-------------------------------+-------+------------------
          1 | 2020-06-03 14:50:54.615186+08 |     6 |                4
(1 row)

uxdb=# select * from products2 ;
 product_no |         product_date          | price | discounted_price 
------------+-------------------------------+-------+------------------
          1 | 2020-06-03 14:51:15.278829+08 |     5 |                4
(1 row)

uxdb=#

在给两表的product_no 字段传入NULL 时,都违反了非空约束not-null constraint;当给两表传入相同的值得时候,第一个表违反了唯一约束unique constraint "must_be_different",而第二个表则违反了唯一约束unique constraint "products2_pkey"

作用在多个列上

主键也是包含多个列的,其语法格式类似于唯一约束,如下:

uxdb=# CREATE TABLE products (
    product_no integer,                             
    product_date timestamptz,
    price numeric CHECK (price > 0),
    discounted_price numeric,
    constraint my_check_of_table CHECK (discounted_price > 0 AND price > discounted_price),PRIMARY KEY(product_no,price)
);
CREATE TABLE
uxdb=# \d products
                           Table "public.products"
      Column      |           Type           | Collation | Nullable | Default 
------------------+--------------------------+-----------+----------+---------
 product_no       | integer                  |           | not null | 
 product_date     | timestamp with time zone |           |          | 
 price            | numeric                  |           | not null | 
 discounted_price | numeric                  |           |          | 
Indexes:
    "products_pkey" PRIMARY KEY, btree (product_no, price)
Check constraints:
    "my_check_of_table" CHECK (discounted_price > 0::numeric AND price > discounted_price)
    "products_price_check" CHECK (price > 0::numeric)
                    ^
uxdb=# insert into products values (1,now(),5,4);
INSERT 0 1
uxdb=# insert into products values (2,now(),5,4);
INSERT 0 1
uxdb=# insert into products values (1,now(),6,4);
INSERT 0 1
uxdb=# insert into products values (1,now(),5,4);
ERROR:  duplicate key value violates unique constraint "products_pkey"
DETAIL:  Key (product_no, price)=(1, 5) already exists.
uxdb=# insert into products values (NULL,now(),5,4);
ERROR:  null value in column "product_no" violates not-null constraint
DETAIL:  Failing row contains (null, 2020-06-03 15:26:25.808028+08, 5, 4).
uxdb=#

对上面的结构解释一下:

1、增加一个主键将自动在主键中列出的列或列组上创建一个唯一B-tree索引
2、并且会强制这些列被标记为NOT NULL
3、(a1,b1)插入之后,(a1,b2)or (a2,b1)插入都没有问题。但(a1,b1)作为整体不可重复
4、其中字段传入NULL将违反非空约束
5、因为是主键,一个表最多只能够拥有一个主键。而具备相同性质/功能的非空约束+唯一约束组合是可以任意数量的

uxdb=# \d products
                           Table "public.products"
      Column      |           Type           | Collation | Nullable | Default 
------------------+--------------------------+-----------+----------+---------
 product_no       | integer                  |           | not null | 
 product_date     | timestamp with time zone |           |          | 
 price            | numeric                  |           | not null | 
 discounted_price | numeric                  |           |          | 
Indexes:
    "products_pkey" PRIMARY KEY, btree (product_no, price)
Check constraints:
    "my_check_of_table" CHECK (discounted_price > 0::numeric AND price > discounted_price)
    "products_price_check" CHECK (price > 0::numeric)

uxdb=# create table product2 (product_no int not null,product_date timestamptz,price numeric check (price>0) not null,discounted_price numeric,constraint my_check_of_table CHECK (discounted_price > 0 AND price > discounted_price),UNIQUE(product_no,price));
CREATE TABLE
uxdb=# \d product2
                           Table "public.product2"
      Column      |           Type           | Collation | Nullable | Default 
------------------+--------------------------+-----------+----------+---------
 product_no       | integer                  |           | not null | 
 product_date     | timestamp with time zone |           |          | 
 price            | numeric                  |           | not null | 
 discounted_price | numeric                  |           |          | 
Indexes:
    "product2_product_no_price_key" UNIQUE CONSTRAINT, btree (product_no, price)
Check constraints:
    "my_check_of_table" CHECK (discounted_price > 0::numeric AND price > discounted_price)
    "product2_price_check" CHECK (price > 0::numeric)

uxdb=# select * from products;
 product_no |         product_date          | price | discounted_price 
------------+-------------------------------+-------+------------------
          1 | 2020-06-03 15:22:50.559202+08 |     5 |                4
          2 | 2020-06-03 15:23:00.463404+08 |     5 |                4
          1 | 2020-06-03 15:23:13.143299+08 |     6 |                4
(3 rows)

uxdb=#
uxdb=# insert into product2 values (1,now(),5,4);
INSERT 0 1
uxdb=# insert into product2 values (2,now(),5,4);
INSERT 0 1
uxdb=# insert into product2 values (1,now(),6,4);
INSERT 0 1
uxdb=# insert into product2 values (1,now(),5,4);
ERROR:  duplicate key value violates unique constraint "product2_product_no_price_key"
DETAIL:  Key (product_no, price)=(1, 5) already exists.
uxdb=# insert into product2 values (NULL,now(),5,4);
ERROR:  null value in column "product_no" violates not-null constraint
DETAIL:  Failing row contains (null, 2020-06-03 15:43:42.861552+08, 5, 4).
uxdb=#

上面的这两个表的功能实际上差不多,但是主键就是主键(有自己独到的用处)!主键对于文档和客户端应用都是有用的。例如,一个允许修改行值的 GUI 应用可能需要知道一个表的主键,以便能唯一地标识行。如果定义了主键,数据库系统也有多种方法来利用主键。例如,主键定义了外键要引用的默认目标列。

OK,既然提及到了外键,那么下面就来看一下外键约束:

外键约束

外键约束一个外键约束是指:某一列(或一组列)中的值必须匹配出现在另一个表中某些行的值,然后我们称之为这维持了两个关联表之间的引用完整性。

背景如下:

1、产品表products:product_no字段为主键;以及两个检查约束my_check_of_table和products2_price_check
2、订单表orders:order_id字段为主键;以及外键约束orders_product_no_fkey

uxdb=# \d products
                           Table "public.products"
      Column      |           Type           | Collation | Nullable | Default 
------------------+--------------------------+-----------+----------+---------
 product_no       | integer                  |           | not null | 
 product_date     | timestamp with time zone |           |          | 
 price            | numeric                  |           |          | 
 discounted_price | numeric                  |           |          | 
Indexes:
    "products2_pkey" PRIMARY KEY, btree (product_no)
Check constraints:
    "my_check_of_table" CHECK (discounted_price > 0::numeric AND price > discounted_price)
    "products2_price_check" CHECK (price > 0::numeric)

uxdb=# CREATE TABLE orders (
uxdb(# order_id integer PRIMARY KEY,product_no integer REFERENCES products (product_no),quantity integer);
CREATE TABLE
uxdb=# \d orders
                 Table "public.orders"
   Column   |  Type   | Collation | Nullable | Default 
------------+---------+-----------+----------+---------
 order_id   | integer |           | not null | 
 product_no | integer |           |          | 
 quantity   | integer |           |          | 
Indexes:
    "orders_pkey" PRIMARY KEY, btree (order_id)
Foreign-key constraints:
    "orders_product_no_fkey" FOREIGN KEY (product_no) REFERENCES products(product_no)

uxdb=# 

此时,订单表是引用表而产品表是被引用表。于是现在就不可能创建出包含不存在于产品表中的product_no值(非空)的订单。

uxdb=# insert into products values (1,now(),6,4);
INSERT 0 1
uxdb=# insert into products values (2,now(),6,4);
INSERT 0 1
uxdb=# insert into products values (3,now(),5,4);
INSERT 0 1
uxdb=# select * from  products;
 product_no |         product_date          | price | discounted_price 
------------+-------------------------------+-------+------------------
          1 | 2020-06-03 16:02:01.577278+08 |     6 |                4
          2 | 2020-06-03 16:02:07.515237+08 |     6 |                4
          3 | 2020-06-03 16:02:15.097843+08 |     5 |                4
(3 rows)

uxdb=# insert into orders values (101,2,4);
INSERT 0 1
uxdb=# select * from orders ;
 order_id | product_no | quantity 
----------+------------+----------
      101 |          2 |        4
(1 row)

uxdb=# insert into orders values (101,8,4);
ERROR:  duplicate key value violates unique constraint "orders_pkey"
DETAIL:  Key (order_id)=(101) already exists.
uxdb=# insert into orders values (201,8,4);
ERROR:  insert or update on table "orders" violates foreign key constraint "orders_product_no_fkey"
DETAIL:  Key (product_no)=(8) is not present in table "products".
uxdb=#

如上,订单表第一个字段是主键,相同则违反了唯一约束;订单表第二个字段是外键(产品表的主键),产品表没有则违反了外键约束。

上面的订单表也是可以如下定义的:(原理:如果缺少列的列表,则被引用表的主键将被用作被引用列。

uxdb=# CREATE TABLE orders (
order_id integer PRIMARY KEY,product_no integer REFERENCES products,quantity integer);
CREATE TABLE
uxdb=# \d orders
                 Table "public.orders"
   Column   |  Type   | Collation | Nullable | Default 
------------+---------+-----------+----------+---------
 order_id   | integer |           | not null | 
 product_no | integer |           |          | 
 quantity   | integer |           |          | 
Indexes:
    "orders_pkey" PRIMARY KEY, btree (order_id)
Foreign-key constraints:
    "orders_product_no_fkey" FOREIGN KEY (product_no) REFERENCES products(product_no)

uxdb=#

当然和上面一样:一个外键也可以约束和引用一组列。同样地它需要被写成表约束的形式。下面是一个例子:


在此过程中(我要使用产品表的product_no和quantity字段),我遇到了一个问题:ERROR: there is no unique constraint matching given keys for referenced table "products"

uxdb=# CREATE TABLE products (
    product_no integer PRIMARY KEY,                                                                                                          
    product_date timestamptz,
    price numeric CHECK (price > 0),
    discounted_price numeric,quantity integer,
    constraint my_check_of_table CHECK (discounted_price > 0 AND price > discounted_price)
);
CREATE TABLE
uxdb=# CREATE TABLE orders (
order_id integer PRIMARY KEY,product_no integer unique,quantity integer,FOREIGN KEY (product_no,quantity) REFERENCES products(product_no,quantity));
ERROR:  there is no unique constraint matching given keys for referenced table "products"
uxdb=# CREATE TABLE orders (
order_id integer PRIMARY KEY,product_no integer,quantity integer,FOREIGN KEY (product_no,quantity) REFERENCES products(product_no,quantity),UNIQUE(product_no,quantity));
ERROR:  there is no unique constraint matching given keys for referenced table "products"
uxdb=# 

解决方法如下:(为我要使用的产品表的这两个字段做一个 唯一约束)就是下面的UNIQUE(product_no,quantity),

uxdb=# CREATE TABLE products (
    product_no integer PRIMARY KEY,                                                                                                          
    product_date timestamptz,
    price numeric CHECK (price > 0),
    discounted_price numeric,quantity integer,UNIQUE(product_no,quantity),
    constraint my_check_of_table CHECK (discounted_price > 0 AND price > discounted_price)
);
CREATE TABLE
uxdb=# CREATE TABLE orders (
order_id integer PRIMARY KEY,product_no integer,quantity integer,FOREIGN KEY (product_no,quantity) REFERENCES products(product_no,quantity));
CREATE TABLE
uxdb=# \d products
                           Table "public.products"
      Column      |           Type           | Collation | Nullable | Default 
------------------+--------------------------+-----------+----------+---------
 product_no       | integer                  |           | not null | 
 product_date     | timestamp with time zone |           |          | 
 price            | numeric                  |           |          | 
 discounted_price | numeric                  |           |          | 
 quantity         | integer                  |           |          | 
Indexes:
    "products_pkey" PRIMARY KEY, btree (product_no)
    "products_product_no_quantity_key" UNIQUE CONSTRAINT, btree (product_no, quantity)
Check constraints:
    "my_check_of_table" CHECK (discounted_price > 0::numeric AND price > discounted_price)
    "products_price_check" CHECK (price > 0::numeric)
Referenced by:
    TABLE "orders" CONSTRAINT "orders_product_no_fkey" FOREIGN KEY (product_no, quantity) REFERENCES products(product_no, quantity)

uxdb=# \d orders
                 Table "public.orders"
   Column   |  Type   | Collation | Nullable | Default 
------------+---------+-----------+----------+---------
 order_id   | integer |           | not null | 
 product_no | integer |           |          | 
 quantity   | integer |           |          | 
Indexes:
    "orders_pkey" PRIMARY KEY, btree (order_id)
Foreign-key constraints:
    "orders_product_no_fkey" FOREIGN KEY (product_no, quantity) REFERENCES products(product_no, quantity)

uxdb=#

注:一个外键必须要么引用一个主键,要么引用一个唯一约束。这意味着被引用行总是有一个索引(一个基本的主键或唯一约束);所以检查一个引用行是否有一个匹配是高效的。因此从被引用表中DELETE一个行或者一个被引用字段UPDATE一列,都需要扫描一次引用表以便从行中匹配老的数值,给引用字段创建索引也是一个好主意。因为这个不是总是被需要,而且怎么去建立索引还有许多其他的选择,外键约束的声明不能在引用字段上自动生成一个索引。


当然,被约束列的数量和类型应该匹配被引用列的数量和类型。 按照前面的方式,我们可以为一个外键约束命名。

uxdb=# \d orders
                 Table "public.orders"
   Column   |  Type   | Collation | Nullable | Default 
------------+---------+-----------+----------+---------
 order_id   | integer |           | not null | 
 product_no | integer |           |          | 
 quantity   | integer |           |          | 
Indexes:
    "orders_pkey" PRIMARY KEY, btree (order_id)
Foreign-key constraints:
    "orders_product_no_fkey" FOREIGN KEY (product_no, quantity) REFERENCES products(product_no, quantity)

uxdb=# CREATE TABLE orders2 (
order_id integer PRIMARY KEY,product_no integer,quantity integer,CONSTRAINT myforeign_key FOREIGN KEY (product_no,quantity) REFERENCES products(product_no,quantity));
CREATE TABLE
uxdb=# \d orders2
                Table "public.orders2"
   Column   |  Type   | Collation | Nullable | Default 
------------+---------+-----------+----------+---------
 order_id   | integer |           | not null | 
 product_no | integer |           |          | 
 quantity   | integer |           |          | 
Indexes:
    "orders2_pkey" PRIMARY KEY, btree (order_id)
Foreign-key constraints:
    "myforeign_key" FOREIGN KEY (product_no, quantity) REFERENCES products(product_no, quantity)

uxdb=#

现在我们的外键约束的名字就不再是:引用表名_第一个字段名_fkey了。
注:我们先在这里提一下:我们大家都知道上面的产品表是被引用的一方,而订单表是引用一方。此时我们删除一下产品表会发生什么?(删不掉,有人正引用着呢

uxdb=# \d
         List of relations
 Schema |   Name   | Type  | Owner 
--------+----------+-------+-------
 public | orders   | table | uxdb
 public | orders2  | table | uxdb
 public | products | table | uxdb
(3 rows)

uxdb=# drop table products;
ERROR:  cannot drop table products because other objects depend on it
DETAIL:  constraint orders_product_no_fkey on table orders depends on table products
constraint myforeign_key on table orders2 depends on table products
HINT:  Use DROP ... CASCADE to drop the dependent objects too.
uxdb=# \d
         List of relations
 Schema |   Name   | Type  | Owner 
--------+----------+-------+-------
 public | orders   | table | uxdb
 public | orders2  | table | uxdb
 public | products | table | uxdb
(3 rows)

uxdb=#

一个表可以有不止一个的外键约束,这通常被用于实现表之间的多对多关系的问题场景。例如:我们有关于产品和订单的表,但我们现在希望一个订单能包含多种产品(这在上面的结构中是不允许的)。我们可以使用如下的表结构:

uxdb=# CREATE TABLE products (
    product_no integer PRIMARY KEY,                                                                                                          
    product_date timestamptz,
    price numeric CHECK (price > 0) 
);                                                                        
CREATE TABLE
uxdb=# CREATE TABLE orders (
order_id integer PRIMARY KEY,quantity integer);
CREATE TABLE
uxdb=# CREATE TABLE order_items (
product_no integer REFERENCES products,
order_id integer REFERENCES orders,
number int,PRIMARY KEY (product_no, order_id));
CREATE TABLE
uxdb=# \d products
                         Table "public.products"
    Column    |           Type           | Collation | Nullable | Default 
--------------+--------------------------+-----------+----------+---------
 product_no   | integer                  |           | not null | 
 product_date | timestamp with time zone |           |          | 
 price        | numeric                  |           |          | 
Indexes:
    "products_pkey" PRIMARY KEY, btree (product_no)
Check constraints:
    "products_price_check" CHECK (price > 0::numeric)
Referenced by:
    TABLE "order_items" CONSTRAINT "order_items_product_no_fkey" FOREIGN KEY (product_no) REFERENCES products(product_no)

uxdb=# \d orders
                Table "public.orders"
  Column  |  Type   | Collation | Nullable | Default 
----------+---------+-----------+----------+---------
 order_id | integer |           | not null | 
 quantity | integer |           |          | 
Indexes:
    "orders_pkey" PRIMARY KEY, btree (order_id)
Referenced by:
    TABLE "order_items" CONSTRAINT "order_items_order_id_fkey" FOREIGN KEY (order_id) REFERENCES orders(order_id)

uxdb=# \d order_items
              Table "public.order_items"
   Column   |  Type   | Collation | Nullable | Default 
------------+---------+-----------+----------+---------
 product_no | integer |           | not null | 
 order_id   | integer |           | not null | 
 number     | integer |           |          | 
Indexes:
    "order_items_pkey" PRIMARY KEY, btree (product_no, order_id)
Foreign-key constraints:
    "order_items_order_id_fkey" FOREIGN KEY (order_id) REFERENCES orders(order_id)
    "order_items_product_no_fkey" FOREIGN KEY (product_no) REFERENCES products(product_no)

uxdb=#

注:上面第三个表order_items里面 主键和外键之间有重叠。

我们上面也稍微的提了一下,引用表没有删除之前,被引用的表删不掉。同时我们也知道外键不允许创建与任何产品都不相关的订单。但如果一个产品在一个引用它的订单创建之后被移除会发生什么? SQL允许我们处理这种情况。我们或许会想到有下面几种可能:

  1. 不允许删除一个被引用的产品
  2. 可以删除,同时也删除引用产品的订单
uxdb=# insert into products values (1,now(),1);
INSERT 0 1
uxdb=# insert into products values (2,now(),2);
INSERT 0 1
uxdb=# insert into orders values (1,1);
INSERT 0 1
uxdb=# insert into orders values (2,2);
INSERT 0 1
uxdb=# insert into order_items values (1,1,3);
INSERT 0 1
uxdb=# insert into order_items values (1,2,3);
INSERT 0 1
uxdb=# select * from order_items ;
 product_no | order_id | number 
------------+----------+--------
          1 |        1 |      3
          1 |        2 |      3
(2 rows)

uxdb=# select * from orders;
 order_id | quantity 
----------+----------
        1 |        1
        2 |        2
(2 rows)

uxdb=# delete from orders where order_id=2;
ERROR:  update or delete on table "orders" violates foreign key constraint "order_items_order_id_fkey" on table "order_items"
DETAIL:  Key (order_id)=(2) is still referenced from table "order_items".
                                   ^
uxdb=# delete from products where product_no=1;
ERROR:  update or delete on table "products" violates foreign key constraint "order_items_product_no_fkey" on table "order_items"
DETAIL:  Key (product_no)=(1) is still referenced from table "order_items".
uxdb=# select * from products;
 product_no |         product_date          | price 
------------+-------------------------------+-------
          1 | 2020-06-03 17:16:07.20693+08  |     1
          2 | 2020-06-03 17:16:14.325581+08 |     2
(2 rows)

uxdb=#

但是如果我们希望是第二种方式(移除一个仍然被一个订单引用(通过order_items)的产品时,订单项也同时被移除),那么我们建表的方式如下:

uxdb=# CREATE TABLE products (
    product_no integer PRIMARY KEY,
    product_date timestamptz,
    price numeric CHECK (price > 0)
);
CREATE TABLE
uxdb=# CREATE TABLE orders (
order_id integer PRIMARY KEY,quantity integer);
CREATE TABLE
uxdb=# CREATE TABLE order_items (
product_no integer REFERENCES products ON DELETE RESTRICT,
order_id integer REFERENCES orders ON DELETE CASCADE,
number int,PRIMARY KEY (product_no, order_id));
CREATE TABLE
uxdb=#

OK,下面是现在表里面数据的依赖关系:

uxdb=# insert into products values (1,now(),1);
INSERT 0 1
uxdb=# insert into products values (2,now(),2);
INSERT 0 1
uxdb=# insert into orders values (1,1);
INSERT 0 1
uxdb=# insert into orders values (2,2);
INSERT 0 1
uxdb=# insert into order_items values (1,1,3);
INSERT 0 1
uxdb=# insert into order_items values (1,2,3);
INSERT 0 1
uxdb=# insert into order_items values (2,1,3);
INSERT 0 1
uxdb=# insert into order_items values (2,2,3);
INSERT 0 1
uxdb=# select * from order_items;
 product_no | order_id | number 
------------+----------+--------
          1 |        1 |      3
          1 |        2 |      3
          2 |        1 |      3
          2 |        2 |      3
(4 rows)

uxdb=#

在上面两种对于删除的规定之后,效果如下:

uxdb=# delete from products where product_no=1;
ERROR:  update or delete on table "products" violates foreign key constraint "order_items_product_no_fkey" on table "order_items"
DETAIL:  Key (product_no)=(1) is still referenced from table "order_items".
uxdb=# select * from order_items;
 product_no | order_id | number 
------------+----------+--------
          1 |        1 |      3
          1 |        2 |      3
          2 |        1 |      3
          2 |        2 |      3
(4 rows)

uxdb=# delete from orders where order_id=1;
DELETE 1
uxdb=# select * from order_items;
 product_no | order_id | number 
------------+----------+--------
          1 |        2 |      3
          2 |        2 |      3
(2 rows)

uxdb=# select * from orders;
 order_id | quantity 
----------+----------
        2 |        2
(1 row)

uxdb=# select * from products ;
 product_no |         product_date          | price 
------------+-------------------------------+-------
          1 | 2020-06-03 17:31:48.697017+08 |     1
          2 | 2020-06-03 17:31:56.225455+08 |     2
(2 rows)

uxdb=#

解释一下:(第一种是限制删除;第二种是级联删除
限制删除级联删除是两种最常见的选项。

这是数据库外键定义的一个可选项,用来设置当主键表中的被参考列的数据发生变化时,外键表中响应字段的变换规则的。
1、update 则是主键表中被参考字段的值更新,delete是指在主键表中删除一条记录:
2、on update 和 on delete 后面可以跟的词语有四个:
no action , set null , set default ,cascade

no action 表示不做任何操作
set null 表示在外键表中将相应字段设置为null
set default 表示设置为默认值
cascade 表示级联操作,就是说,如果主键表中被参考字段更新,外键表中也更新;主键表中的记录被删除,外键表中该行也相应删除

对于上面的on delete操作,再详细一点说明:

  • RESTRICT阻止删除一个被引用的行。
  • NO ACTION表示在约束被检察时如果有任何引用行存在,则会抛出一个错误,这是我们没有指定任何东西时的默认行为(这两种选择的本质不同在于NO ACTION允许检查被推迟到事务的最后,而RESTRICT则不会)。
  • CASCADE指定当一个被引用行被删除后,引用它的行也应该被自动删除。
  • SET NULL和SET DEFAULT。这些将导致在被引用行被删除后,引用行中的引用列被置为空值或它们的默认值。注意这些并不会是我们免于遵守任何约束。例如,如果一个动作指定了SET DEFAULT,但是默认值不满足外键约束,操作将会失败。

而与on delete操作对于的on update选项,它是在被引用字段修改(更新)的时候调用的,可用的动作也是一样的。在这种情况下,选择CASCADE的话就意味着被引用字段的更新后的值,应该被拷贝到引用行中。而如果一个引用行的任意引用字段为NULL,那么这个引用行不必满足外键约束。如果外键声明中添加了MATCH FULL,引用行只有在所有的引用字段都是NULL时,才能逃避满足约束(所以null和non-null值的混合肯定不能满足MATCH FULL约束)。如果你不想引用行能够避免满足外键约束,那么声明引用行为NOT NULL。

排除约束

排除约束 它保证如果使用指定的运算符在指定列或表达式上比较任意两行,至少其中一个运算符比较将返回 false 或 null。

示例如下:

uxdb=# CREATE TABLE test(ID INT PRIMARY KEY  NOT NULL,NAME char(40),age int,addr char(40),salary real,
EXCLUDE USING gist(NAME WITH =,age WITH <>));
ERROR:  data type character has no default operator class for access method "gist"
HINT:  You must specify an operator class for the index or define a default operator class for the data type.
uxdb=# create extension btree_gist ;
CREATE EXTENSION
uxdb=# CREATE TABLE test(ID INT PRIMARY KEY  NOT NULL,NAME char(40),age int,addr char(40),salary real,
EXCLUDE USING gist(NAME WITH =,age WITH <>));
CREATE TABLE
uxdb=# \d test
                   Table "public.test"
 Column |     Type      | Collation | Nullable | Default 
--------+---------------+-----------+----------+---------
 id     | integer       |           | not null | 
 name   | character(40) |           |          | 
 age    | integer       |           |          | 
 addr   | character(40) |           |          | 
 salary | real          |           |          | 
Indexes:
    "test_pkey" PRIMARY KEY, btree (id)
    "test_name_age_excl" EXCLUDE USING gist (name WITH =, age WITH <>)

uxdb=#

注:USING gist 是用于构建和执行的索引一种类型,需要安装插件。它定义了对纯标量数据类型的 EXCLUDE 约束。

解释一下上面的SQL语句:

如果满足 NAME 相同,AGE 不相同则不允许插入,否则允许插入。
其比较的结果是:如果整个表边式返回 true,则不允许插入,否则允许插入。

uxdb=# INSERT INTO test values(1, 'Paul', 32, 'California', 20000.00 );
INSERT 0 1
uxdb=# INSERT INTO test VALUES(2, 'Paul', 32, 'Texas', 20000.00 );
INSERT 0 1
uxdb=# INSERT INTO test VALUES(3, 'Paul', 23, 'Texas', 20000.00 );
ERROR:  conflicting key value violates exclusion constraint "test_name_age_excl"
DETAIL:  Key (name, age)=(Paul, 23) conflicts with existing key (name, age)=(Paul, 32).
uxdb=# INSERT INTO test VALUES(4, 'Allen', 42, 'California', 20000.00 );
INSERT 0 1
uxdb=# INSERT INTO test VALUES(5, 'tom', 42, 'California', 20000.00 );
INSERT 0 1
uxdb=#
uxdb=# select * from test;
 id |                   name                   | age |                   addr                   | salary 
----+------------------------------------------+-----+------------------------------------------+--------
  1 | Paul                                     |  32 | California                               |  20000
  2 | Paul                                     |  32 | Texas                                    |  20000
  4 | Allen                                    |  42 | California                               |  20000
  5 | tom                                      |  42 | California                               |  20000
(4 rows)

第二条数据:名字和年龄都相同,表达式为假 插入
第三条数据:名字相同但年龄不同,表达式为真 不可插入
第四条数据:名字和年龄都不同,表达式为假 插入
第五条数据:名字不同但年龄相同,表达式为假 插入

现在再看一下表结构:

uxdb=# \d test
                   Table "public.test"
 Column |     Type      | Collation | Nullable | Default 
--------+---------------+-----------+----------+---------
 id     | integer       |           | not null | 
 name   | character(40) |           |          | 
 age    | integer       |           |          | 
 addr   | character(40) |           |          | 
 salary | real          |           |          | 
Indexes:
    "test_pkey" PRIMARY KEY, btree (id)
    "test_name_age_excl" EXCLUDE USING gist (name WITH =, age WITH <>)

如上:添加一个排除约束会在约束声明里自动创建一个声明类型的索引。

猜你喜欢

转载自blog.csdn.net/weixin_43949535/article/details/106517464