暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

PostgreSQL 范围、空间唯一性约束

原创 necessary 2025-08-12
113

PostgreSQL 范围、空间唯一性约束

在 PostgreSQL 中,实现数据唯一性不仅限于 UNIQUEPRIMARY KEY,还可以使用 EXCLUDE 约束配合 GiST 索引来实现更灵活的唯一性检查。


1. UNIQUE 约束

作用

  • 保证某列(或多列组合)的值在表中不重复。
  • 可以有多个 UNIQUE 约束。
  • 允许列中出现多个 NULL(因为 NULL 被认为是“未知”,彼此不相等)。

测试语句

DROP TABLE IF EXISTS customers; postgres=# CREATE TABLE customers ( postgres(# customerid INT UNIQUE, postgres(# name TEXT postgres(# ); CREATE TABLE postgres=# postgres=# -- 插入测试数据 postgres=# INSERT INTO customers VALUES (1, 'Alice'); -- 成功 INSERT 0 1 postgres=# INSERT INTO customers VALUES (2, 'Bob'); -- 成功 INSERT 0 1 postgres=# INSERT INTO customers VALUES (1, 'Charlie'); -- 报错:违反唯一约束 ERROR: duplicate key value violates unique constraint "customers_customerid_key" DETAIL: Key (customerid)=(1) already exists. postgres=# postgres=# -- NULL 测试 postgres=# INSERT INTO customers VALUES (NULL, 'David'); -- 成功 INSERT 0 1 postgres=# INSERT INTO customers VALUES (NULL, 'Eve'); -- 成功(UNIQUE 允许多个 NULL)

特点总结
可在同一个表中定义多个。可接受多个 NULL。自动创建唯一 B-Tree 索引。

2. PRIMARY KEY 约束

作用
唯一标识表中每一行数据。每个表只能有一个 PRIMARY KEY。自动包含 NOT NULL 和唯一性约束。
测试语句

DROP TABLE IF EXISTS customers_pk; postgres=# CREATE TABLE customers_pk ( postgres(# customerid INT PRIMARY KEY, postgres(# name TEXT postgres(# ); CREATE TABLE postgres=# -- 插入测试数据 postgres=# INSERT INTO customers_pk VALUES (1, 'Alice'); -- 成功 INSERT 0 1 postgres=# INSERT INTO customers_pk VALUES (2, 'Bob'); -- 成功 INSERT 0 1 postgres=# INSERT INTO customers_pk VALUES (1, 'Charlie'); -- 报错:违反主键唯一性 ERROR: duplicate key value violates unique constraint "customers_pk_pkey" DETAIL: Key (customerid)=(1) already exists. postgres=# -- NULL 测试 postgres=# INSERT INTO customers_pk VALUES (NULL, 'David'); ERROR: null value in column "customerid" of relation "customers_pk" violates not-null constraint DETAIL: Failing row contains (null, David). -- 报错:主键列不允许 NULL

特点总结
每表只能有一个主键,可由一列或多列组合。自动 NOT NULL。自动创建唯一 B-Tree 索引。

3. EXCLUDE USING gist

作用
用 GiST 索引实现更灵活的唯一性检查,不仅能比较相等,还能比较空间重叠、范围重叠等复杂条件。常见于几何类型(point、box)、范围类型(int4range、tsrange 等)。

几何类型示例

postgres=# CREATE TABLE boxes ( postgres(# id SERIAL PRIMARY KEY, postgres(# position box, postgres(# EXCLUDE USING gist (position WITH &&) postgres(# ); CREATE TABLE postgres=# postgres=# -- 插入测试数据 postgres=# INSERT INTO boxes (position) VALUES (box(point(0,0), point(1,1))); -- 成功 INSERT 0 1 postgres=# INSERT INTO boxes (position) VALUES (box(point(2,2), point(3,3))); -- 成功 INSERT 0 1 postgres=# INSERT INTO boxes (position) VALUES (box(point(0.5,0.5), point(1.5,1.5))); ERROR: conflicting key value violates exclusion constraint "boxes_position_excl" DETAIL: Key ("position")=((1.5,1.5),(0.5,0.5)) conflicts with existing key ("position")=((1,1),(0,0)). -- 报错:两个矩形重叠

时间区间冲突检测示例

假设我们要做一个 会议室预约系统,要求同一个会议室不能在同一时间段被重复预约。我们可以用 EXCLUDE USING gist 来做时间范围的唯一性约束。满足以下三点要求
1、同一会议室 + confirmed 状态 → 不能有时间交叉。
2、不同会议室 → 不受影响。
3、status 不是 confirmed → 不受影响。

-- 1) GiST 索引支持等值比较 CREATE EXTENSION IF NOT EXISTS btree_gist; --GiST 索引默认能处理几何类型、范围类型的操作符(如 &&),本范例中加入= 的比较,所以增加btree_gist插件辅助。 -- 2) 表结构 drop table if exists reservations; CREATE TABLE reservations ( id serial PRIMARY KEY, room text NOT NULL, during tstzrange NOT NULL, status text NOT NULL DEFAULT 'pending', -- 同一 room 在 status='confirmed' 时,不允许 during 区间重叠 CONSTRAINT no_overlap_confirmed EXCLUDE USING gist ( room WITH =, -- 同一个会议室 during WITH && -- 时间范围有重叠 ) WHERE (status = 'confirmed') ); postgres=# -- ✅ 成功:第一次预约,会议室 A postgres=# INSERT INTO reservations (room, during, status) postgres-# VALUES ('A', '[2025-08-12 09:00,2025-08-12 10:00)', 'confirmed'); INSERT 0 1 postgres=# -- ❌ 失败:同一会议室 A,时间区间有交叉 postgres=# INSERT INTO reservations (room, during, status) postgres-# VALUES ('A', '[2025-08-12 09:30,2025-08-12 10:30)', 'confirmed'); ERROR: conflicting key value violates exclusion constraint "no_overlap_confirmed" DETAIL: Key (room, during)=(A, ["2025-08-12 09:30:00+08","2025-08-12 10:30:00+08")) conflicts with existing key (room, during)=(A, ["2025-08-12 09:00:00+08","2025-08-12 10:00:00+08")) postgres=# INSERT INTO reservations (room, during, status) postgres-# VALUES ('A', '[2025-08-12 10:00,2025-08-12 11:00)', 'confirmed'); INSERT 0 1 postgres=# postgres=# -- ✅ 成功:会议室 B,即使时间重叠也没问题 postgres=# INSERT INTO reservations (room, during, status) postgres-# VALUES ('B', '[2025-08-12 09:30,2025-08-12 10:30)', 'confirmed'); INSERT 0 1 postgres=# postgres=# -- ✅ 成功:会议室 A,但 status 不是 confirmed,不受限制 postgres=# INSERT INTO reservations (room, during, status) postgres-# VALUES ('A', '[2025-08-12 09:30,2025-08-12 10:30)', 'pending'); INSERT 0 1

小结
UNIQUE:适合需要唯一性但允许多个 NULL 的列。
PRIMARY KEY:适合作为表的主标识,不允许 NULL,只能有一个。
EXCLUDE USING gist:适合范围/空间/时间等需要复杂冲突检测的场景,比唯一约束灵活。

「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论