From harness-claude
Models temporal data with valid-time, transaction-time, and bitemporal dimensions in PostgreSQL using range types and exclusion constraints. Enables time-travel queries for audits, compliance, price histories, and HR reporting.
npx claudepluginhub intense-visions/harness-engineering --plugin harness-claudeThis skill uses the workspace's default tool permissions.
> Modeling when facts are true (valid-time), when they were recorded (transaction-time), or both (bitemporal), enabling time-travel queries and regulatory audit.
Provides DuckDB temporal versioning for interaction history with time-travel queries, frozen snapshots, causality tracking via vector clocks, and immutable audit logs.
Designs PostgreSQL-specific schemas with best practices for data types, constraints, indexing, partitioning, RLS, and performance patterns.
Designs PostgreSQL tables and schemas using best practices for data types, primary/foreign keys, constraints, indexes, normalization, JSONB, and partitioning.
Share bugs, ideas, or general feedback.
Modeling when facts are true (valid-time), when they were recorded (transaction-time), or both (bitemporal), enabling time-travel queries and regulatory audit.
Three temporal dimensions:
1. Valid-time (application time): When the fact is true in the real world. An employee salary might be effective 2024-01-01 to 2024-12-31. The database might record this fact on 2023-12-15 -- the valid time and the recording time differ.
2. Transaction-time (system time): When the row was stored in the database. Columns like recorded_at and superseded_at track the database's knowledge. Transaction-time columns are never manually edited -- the system manages them.
3. Bitemporal: Both dimensions combined. Answers questions like "what did the system believe was true about time T, as of database time S?" Essential for late-arriving corrections in financial systems.
Slowly Changing Dimensions (SCD) types:
| Type | Strategy | History? |
|---|---|---|
| Type 1 | Overwrite the row | No |
| Type 2 | New row with date range | Yes |
| Type 3 | Previous/current columns | Partial |
| Type 6 | Hybrid (Type 1 + 2 + 3) | Yes |
SCD Type 2 is the most common for temporal data. In PostgreSQL, use range types:
CREATE EXTENSION IF NOT EXISTS btree_gist;
CREATE TABLE employee_salaries (
id serial PRIMARY KEY,
employee_id int NOT NULL,
salary numeric(10,2) NOT NULL,
valid_range tstzrange NOT NULL,
EXCLUDE USING gist (
employee_id WITH =,
valid_range WITH &&
)
);
The exclusion constraint prevents overlapping valid periods for the same employee -- the database enforces temporal integrity, not the application.
Insurance policy pricing with valid-time tracking:
-- Initial price: $500/month effective Jan 1 to Dec 31
INSERT INTO policy_prices (policy_id, monthly_rate, valid_range)
VALUES (101, 500.00, '[2024-01-01, 2025-01-01)');
-- Price change mid-year: close the old range, insert new
UPDATE policy_prices
SET valid_range = '[2024-01-01, 2024-07-01)'
WHERE policy_id = 101 AND valid_range @> '2024-06-15'::timestamptz;
INSERT INTO policy_prices (policy_id, monthly_rate, valid_range)
VALUES (101, 550.00, '[2024-07-01, 2025-01-01)');
Point-in-time query -- "what was the price on June 15?":
SELECT monthly_rate FROM policy_prices
WHERE policy_id = 101
AND valid_range @> '2024-06-15'::timestamptz;
-- Returns: 500.00
Bitemporal query -- "what did we think the price was on June 15, before the July correction?":
SELECT monthly_rate FROM policy_prices
WHERE policy_id = 101
AND valid_range @> '2024-06-15'::timestamptz
AND recorded_at < '2024-07-01'::timestamptz;
This is essential when auditors need to reconstruct what the system reported at a specific point in database history.
updated_at as a substitute for temporal modeling. updated_at only records the last change -- all history is lost. It cannot answer "what was true at time T?"valid_from and valid_to columns require application-level overlap checks. Range types with exclusion constraints push this to the database.'[2024-07-01,)' -- the open upper bound means "still current" and works correctly with range operators.Range types (tstzrange, daterange) provide built-in temporal operators:
@> -- contains a timestamp: valid_range @> '2024-06-15'::timestamptz&& -- overlaps another range: range1 && range2-|- -- adjacent ranges: range1 -|- range2GiST index for range columns:
CREATE INDEX idx_prices_valid ON policy_prices USING gist (valid_range);
Exclusion constraints require the btree_gist extension:
CREATE EXTENSION btree_gist;
-- Prevents overlapping valid periods for the same policy
EXCLUDE USING gist (policy_id WITH =, valid_range WITH &&)
SQL:2011 temporal support is partially implemented in PostgreSQL. System-versioned tables are not yet native -- use triggers or the temporal_tables extension to approximate transaction-time tracking.
SQL:2011 standard temporal syntax (FOR SYSTEM_TIME AS OF) is supported in MariaDB and SQL Server but not yet in PostgreSQL. PostgreSQL approximates this with view-based patterns or the temporal_tables extension.
Gap detection in temporal data -- finding periods with no valid record:
SELECT upper(a.valid_range) AS gap_start, lower(b.valid_range) AS gap_end
FROM policy_prices a
JOIN policy_prices b ON a.policy_id = b.policy_id
AND upper(a.valid_range) < lower(b.valid_range)
WHERE NOT EXISTS (
SELECT 1 FROM policy_prices c
WHERE c.policy_id = a.policy_id
AND c.valid_range && tstzrange(upper(a.valid_range), lower(b.valid_range))
);
Temporal joins for combining two temporal tables:
SELECT e.name, s.salary, d.department_name
FROM employees e
JOIN employee_salaries s ON e.id = s.employee_id
JOIN employee_departments d ON e.id = d.employee_id
AND s.valid_range && d.valid_range;
Partitioning temporal tables by valid_from enables archival of old time periods and efficient pruning of historical queries.
MySQL lacks range types and exclusion constraints entirely. Temporal data in MySQL requires:
valid_from DATETIME and valid_to DATETIME columnsMySQL 8.0 does not support SQL:2011 temporal tables.
MariaDB 10.3+ is the notable exception -- it supports system-versioned tables (WITH SYSTEM VERSIONING), making it the only major open-source engine with native transaction-time temporal support. MariaDB's temporal tables automatically track row history without triggers.
Financial compliance system tracking 10M account balances. Regulators require reconstructing the account balance at any historical date. SCD Type 2 implementation using tstzrange with GiST index on (account_id, valid_range). Point-in-time queries ("what was balance on date X?") complete in 3ms. After an audit discovered late-arriving corrections (a deposit backdated to last month), the team added bitemporal columns to track both valid-time and transaction-time. This enabled the query "what did we report the balance was on March 15, before the April 2 correction?" -- critical for regulatory reconciliation.