temporal_tables
Overview
| Package | Version | Category | License | Language |
|---|---|---|---|---|
temporal_tables | 1.2.2 | TIME | BSD 2-Clause | C |
| ID | Extension | Bin | Lib | Load | Create | Trust | Reloc | Schema |
|---|---|---|---|---|---|---|---|---|
| 1040 | temporal_tables | No | Yes | No | Yes | No | Yes | - |
| Related | timescaledb_toolkit timescaledb timeseries periods emaj table_version pg_cron pg_partman |
|---|
no pg17 on el8/9 pgdg repo
Version
| Type | Repo | Version | PG Ver | Package | Deps |
|---|---|---|---|---|---|
| EXT | PIGSTY | 1.2.2 | 1817161514 | temporal_tables | - |
| RPM | PIGSTY | 1.2.2 | 1817161514 | temporal_tables_$v | - |
| DEB | PIGSTY | 1.2.2 | 1817161514 | postgresql-$v-temporal-tables | - |
Build
You can build the RPM / DEB packages for temporal_tables using pig build:
pig build pkg temporal_tables # build RPM / DEB packages
Install
You can install temporal_tables directly. First, make sure the PGDG and PIGSTY repositories are added and enabled:
pig repo add pgsql -u # Add repo and update cache
Install the extension using pig or apt/yum/dnf:
pig install temporal_tables; # Install for current active PG version
pig ext install -y temporal_tables -v 18 # PG 18
pig ext install -y temporal_tables -v 17 # PG 17
pig ext install -y temporal_tables -v 16 # PG 16
pig ext install -y temporal_tables -v 15 # PG 15
pig ext install -y temporal_tables -v 14 # PG 14
dnf install -y temporal_tables_18 # PG 18
dnf install -y temporal_tables_17 # PG 17
dnf install -y temporal_tables_16 # PG 16
dnf install -y temporal_tables_15 # PG 15
dnf install -y temporal_tables_14 # PG 14
apt install -y postgresql-18-temporal-tables # PG 18
apt install -y postgresql-17-temporal-tables # PG 17
apt install -y postgresql-16-temporal-tables # PG 16
apt install -y postgresql-15-temporal-tables # PG 15
apt install -y postgresql-14-temporal-tables # PG 14
Create Extension:
CREATE EXTENSION temporal_tables;
Usage
temporal_tables: System-period temporal tables for PostgreSQL
A temporal table is a table that records the period of time when a row is valid. The system period is a column (or a pair of columns) with a system-maintained value that contains the period of time when a row is valid from a database perspective. When you insert a row into such table, the system automatically generates the values for the start and end of the period. When you update or delete a row from a system-period temporal table, the old row is archived into another table, which is called the history table.
There is a fantastic tutorial on using and querying temporal tables in PostgreSQL with this extension.
Creating a System-Period Temporal Table
The extension uses a general trigger function to maintain system-period temporal table behaviour:
versioning(<system_period_column_name>, <history_table_name>, <adjust>)
First, create a table and add a system period column:
CREATE TABLE employees (
name text NOT NULL PRIMARY KEY,
department text,
salary numeric(20, 2)
);
ALTER TABLE employees ADD COLUMN sys_period tstzrange NOT NULL;
Then create a history table:
CREATE TABLE employees_history (LIKE employees);
A history table must contain a system period column with the same name and data type as in the original one. If both tables contain a column, the data type must be the same.
Finally, create a trigger to link it with the history table:
CREATE TRIGGER versioning_trigger
BEFORE INSERT OR UPDATE OR DELETE ON employees
FOR EACH ROW EXECUTE PROCEDURE versioning('sys_period',
'employees_history',
true);
Inserting Data
Inserting data into a system-period temporal table is similar to inserting data into a regular table:
INSERT INTO employees (name, department, salary)
VALUES ('Bernard Marx', 'Hatchery and Conditioning Centre', 10000);
INSERT INTO employees (name, department, salary)
VALUES ('Lenina Crowne', 'Hatchery and Conditioning Centre', 7000);
The start of sys_period column represents the time when the row became current, generated by CURRENT_TIMESTAMP.
Updating Data
When a user updates rows, the trigger inserts a copy of the old row into the history table. If a single transaction makes multiple updates to the same row, only one history row is generated:
UPDATE employees SET salary = 11200 WHERE name = 'Bernard Marx';
The history table now contains the previous version:
| name | department | salary | sys_period |
|---|---|---|---|
| Bernard Marx | Hatchery and Conditioning Centre | 10000 | [2006-08-08, 2007-02-27) |
Update Conflicts and Time Adjustment
Update conflicts can occur when multiple transactions update the same row. When the adjust parameter is set to true, the start of sys_period is adjusted by adding a small delta (typically 1 microsecond) to avoid failures with SQLSTATE 22000.
Deleting Data
When a user deletes data, the trigger adds rows to the history table:
DELETE FROM employees WHERE name = 'Helmholtz Watson';
Advanced Usage
You can set a custom system time for versioning triggers, useful for creating a data warehouse from a system that recorded timestamps:
SELECT set_system_time('1985-08-08 06:42:00+08');
To revert to the default behaviour:
SELECT set_system_time(NULL);
If issued within a transaction that is later aborted, all changes are undone. If committed, changes persist until the end of the session.
Examples and Hints
Using Inheritance for History Tables
CREATE TABLE employees_history (
name text NOT NULL,
department text,
salary numeric(20, 2),
sys_period tstzrange NOT NULL
);
CREATE TABLE employees (PRIMARY KEY(name)) INHERITS (employees_history);
Pruning History Tables
History tables are always growing. Several pruning strategies:
- Periodically delete old data from a history table.
- Use partitioning and detach old partitions from a history table.
- Retain only the latest N versions of a row.
- Prune rows when a corresponding row is deleted from the temporal table.
- Prune rows that satisfy specified business rules.
You can also set another tablespace for a history table to move it on cheaper storage.
Data Audit
You can add triggers to save the user that modified or deleted the current row:
CREATE FUNCTION employees_modify()
RETURNS TRIGGER AS $$
BEGIN
NEW.user_modified = SESSION_USER;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER employees_modify
BEFORE INSERT OR UPDATE ON employees
FOR EACH ROW EXECUTE PROCEDURE employees_modify();
CREATE FUNCTION employees_delete()
RETURNS TRIGGER AS $$
BEGIN
NEW.user_deleted = SESSION_USER;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER employees_delete
BEFORE INSERT ON employees_history
FOR EACH ROW EXECUTE PROCEDURE employees_delete();
Feedback
Was this page helpful?
Thanks for the feedback! Please let us know how we can improve.
Sorry to hear that. Please let us know how we can improve.