约束

159
2023/07/31 14:54:20

本章将讨论 PostgreSQL 的约束,包括:主键约束、外键约束、唯一约束、非空约束、 CHECK 约束。

非空约束

在数据库理论中,NULL 代表未知或信息缺失。NULL零值(空字符串、数字0等)不是一个概念。

NULL 很特别,它不等于任何东西,包括它自己。NULL = NULL 的结果是 NULL,而不是你期望的TRUE。要检查是否为 NULL,需要使用 IS NULLIS NO NULL,而不是 =<>

非空约束是指,限定某个字段的值不能为 NULL

CREATE TABLE 表名(
   ...
   字段名 数据类型 NOT NULL,
   ...
);

在创建表的时候,如果要给某个字段指定非空约束,可以参照上面的语法。

如果要给已存在的字段指定非空约束,可以使用 ALTER TABLE ... ALTER COLUMN ... SET NOT NULL 语法:

ALTER TABLE 表名
ALTER COLUMN 字段名 SET NOT NULL;

唯一约束

如果要限定某个字段的值是唯一的,可以使用唯一约束 UNIQUE

CREATE TABLE 表名(
	字段名 数据类型 UNQIUE
);

也可以在表约束中使用:

CREATE TABLE 表名(
	字段名 数据类型,
    UNIQUE(字段名)
);

表约束中使用的场景更多的是多个字段组成的唯一约束:

CREATE TABLE 表名(
	字段1 数据类型,
    字段2 数据类型,
    UNIQUE(字段1, 字段2)
);

NULL 是个【魔鬼】(高级篇中会进行详解):对于唯一索引而言,如果字段允许NULL(没加 NOT NULL非空约束),那么,多个NULL并不违反唯一约束,也就就是说,你可以给允许NULL的唯一约束的字段上,插入N个NULL值。原因在于 NULL不等于任何东西,包括它自己。

主键约束

使用 PRIMARY KEY 给字段加主键约束:

  • 每张表可以没有主键约束,但最多只能有一个主键约束
  • 主键约束自动具备了非空约束(NOT NULL)和唯一约束(UNQIUE

你可以在字段后面加上主键约束:

也可以在表约束里指定:

CREATE TABLE 表名(
	字段名 数据类型,
    PRIMARY KEY(字段名)
);

对于表约束,更多的是指定复合约束:

CREATE TABLE 表名(
	字段1 数据类型,
    字段2 数据类型,
    PRIMARY KEY(字段1, 字段2)
);
  • 主键约束不可能出现 NULL 值。在大部分数据库中,如果你在INSERT时给一个自动编号的主键设置NULL,结果是自动编号的值。
  • 你可以给多个字段同时设置非空和唯一约束
  • 虽然主键约束具备非空和唯一约束的特性,但一张表中只能有一个主键约束(一张表中,只能有一个PRIMARY KEY约束,哪怕它的行为和 NOT NULL + UNQIUE类似)

CHECK 约束

PostgreSQL 的 CHECK 约束是非常强大的,对比一下 MySQL 的 CHECK 约束,你会觉得 MySQL 简直弱鸡一只。

  • CHECK 约束,允许你指定字段中的值是否必须满足特定条件
  • CHECK约束,在插入或修改值之前,会对值进行评估,如果满足条件则对值进行写入;否则报错

示例:

DROP TABLE IF EXISTS employees;
CREATE TABLE employees (
	id SERIAL PRIMARY KEY,
	first_name VARCHAR (50),
	last_name VARCHAR (50),
	birth_date DATE CHECK (birth_date > '1900-01-01'),
	joined_date DATE CHECK (joined_date > birth_date),
	salary numeric CHECK(salary > 0)
);

这个示例中有三个 CHECK 约束:

  • birth_date的约束:限定生日必须在 1900-01-01之后的日期
  • joined_date的约束:限定入职时间必须大于其生日
  • salary的约束:限定薪资必须大于0

为已存在的字段加上 CHECK 约束:

ALTER TABLE 表名
ADD CONSTRAINT 字段名
CHECK (条件);

非空约束其实可以看作 CHECK 约束的一种:

外键约束

语法:

[CONSTRAINT fk_name]
   FOREIGN KEY(fk_columns) 
   REFERENCES parent_table(parent_key_columns)
   [ON DELETE delete_action]
   [ON UPDATE update_action]

其中,action的取值可能是:

  • SET NULL :设置为 NULL值 ——前提是,该字段没有 NOT NULLPRIMARY KEY 约束
  • SET DEFAULT:设置为默认值
  • RESTRICT:限制操作
  • NO ACTION:无任何操作
  • CASCADE :联动操作

包括阿里在内的诸多互联网巨头都提出不要使用外键约束,而是由程序编码来处理数据,原因:

  • 外键约束会增加额外的系统开销
  • 某些时候,外键约束会引入NULL值,而大部分互联网项目中NULL 值是不被允许
  • 在分布式系统中,可能根本没办法使用外键约束,因为每个微服务(包括它对应的数据库)都是独立的

特别说明

CREATE TABLE tb1(
	c1 INTEGER  UNQIUE,
    c2 INTEGER  UNQIUE,
);

CREATE TABLE tb2(
	c1 INTEGER  ,
    c2 INTEGER ,
    UNIQUE(c1, c2)
);

含义是不一样的。前者表示 c1c2分别是唯一约束;后者表示c1c2 一起组成唯一索引。

-- 对于tb1
INSERT INTO tb1(c1,c2) VALUES
	(1, 2),
	(1, 3), -- 错误
	(2, 2); -- 错误

-- 对于tb2
INSERT INTO tb2(c1,c2) VALUES
	(1, 2),
	(1, 3), -- 正确
	(2, 2); -- 正确