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

相关推荐
修己xj2 小时前
GoTab:打造属于你自己的个性化浏览器新标签页
开源
蝎子莱莱爱打怪7 小时前
XZLL-IM干货系列 03|消息 ID 设计:一个 UUID 搞不定的事,我用两个 ID 解决了
后端·面试·开源
冬奇Lab9 小时前
每日一个开源项目(第137篇):Penpot - 真正开源的设计协作工具,SVG 原生格式消灭设计-开发鸿沟
前端·开源·设计
冬奇Lab2 天前
每日一个开源项目(第135篇):codebase-memory-mcp - 给 AI Agent 一张代码库的知识图谱
人工智能·开源·llm
uniquejing2 天前
《每次 API 调用前扔掉 43% Token,我开源了一个 AI 提示词瘦身工具》
开源
倔强的石头_3 天前
《Kingbase护城河》——数据库存储空间全景探测与精细化瘦身实战
数据库
冬奇Lab3 天前
每日一个开源项目(第134篇):Zvec - 阿里开源的嵌入式向量数据库,向量搜索界的 SQLite
数据库·人工智能·llm
ClouGence4 天前
Oracle CDC 架构优化:从主库直连到 DataGuard 备库同步
数据库·后端·oracle
文心快码BaiduComate4 天前
Comate 搭载GLM-5.2:百万上下文,稳定支撑长程任务
前端·程序员·开源
无响应de神4 天前
三、用户与权限管理
数据库·mysql