首页 / PostgreSQL 入门教程 / 唯一、非空、检查与默认值约束

PostgreSQL 入门教程

唯一、非空、检查与默认值约束

本教程共 50 篇 · 第 37 篇 · 更新于 2026-07-31 · 约 12 分钟阅读

PostgreSQLPostgreSQL 入门教程约束UNIQUENOT NULLCHECKDEFAULT

37. 唯一、非空、检查与默认值约束

本节目标:掌握 UNIQUE、NOT NULL、CHECK、DEFAULT 四种列约束的写法,知道 NULL 在它们各自中的行为差异,会如何给约束命名、查看和删除,能在建表时给数据加上合理的校验。

约束(constraint)是加在表或列上的「规矩」,用来保证存进去的数据靠谱。第 36 章讲了主键和外键,这章补全四家常客:UNIQUENOT NULLCHECKDEFAULT。它们都能写在建表时,也能事后用 ALTER TABLE 调整。

Note

约束尽量下在数据库层,而不是只靠应用代码校验。因为数据可能从多处写入(脚本、别的程序、手动 psql),数据库层的约束是最后一道、也是最可靠的防线。

UNIQUE:值不能重复

UNIQUE 保证这一列(或这几列的组合)在表里没有两行相同。比如用户名、邮箱通常要唯一。

CREATE TABLE users (
    id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    username VARCHAR(50) NOT NULL UNIQUE,
    email VARCHAR(100) UNIQUE
);

也可以写成表级约束,多个列组合唯一时用这种:

CREATE TABLE users (
    id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    username VARCHAR(50) NOT NULL,
    email VARCHAR(100),
    UNIQUE (username, email)   -- 两列组合不重复即可
);

插入重复邮箱会报错:ERROR: duplicate key value violates unique constraint ...。和主键不同的是:

  • 一张表可以有多个 UNIQUE,但只能有一个主键;
  • UNIQUE允许有多个 NULL(在 SQL 里,NULL 互不相等,所以不算重复)。

UNIQUE 时 PostgreSQL 会自动建一个唯一索引,所以按这列查重也很快。

-- 多列唯一:允许 (张三, 138...) 和 (张三, 139...) 共存,但不允许完全相同
ALTER TABLE users ADD CONSTRAINT uq_user_contact UNIQUE (username, email);

NOT NULL:不许为空

NULL 表示「未知/缺失」。NOT NULL 强制这一列必须有值。

CREATE TABLE users (
    id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    username VARCHAR(50) NOT NULL,
    age INT,
    created_at DATE
);

这里 agecreated_at 没写 NOT NULL,所以允许为空;username 必须填。试图插入 NULLusername 会直接报错。注意:默认情况下列是允许 NULL 的,要禁空就得显式写 NOT NULL

给已有列追加非空约束:

ALTER TABLE users ALTER COLUMN age SET NOT NULL;
Warning

如果该列已经存在 NULL 值,直接 SET NOT NULL 会失败。得先把 NULL 改成具体值,再加约束。反过来,去掉非空用 ALTER TABLE users ALTER COLUMN age DROP NOT NULL;

CHECK:满足条件才让进

CHECK 用一个布尔表达式给数据「立规矩」,只有表达式为真(或结果为 NULL)才允许插入/更新。

CREATE TABLE users (
    id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    username VARCHAR(50) NOT NULL,
    age INT CHECK (age >= 0 AND age < 150)
);

插入 age = -5 会报错,违反检查约束。表达式可以很灵活,还能调用函数:

-- 用户名至少 3 个字符(去空格后)
ALTER TABLE users
ADD CONSTRAINT username_len_check
CHECK (LENGTH(TRIM(username)) >= 3);

多列联合校验也行,比如「结束日期不能早于开始日期」:

CREATE TABLE projects (
    id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    name TEXT NOT NULL,
    start_date DATE,
    end_date DATE
);

ALTER TABLE projects
ADD CONSTRAINT dates_check
CHECK (end_date >= start_date);
Warning

注意 CHECKNULL 的坑:表达式结果为 NULL 时不算违规,允许通过(因为约束只拒绝「结果为假」的行,而 NULL 既不是真也不是假)。如果希望「空也不行」,要把该列同时设为 NOT NULL,否则带 NULL 的行会绕过 CHECK。

DEFAULT:没填就用默认值

DEFAULT 给列一个默认值。插入时没指定这一列,PostgreSQL 就用它。

CREATE TABLE users (
    id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    username VARCHAR(50) NOT NULL,
    age INT DEFAULT 18,
    created_at DATE DEFAULT CURRENT_DATE
);

插入 INSERT INTO users(username) VALUES ('Tom'); 时,age 自动变 18,created_at 变成当天。默认值可以是字面量,也可以是表达式(如 CURRENT_DATEnow()gen_random_uuid())。

既想用默认值又想在 INSERT 里显式占位,可以写 DEFAULT 关键字:

INSERT INTO users(username, age) VALUES ('Tom', DEFAULT);

事后改默认值、删默认值:

ALTER TABLE users ALTER COLUMN age SET DEFAULT 20;
ALTER TABLE users ALTER COLUMN age DROP DEFAULT;
Note

DEFAULT 只在「没指定这列」时生效。如果显式插入 NULL,那么存进去的就是 NULL,默认值不会兜底。DEFAULT 管的是「省略」,不是「填空」。想让 NULL 也变成默认值,得配合 NOT NULL 或在写入前用 COALESCE 处理。

给约束命名与查看

写约束时最好用 CONSTRAINT 名字 ... 显式起名,方便以后维护。系统自动命名的格式一般是 表_列_约束类型,比如 users_age_check,可读性差且难记。

查看一张表上有哪些约束,用 psql 的 \d

postgres=# \d users

输出里会列出 CHECKUNIQUENOT NULL 等约束及它们的名字。删除约束统一用「按名删除」:

ALTER TABLE users DROP CONSTRAINT username_len_check;
Tip

想一次性看全库约束,可以查系统视图:SELECT constraint_name, table_name, constraint_type FROM information_schema.table_constraints; 排查「为什么插不进数据」时很好用。

多种约束可以叠加

一列上可以同时挂多个约束,顺序无所谓:

CREATE TABLE users (
    id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    username VARCHAR(50) NOT NULL UNIQUE CHECK (LENGTH(username) > 0),
    age INT NOT NULL CHECK (age >= 0) DEFAULT 18
);

NOT NULLUNIQUECHECKDEFAULT 各管一摊,配合起来就能把数据规矩定得很细。再叠加上一章的主键、外键,一张表的数据质量就能从库层面兜住。

四种约束行为对比:

约束是否允许 NULL主要作用
NOT NULL不允许必须填值
UNIQUE允许多个 NULL值不重复
CHECK表达式 NULL 时放行满足条件才让进
DEFAULT省略时填默认值

常见错误

  1. 以为 UNIQUE 会拦 NULL 重复——其实 NULL 不算重复,允许多个。
  2. 给已有 NULL 的列加 NOT NULLCHECK 失败,忘了先清数据。
  3. 以为 DEFAULT 能兜住显式写入的 NULL——它只管「省略列」的情况。
  4. 以为 CHECK 会自动拒绝 NULL——表达式结果为 NULL 时约束放行。

约束对写入性能的影响

每加一道约束,写入(INSERT/UPDATE)时数据库就多一道检查,所以约束不是越多越好:

  • NOT NULLCHECK:开销很小,基本可忽略。
  • UNIQUE:写入时要维护唯一索引,数据量大时插入会变慢一点。
  • DEFAULT:几乎无成本,只在省略列时算一次。
  • 外键(第 36 章):写入子表时要去父表核对,建议在外键列上建索引。
Tip

对高频写入的表,核心约束(NOT NULL、CHECK、关键 UNIQUE)该上加上;一些「锦上添花」的 CHECK 可以放到应用层,或只在入库前批次校验,平衡可靠性与性能。

用 SQL 查看约束明细

除了 psql 的 \d,也能直接查系统视图拿到约束定义,方便写脚本或排查:

SELECT conname AS 约束名,
       pg_get_constraintdef(oid) AS 定义
FROM pg_constraint
WHERE conrelid = 'users'::regclass;

pg_get_constraintdef 会把约束还原成建表时的定义文本,比 \d 更适合程序化处理。想看全库的,去掉 WHERE 条件即可。

约束命名的小建议

起名用「表_列_类型」这种可读格式,比如 users_email_uniqueorders_amount_check。别用系统自动生成的 users_email_key 这类含糊名字——出问题时你根本想不起它管什么。好名字能让你删约束、查问题省力一半,也是多人协作时少踩坑的基本功。

小结

四种列约束各管一件事:UNIQUE 防重复(允许多个 NULL)、NOT NULL 禁空、CHECK 按布尔条件校验(NULL 会放行)、DEFAULT 在省略列时填空值。建表时写进列定义,事后用 ALTER TABLE 增删;给约束起个好名字,再用 \d 表名 或系统视图就能随时查看。把约束下在数据库层,比在代码里校验更可靠。