Create a monthly range-partitioned table (generate partitions in a loop)
PostgreSQLPartitioningCommand
CREATE TABLE sales (
id bigint GENERATED ALWAYS AS IDENTITY,
created_at date NOT NULL,
customer_id bigint NOT NULL,
amount numeric(12,2),
PRIMARY KEY (id, created_at)
) PARTITION BY RANGE (created_at);
-- An index defined on the parent propagates to all partitions automatically
CREATE INDEX sales_customer_id_idx ON sales (customer_id);
-- 12 monthly partitions for 2026
DO $$
DECLARE
m date;
BEGIN
FOR m IN SELECT generate_series(date '2026-01-01', date '2026-12-01', interval '1 month')::date LOOP
EXECUTE format(
'CREATE TABLE IF NOT EXISTS %I PARTITION OF sales FOR VALUES FROM (%L) TO (%L)',
'sales_' || to_char(m, 'YYYY_MM'), m, (m + interval '1 month')::date);
END LOOP;
END $$;
-- Hierarchy, bounds and sizes
SELECT t.relid::regclass AS partition_name,
t.parentrelid::regclass AS parent,
t.isleaf,
t.level,
pg_get_expr(c.relpartbound, c.oid) AS bounds,
pg_size_pretty(pg_total_relation_size(t.relid)) AS total_size
FROM pg_partition_tree('sales') t
JOIN pg_class c ON c.oid = t.relid
ORDER BY t.level, bounds;What it does
Creates a RANGE-partitioned parent on a date column, an index that propagates to every partition, and 12 monthly partitions in a loop; then lists the hierarchy with bounds and sizes.
When to use it
For large, ever-growing tables that drop old data periodically (DETACH/DROP) or whose queries mostly filter by date range (orders, logs, time series).
Caution
On a partitioned table, PRIMARY KEY/UNIQUE constraints must include the partition key (there are no global unique indexes). The lower bound is inclusive, the upper bound exclusive (FROM inclusive, TO exclusive). Without a job that pre-creates future partitions (pg_cron, pg_partman), out-of-range INSERTs fail. Thousands of partitions increase planning time and lock overhead. CREATE INDEX CONCURRENTLY is not supported on the parent: use CREATE INDEX ... ON ONLY on the parent, CREATE INDEX CONCURRENTLY on each partition, then ALTER INDEX ... ATTACH PARTITION.
Related
- Attach a prepared table as a partition with a short lock (ATTACH PARTITION)
- Detach an old partition without blocking live traffic (DETACH CONCURRENTLY)
- Default partition pitfall: new partition fails with 'would be violated by some row'
- Verify that partition pruning works (plan time and run time)
- Automate partition creation and retention with pg_partman