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

PostgreSQL 入门教程

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

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

PostgreSQLPostgreSQL 入门教程约束UNIQUENOT NULLCHECKDEFAULT

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

约束(constraint)是加在表或列上的「规矩」,用来保证存进去的数据靠谱。第 36 章讲了主键和外键,这章补全四家常客:UNIQUE、NOT NULL、CHECK、DEFAULT。它们都能写在建表时,也能事后用 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
);

这里 age、created_at 没写 NOT NULL,所以允许为空;username 必须填。试图插入 NULL 到 username 会直接报错。注意:默认情况下列是允许 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

注意 CHECK 里 NULL 的坑:表达式结果为 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_DATE、now()、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

输出里会列出 CHECK、UNIQUE、NOT 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 NULL、UNIQUE、CHECK、DEFAULT 各管一摊,配合起来就能把数据规矩定得很细。再叠加上一章的主键、外键,一张表的数据质量就能从库层面兜住。

四种约束行为对比:

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

常见错误

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

约束对写入性能的影响

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

  • NOT NULL、CHECK:开销很小,基本可忽略。
  • 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_unique、orders_amount_check。别用系统自动生成的 users_email_key 这类含糊名字——出问题时你根本想不起它管什么。好名字能让你删约束、查问题省力一半,也是多人协作时少踩坑的基本功。

小结

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