Working with Table Partitioning

1. Creating Partitioned Table

CREATE TABLE events (
   id         bigserial,
   tenant_id  int  NOT NULL,
   created_at timestamptz NOT NULL,
   payload    jsonb,
   PRIMARY KEY (id, created_at)
) PARTITION BY RANGE (created_at);

2. Using Range Partitioning

CREATE TABLE events_2025_01 PARTITION OF events
   FOR VALUES FROM ('2025-01-01') TO ('2025-02-01');

3. Using List Partitioning

CREATE TABLE orders (...) PARTITION BY LIST (region);
CREATE TABLE orders_us  PARTITION OF orders FOR VALUES IN ('US','CA');
CREATE TABLE orders_eu  PARTITION OF orders FOR VALUES IN ('DE','FR','GB');

4. Using Hash Partitioning

CREATE TABLE users (...) PARTITION BY HASH (id);
CREATE TABLE users_p0 PARTITION OF users FOR VALUES WITH (MODULUS 4, REMAINDER 0);
CREATE TABLE users_p1 PARTITION OF users FOR VALUES WITH (MODULUS 4, REMAINDER 1);
StrategyBest For
RANGETime-series, ordinal keys
LISTDiscrete categories (region, tenant)
HASHEven data spread, multitenancy

5. Creating Partitions

CREATE TABLE events_2025_02 PARTITION OF events
   FOR VALUES FROM ('2025-02-01') TO ('2025-03-01')
   TABLESPACE fast_ssd;

6. Setting Range Bounds

FOR VALUES FROM (MINVALUE) TO ('2024-01-01');
FOR VALUES FROM ('2025-01-01') TO (MAXVALUE);

7. Setting List Values

FOR VALUES IN ('a','b','c');

8. Creating Default Partition

CREATE TABLE events_default PARTITION OF events DEFAULT;
Warning: Adding a new partition requires scanning the default partition for misplaced rows; keep it empty if possible.

9. Attaching Existing Table

ALTER TABLE events ATTACH PARTITION events_2024
   FOR VALUES FROM ('2024-01-01') TO ('2025-01-01');

10. Detaching Partition

ALTER TABLE events DETACH PARTITION events_2023 CONCURRENTLY;
-- PG 14+ avoids ACCESS EXCLUSIVE on the parent

11. Dropping Partition

DROP TABLE events_2022;     -- after DETACH (fast purge of old data)

12. Creating Indexes on Partitioned Tables

CREATE INDEX events_tenant_created_idx
   ON events (tenant_id, created_at DESC);
-- Cascades to all current/future partitions as partitioned index

13. Using Partition Pruning

SET enable_partition_pruning = on;        -- default
EXPLAIN SELECT * FROM events
 WHERE created_at >= '2025-02-15' AND created_at < '2025-02-20';
-- Plan: Append scans only events_2025_02

14. Using Partition-wise Join

SET enable_partitionwise_join = on;

15. Using Partition-wise Aggregate

SET enable_partitionwise_aggregate = on;
SettingEffect
partition_pruningSkip non-matching partitions
partitionwise_joinJoin matching partitions in parallel
partitionwise_aggregateAggregate per partition then combine