首页 / SQLite 入门教程 / 列约束与表约束

SQLite 入门教程

列约束与表约束

本教程共 50 篇 · 第 11 篇 · 更新于 2026-07-31

sqlite约束PRIMARY KEYCHECK

11. 列约束与表约束

本节目标:学完本章你能区分列级约束和表级约束,会用 NOT NULL、UNIQUE、CHECK、PRIMARY KEY 给表立规矩,并知道什么约束只能写在列上、什么约束能跨多列。

上一章我们把表的”骨架”搭起来了,但骨架只是形状,还没有”规矩”。比如:用户名能不能为空?邮箱能不能重复?年龄能不能写负数?这些规矩在 SQLite 里靠**约束(constraint)**来表达。约束是建表时写在列定义或表定义里的规则,数据插入或更新时 SQLite 会自动检查,违规就拒绝并抛 SQLITE_CONSTRAINT 错误。约束的价值很简单:让脏数据在进门之前就被拦下,而不是等程序跑飞了才去查”怎么有空名字”。

两种摆放位置:列级 vs 表级

约束可以出现在两个地方,这是本章第一个要分清的概念:

  • 列级约束(column constraint):紧跟在某一列的声明类型后面,只管这一列。例如 name TEXT NOT NULL
  • 表级约束(table constraint):写在所有列定义之后、括号收尾之前,独立成行,可以管多列组合。例如 PRIMARY KEY(id, seq)UNIQUE(col_a, col_b)

绝大多数约束两种写法都行,但有例外:NOT NULL 只能写成列级约束;而需要跨多列唯一或跨多列作主键时,只能用表级约束。下面逐个看。

NOT NULL:这一列不许空

NOT NULL 规定该列必须有值,插入或更新时给 NULL 直接报错。回想第 09 章:SQLite 的列默认都允许 NULL,所以”必填”必须显式声明。

CREATE TABLE users(
  id    INTEGER PRIMARY KEY,
  name  TEXT NOT NULL,
  age   INTEGER,
  email TEXT
);

如果试图塞一个空名字:

sqlite> INSERT INTO users(name) VALUES(NULL);
Error: NOT NULL constraint failed: users.name
Note

重点提示:标准 SQL 里主键列应当隐含 NOT NULL,但 SQLite 出于历史兼容,允许普通主键列出现 NULL(除非是 INTEGER PRIMARY KEYWITHOUT ROWID 表)。所以别依赖隐含规则,该写 NOT NULL 就写,规矩写清楚最稳。

PRIMARY KEY:行的唯一身份证

主键(primary key)是唯一标识一行数据的列(或列组合)。一张表有且只有一个主键。它有两个含义:值不能重复、且通常不可空。

单列的写法(列级):

CREATE TABLE users(
  id   INTEGER PRIMARY KEY,
  name TEXT NOT NULL
);

如果主键由多列组成(比如”用户 id + 序号”才能唯一确定一行),就必须用表级写法:

CREATE TABLE order_items(
  user_id INTEGER,
  seq     INTEGER,
  note    TEXT,
  PRIMARY KEY(user_id, seq)
);

这里 (user_id, seq) 合起来才是主键,单独一个 user_id 重复没关系。注意:关于 INTEGER PRIMARY KEY 为什么特殊、为什么它自带”自增”——留到第 12 章专门讲,那是 SQLite 最容易被误解的点之一。

UNIQUE:值不能重复,但能多个空

UNIQUE 约束保证某列(或某几列组合)的值彼此不重复。典型用途:邮箱、手机号、用户名这类”业务上唯一”的字段。

单列唯一(列级):

CREATE TABLE users(
  id    INTEGER PRIMARY KEY,
  name  TEXT NOT NULL,
  email TEXT UNIQUE
);

重复插入同一个邮箱会被拦:

sqlite> INSERT INTO users(name, email) VALUES('张三','a@b.com');
sqlite> INSERT INTO users(name, email) VALUES('李四','a@b.com');
Error: UNIQUE constraint failed: users.email

多列组合唯一(表级):

CREATE TABLE seats(
  id      INTEGER PRIMARY KEY,
  row     TEXT,
  col     TEXT,
  UNIQUE(row, col)
);

这样 (row, col) 这一对不能重复,相当于”一个座位只能卖一次票”。

Tip

实用技巧:UNIQUE 和主键的区别在于——一张表只能有一个主键,但可以有多个 UNIQUE 列;并且 UNIQUE 允许出现多个 NULL(SQLite 认为两个 NULL 互不相等),而主键列不允许 NULL。所以”允许空、但不允许重复非空值”的场景,用 UNIQUE 而不是主键。

CHECK:自定义的业务校验

CHECK 是最灵活的约束:你写一个表达式,插入/更新时只要表达式结果为”非 0 且非 NULL”就通过,结果为 0(假)就拒绝。它用来表达”年龄必须 ≥ 0""折扣不能高于原价”这类业务规则。

列级写法,要求电话至少 10 个字符:

CREATE TABLE contacts(
  id    INTEGER PRIMARY KEY,
  name  TEXT NOT NULL,
  phone TEXT NOT NULL CHECK(length(phone) >= 10)
);

表级写法,要求折扣不超过原价且两者都非负:

CREATE TABLE products(
  id         INTEGER PRIMARY KEY,
  list_price REAL NOT NULL,
  discount   REAL NOT NULL DEFAULT 0,
  CHECK(list_price >= discount AND discount >= 0 AND list_price >= 0)
);

插入一个折扣高于原价的商品会被拦:

sqlite> INSERT INTO products(list_price, discount) VALUES(900, 1000);
Error: CHECK constraint failed: products
Note

重点提示:CHECK 表达式里不能写子查询(subquery),只能用当前行的列和函数做判断。另外 SQLite 不支持用 ALTER TABLE 给已存在的表直接追加表级 CHECK 约束,要加这类约束通常只能新建表、搬数据、再改名(这是 ALTER TABLE 的局限,后续章节会讲)。

约束还能起名字(CONSTRAINT 关键字)

当报错信息只说 UNIQUE constraint failed: users.email 时其实够用了,但大型项目里你可能想给约束起个名字,方便维护和定位。用 CONSTRAINT 名字 前缀即可,表级和列级都支持:

CREATE TABLE users(
  id    INTEGER PRIMARY KEY,
  email TEXT,
  CONSTRAINT uq_users_email UNIQUE(email)
);

起名后报错会带上 uq_users_email,一眼知道是哪条规矩炸了。

约束失败会怎样

所有约束违规的结果是一致的:SQLite 中止当前这条语句(默认是 ABORT 策略),什么也不写入,并返回 SQLITE_CONSTRAINT 类错误。换句话说,约束是”原子级守门员”——一条脏数据想进门,整条插入/更新直接作废,不会只存一半。这对保持数据干净非常关键。

把四类约束组合到一张真实表

学完单个约束,来看它们如何在一张表里”合体”。给 users 同时上规矩:主键 id、名字必填、邮箱唯一且必填、年龄非负:

CREATE TABLE users(
  id    INTEGER PRIMARY KEY,
  name  TEXT NOT NULL,
  age   INTEGER CHECK(age >= 0),
  email TEXT NOT NULL UNIQUE
);

插入一条合法数据没问题;下面几种都会当场被拦:

sqlite> INSERT INTO users(name, age, email) VALUES('张三', -5, 'a@b.com');
Error: CHECK constraint failed: users
sqlite> INSERT INTO users(name, email) VALUES('李四', 'a@b.com');
Error: UNIQUE constraint failed: users.email
sqlite> INSERT INTO users(id, email) VALUES(1, 'b@b.com');
Error: NOT NULL constraint failed: users.name

注意不同约束的报错信息会点名是哪一种(CHECK / UNIQUE / NOT NULL),方便你定位是哪条规矩炸了。一张表可以同时挂多个 UNIQUE、多个 CHECK,但主键只有一个——这正是”身份证只能有一张,但手机号、邮箱可以各自唯一”的现实映射。

类比小结

把约束想成”收纳柜的使用须知”:NOT NULL 是”这个格子必须放东西,不许空”;PRIMARY KEY 是”每件物品贴的唯一条形码,扫得出身份”;UNIQUE 是”这类标签全柜只能出现一次”;CHECK 是”不符合规格(比如太短、负数)直接拒收”。列级须知贴在单个格子上,表级须知贴在柜门上管”几个格子组合”的规矩。规矩立得早,后面查数据才省心——这正是”约束前置”的价值。

Warning

常见坑:约束只在”插入/更新时”检查,对已经存在的旧脏数据不会自动清洗。如果一张老表原本没加 NOT NULL,后来你 ALTER TABLE 加约束,表里既有的 NULL 行并不会被自动改掉——新插入才会被拦。迁移老数据时记得先手工清理历史脏数据。