periods

为 PERIODs 和 SYSTEM VERSIONING 提供标准 SQL 功能

概览

扩展包名版本分类许可证语言
periods1.2.3TIMEPostgreSQLC
ID扩展名BinLibLoadCreateTrustReloc模式
1030periods-
相关扩展btree_gist timescaledb_toolkit timescaledb timeseries temporal_tables emaj table_version pg_cron pg_partman

版本

类型仓库版本PG 大版本包名依赖
EXTPIGSTY1.2.31817161514periodsbtree_gist
RPMPGDG1.2.31817161514periods_$v-
DEBPGDG1.2.31817161514postgresql-$v-periods-
OS / PGPG18PG17PG16PG15PG14
el8.x86_64
el8.aarch64
el9.x86_64
el9.aarch64
el10.x86_64
el10.aarch64
d12.x86_64
PGDG 1.2.3
PGDG 1.2.3
PGDG 1.2.3
PGDG 1.2.3
PGDG 1.2.3
d12.aarch64
PGDG 1.2.3
PGDG 1.2.3
PGDG 1.2.3
PGDG 1.2.3
PGDG 1.2.3
d13.x86_64
PGDG 1.2.3
PGDG 1.2.3
PGDG 1.2.3
PGDG 1.2.3
PGDG 1.2.3
d13.aarch64
PGDG 1.2.3
PGDG 1.2.3
PGDG 1.2.3
PGDG 1.2.3
PGDG 1.2.3
u22.x86_64
u22.aarch64
PGDG 1.2.3
PGDG 1.2.3
PGDG 1.2.3
PGDG 1.2.3
PGDG 1.2.3
u24.x86_64
u24.aarch64
PGDG 1.2.3
PGDG 1.2.3
PGDG 1.2.3
PGDG 1.2.3
PGDG 1.2.3

构建

您可以使用 pig build 命令构建 periods 扩展的 RPM 包:

pig build pkg periods         # 构建 RPM 包

安装

您可以直接安装 periods 扩展包的预置二进制包,首先确保 PGDGPIGSTY 仓库已经添加并启用:

pig repo add pgsql -u          # 添加仓库并更新缓存

使用 pig 或者是 apt/yum/dnf 安装扩展:

pig install periods;          # 当前活跃 PG 版本安装
pig ext install -y periods -v 18  # PG 18
pig ext install -y periods -v 17  # PG 17
pig ext install -y periods -v 16  # PG 16
pig ext install -y periods -v 15  # PG 15
pig ext install -y periods -v 14  # PG 14
dnf install -y periods_18       # PG 18
dnf install -y periods_17       # PG 17
dnf install -y periods_16       # PG 16
dnf install -y periods_15       # PG 15
dnf install -y periods_14       # PG 14
apt install -y postgresql-18-periods   # PG 18
apt install -y postgresql-17-periods   # PG 17
apt install -y postgresql-16-periods   # PG 16
apt install -y postgresql-15-periods   # PG 15
apt install -y postgresql-14-periods   # PG 14

创建扩展

CREATE EXTENSION periods CASCADE;  -- 依赖: btree_gist

用法

periods: PostgreSQL 的时间段与系统版本控制

本扩展实现了 SQL:2016 标准(最初于 SQL:2011 引入)中关于时间段(Period)和 SYSTEM VERSIONING 系统版本控制的行为。

什么是时间段?

时间段是表上的一个定义,指定一个名称和两个列。时间段的值包含起始值,但不包含结束值。

-- 标准 SQL 语法
CREATE TABLE example (
    id bigint,
    start_date date,
    end_date date,
    PERIOD FOR validity (start_date, end_date)
);

由于扩展无法修改 PostgreSQL 的语法,我们通过函数、视图和触发器来尽可能模拟相同的行为:

CREATE TABLE example (
    id bigint,
    start_date date,
    end_date date
);
SELECT periods.add_period('example', 'validity', 'start_date', 'end_date');

定义时间段后,会约束两列:起始值必须严格小于结束值,且两列均不能为空。

唯一约束

时间段可以作为 PRIMARY KEYUNIQUE 约束的一部分:

CREATE TABLE example (
    id bigint,
    start_date date,
    end_date date
);
SELECT periods.add_period('example', 'validity', 'start_date', 'end_date');
SELECT periods.add_unique_key('example', ARRAY['id'], 'validity');

扩展会创建一个覆盖所有指定列和时间段两列的唯一约束,并通过 GiST 排他约束实现 WITHOUT OVERLAPS 语义。

外键

有了带时间段的唯一键,就可以创建指向它们的外键:

SELECT periods.add_foreign_key('example2', 'ARRAY[ex_id]', 'validity', 'example_id_validity');

部分操作

SQL 标准允许对时间段的一部分进行更新或删除,扩展通过 INSTEAD OF 触发器的视图来实现:

UPDATE example__for_portion_of_validity
SET ...,
    start_date = ...,
    end_date = ...
WHERE ...;

使用此功能前,表必须有主键。

谓词函数

SQL 标准定义了多种时间段谓词,以内联函数形式实现:

-- "t" 和 "u" 是具有时间段 "p" 和 "q" 的表
-- 两个时间段的底层列为 "s" 和 "e"

WHERE periods.contains(t.s, t.e, 42)            -- t.p CONTAINS 42
WHERE periods.contains(t.s, t.e, u.s, u.e)      -- t.p CONTAINS u.q
WHERE periods.equals(t.s, t.e, u.s, u.e)        -- t.p EQUALS u.q
WHERE periods.overlaps(t.s, t.e, u.s, u.e)      -- t.p OVERLAPS u.q
WHERE periods.precedes(t.s, t.e, u.s, u.e)      -- t.p PRECEDES u.q
WHERE periods.succeeds(t.s, t.e, u.s, u.e)      -- t.p SUCCEEDS u.q
WHERE periods.immediately_precedes(t.s, t.e, u.s, u.e)  -- t.p IMMEDIATELY PRECEDES u.q
WHERE periods.immediately_succeeds(t.s, t.e, u.s, u.e)  -- t.p IMMEDIATELY SUCCEEDS u.q

系统版本控制表

SYSTEM_TIME

如果时间段名为 SYSTEM_TIME,则适用特殊规则。列类型必须是 datetimestamp without time zonetimestamp with time zone,且用户不可修改。扩展使用触发器将起始列设为 transaction_timestamp(),结束列始终为 'infinity'

注意: 一般建议使用 timestamp with time zone,因为时区配置参数或夏令时变更可能导致历史记录失真。

CREATE TABLE example (
    id bigint PRIMARY KEY,
    value text
);
SELECT periods.add_system_time_period('example', 'row_start', 'row_end');

这些列无需预先存在——扩展会自动创建。

排除列

可以阻止某些列的更新触发 SYSTEM_TIME 值变化:

SELECT periods.add_system_time_period(
            'example',
            excluded_column_names => ARRAY['foo', 'bar']);

启用系统版本控制

SELECT periods.add_system_time_period('example', 'row_start', 'row_end');
SELECT periods.add_system_versioning('example');

系统会将所有变更记录到一张独立的历史表中。你也可以自行创建历史表(例如添加分区)并指定扩展使用它。

时态查询

SQL 标准扩展了 FROMJOIN 子句以支持时间点或时间范围查询,扩展通过内联函数实现:

SELECT * FROM t__as_of('...');                       -- FOR system_time AS OF '...'
SELECT * FROM t__from_to('...', '...');              -- FOR system_time FROM '...' TO '...'
SELECT * FROM t__between('...', '...');              -- FOR system_time BETWEEN '...' AND '...'
SELECT * FROM t__between_symmetric('...', '...');    -- FOR system_time BETWEEN SYMMETRIC '...' AND '...'

访问控制

历史表及辅助函数遵循基表的所有权和访问权限。历史数据为只读。如需清理旧数据,必须先暂停系统版本控制:

BEGIN;
SELECT periods.drop_system_versioning('t');
GRANT DELETE ON TABLE t TO CURRENT_USER;
DELETE FROM t_history WHERE system_time_end < now() - interval '1 year';
SELECT periods.add_system_versioning('t');
COMMIT;

最后修改 2026-03-14: update extension metadata (953cbd0)