PostgreSQL 18 - 时间约束 (Temporal Constraints)

PostgreSQL 18 引入了一项强大的新功能,允许你在时间段内强制执行唯一性:时间约束(Temporal Constraints) 。通过这一新增功能,你现在可以在定义 UNIQUEPRIMARY KEYFOREIGN KEY 约束时,利用日期范围(Date Range)或时间戳范围(Timestamp Range)字段,比以往更容易地防止数据重叠。

问题所在:基于时间的唯一性

在最新的这个版本中,PostgreSQL 增加了一种更灵活的方式来定义 UNIQUEPRIMARY KEYFOREIGN KEY 约束。我将把示例重点放在 UNIQUE 约束上,因为这在我看来是时间约束最有用的场景。

让我们从一个现实世界的场景开始:管理用户订阅。

设置示例

首先,我们有一个简单的 users 表,包含 2 行数据:

sql 复制代码
CREATE TABLE users (
  id uuid DEFAULT uuidv7() PRIMARY KEY,
  email VARCHAR(100) UNIQUE NOT NULL
);

INSERT INTO users (email) VALUES 
('darth@example.com'),
('luke@example.com');

使用 WITHOUT OVERLAPS 的时间约束

现在我想创建一个属于 users 表的 subscriptions(订阅)表。每个订阅都有开始和结束日期,我想确保每个用户的订阅是唯一的。用户可以有过去的订阅(也许他们升级了层级),但在任何给定的时间点只能有一个活跃的订阅。

以下是我们如何使用新的 WITHOUT OVERLAPS 语法来强制执行此操作:

sql 复制代码
CREATE TABLE subscriptions (
  user_id uuid NOT NULL,
  type VARCHAR(50) NOT NULL,
  valid_period daterange NOT NULL,
  FOREIGN KEY (user_id) REFERENCES users(id),
  UNIQUE (user_id, valid_period WITHOUT OVERLAPS)
);

如你所见,我们在 valid_period 这个 daterange 字段的 UNIQUE 约束中使用了 WITHOUT OVERLAPS。时间约束要求键列(valid_period)必须是范围类型(range type),因此通常你会使用 daterangetimestamprange,但它也适用于其他范围类型。

测试一下

让我们插入一些数据:

sql 复制代码
INSERT INTO subscriptions (user_id, type, valid_period)
SELECT u.id, 'basic', daterange('2024-01-01', '2025-01-01', '[)')
FROM users u;

INSERT INTO subscriptions (user_id, type, valid_period)
SELECT u.id, 'premium', daterange('2025-01-01', '2026-01-01', '[)')
FROM users u
WHERE u.email = 'darth@example.com';

SELECT * FROM subscriptions;

结果如下:

text 复制代码
               user_id                |  type   |      valid_period
--------------------------------------+---------+-------------------------
 0199f293-291a-70bf-b9ee-872247723d29 | basic   | [2024-01-01,2025-01-01)
 0199f293-291b-737a-9bd0-e0e0853e3377 | basic   | [2024-01-01,2025-01-01)
 0199f293-291a-70bf-b9ee-872247723d29 | premium | [2025-01-01,2026-01-01)

在这个例子中,Luke 显然在 2025 年停止了付费,而 Darth 升级到了高级版。我通常习惯使用单独的列来表示开始和结束日期,但我很欣赏范围类型(Ranges)能够启用此功能,并允许我们使用强大的范围运算符。

额外内容:查询活跃订阅

使用"冰淇淋筒"运算符(@>,即包含运算符),我们可以轻松找到特定日期有效的订阅:

sql 复制代码
SELECT * FROM subscriptions
WHERE valid_period @> '2025-01-01'::date;

这个查询会告诉我们哪些订阅在 2025 年 1 月 1 日是活跃的。

重要提示:GIST 与 B-Tree 索引

有一个陷阱:范围列使用 GIST 索引,而 UUID 列使用 B-Tree 索引。当我运行 CREATE TABLE subscriptions 命令时,我遇到了一个错误。为了解决这个问题,你需要启用以下扩展:

sql 复制代码
CREATE EXTENSION btree_gist;

了解更多

如果你想深入了解时间约束,请查看 PostgreSQL 文档中关于 WITHOUT OVERLAPS 的部分。

原文链接:hashrocket.com/blog/posts/...

作者:Vinicius Negrisolo

相关推荐
q***61412 小时前
从MySQL迁移到PostgreSQL的完整指南
数据库·mysql·postgresql
N***73852 小时前
SQL锁机制
java·数据库·sql
小羊在奋斗2 小时前
MySQL表的约束:从基础到核心(附场景+案例)
android·数据库·mysql
东南门吹雪2 小时前
PostgreSQL与MySQL的锁与隔离级别
mysql·postgresql·区块链
Wang's Blog2 小时前
MongoDB小课堂: 文档查询之匹配查询与比较操作符深度解析
数据库·mongodb
cookqq2 小时前
mongodb根据索引IXSCAN 查询记录流程
数据结构·数据库·sql·mongodb·nosql
p***32352 小时前
如何使用C#与SQL Server数据库进行交互
数据库·c#·交互
h***34633 小时前
Redis安装教程(Windows版本)
数据库·windows·redis
极客密码4 小时前
【已开源】Cursor AI 开发实战:小文件在线互传工具
开源·ai编程·cursor