Partitioning: How Declarative Partitioning Prunes Work
A partitioned table is one logical table split into many physical child tables by a partition key. The win is not magic speed on every query — it is the ability to skip entire partitions (pruning), to drop old data instantly (DROP/DETACH a partition instead of a massive DELETE), and to vacuum and index partitions independently. Declarative partitioning lives in src/backend/partitioning/.
The one thing to understand first
A partitioned table is one logical table split into many physical child tables by a partition key. The win is not magic speed on every query — it is the ability to skip entire partitions (pruning), to drop old data instantly (DROP/DETACH a partition instead of a massive DELETE), and to vacuum and index partitions independently. Declarative partitioning lives in src/backend/partitioning/.
Partitioning only pays off when your queries filter on the partition key — that is what lets the planner prune; a query without the key must scan every partition. Choose the key around your dominant access pattern, and the operational wins (instant retention, independent maintenance) follow.
Three partitioning methods
- RANGE — by value ranges, ideal for time-series (
created_atper month). - LIST — by discrete values (
region IN ('EU')per partition). - HASH — by a hash of the key, for even distribution when there is no natural range/list.
CREATE TABLE events (id bigint, created_at timestamptz, payload jsonb)
PARTITION BY RANGE (created_at);
CREATE TABLE events_2026_05 PARTITION OF events
FOR VALUES FROM ('2026-05-01') TO ('2026-06-01');Insert routing
On INSERT, the executor computes the partition key and routes each row to the correct child via ExecFindPartition(). Routing is fast (a binary search for range, a hash for hash), but it does require the key to be present and non-null according to the partition scheme. This is why the partition key must be part of the primary key / unique constraints on a partitioned table.
Partition pruning: the main event
Pruning is the planner/executor eliminating partitions that cannot match a query's filters. It happens in two places:
- Plan-time pruning — when the filter uses constants (
WHERE created_at >= '2026-05-01'), the planner discards non-matching partitions before execution.EXPLAINshows only the surviving partitions. - Execution-time pruning — when the value is only known at run time (a parameter, or the result of a join), the executor prunes during execution.
EXPLAIN ANALYZEshows(never executed)on skipped partitions.
EXPLAIN SELECT count(*) FROM events
WHERE created_at >= '2026-05-01' AND created_at < '2026-06-01';
-- Only events_2026_05 appears in the plan.Partition-wise joins and aggregates
When two tables are partitioned the same way and joined on the partition key, PostgreSQL can perform a partition-wise join — joining matching partitions pairwise instead of the whole tables — controlled by enable_partitionwise_join. Similarly enable_partitionwise_aggregate can aggregate per partition then combine. Both reduce memory and enable more parallelism, but are off by default because they increase planning cost.
Layer 3 — Watch it happen on your own database
-- Plan-time pruning: constant in the filter
EXPLAIN SELECT count(*) FROM events
WHERE created_at >= '2026-05-01' AND created_at < '2026-06-01';
-- only events_2026_05 appears in the plan
-- Execution-time pruning: value known only at run time
EXPLAIN (ANALYZE) SELECT * FROM events WHERE created_at = $1;
-- skipped partitions show "(never executed)"
-- The anti-pattern: no partition key in the filter
EXPLAIN SELECT * FROM events WHERE payload->>'user' = '42';
-- every partition is scanned -> Append over all childrenRun the first query and confirm only one child appears — that is pruning working. Then run the third (no partition key) and watch the plan fan out into an Append across every partition: proof that the partition key must be in the predicate. (never executed) in EXPLAIN ANALYZE is the signature of execution-time pruning on parameterised or join-driven values.
Layer 4 — The levers this hands you
Used well, partitioning gives operational superpowers; used carelessly it adds overhead. The levers and the traps:
- Instant retention.
DROP TABLE events_2023_01orDETACH PARTITIONremoves a month in milliseconds versus a multi-hourDELETE+ vacuum. - Independent maintenance. Each partition vacuums, analyzes, and reindexes as a smaller unit, with less bloat impact; range-by-time pairs beautifully with BRIN indexes.
- Partition-wise joins/aggregates (
enable_partitionwise_join,enable_partitionwise_aggregate) cut memory and unlock parallelism — off by default because they raise planning cost. - Mind the traps: thousands of partitions inflate planning time; the partition key must be in every unique constraint (no global uniqueness on a non-key column); and automate partition creation (
pg_partman) so you never run out of future partitions.
Layer 5 — What an Oracle DBA should expect vs what they get
Partitioning is one area where Oracle has a long head start, and the gaps matter:
- RANGE/LIST/HASH map directly, but Postgres has no interval partitioning — Oracle auto-creates the next range partition on insert; in Postgres you pre-create partitions or run
pg_partman. - Pruning ≈ partition elimination. Plan-time and execution-time pruning correspond to Oracle's static and dynamic partition pruning; the
(never executed)marker is the Postgres tell. - Global indexes don't exist. Oracle supports global indexes spanning all partitions; PostgreSQL indexes are always local (per-partition), which is why a unique constraint must include the partition key.
- Routing is declarative, not trigger-based. Modern Postgres routes inserts via
ExecFindPartition()— no inheritance triggers like the pre-10 (or old Oracle 8-style) era.
Key takeaway
Declarative partitioning splits one logical table into children by RANGE/LIST/HASH; inserts route via ExecFindPartition() and queries that filter on the partition key prune away irrelevant partitions at plan or execution time. The real payoff is operational — instant retention with DROP/DETACH and independent maintenance — provided you choose the key to match your dominant filter, keep partition counts sane, and automate partition creation.
Interview lens
How this shows up in interviews
What an interviewer is actually testing, the same mechanism seen from three roles, and the gap between a shallow answer and one that shows real depth.
What the interviewer is testing
Whether you know partitioning's real wins (pruning, instant data lifecycle, independent maintenance) versus the myth that it speeds every query.
Questions you should be able to answer
- ▸What does partitioning actually buy you — and what does it not?
- ▸What is partition pruning, and does it happen at plan time or run time?
- ▸How do you drop a month of data instantly?
Database engineer
You partition by a key aligned to access/retention (usually time), vacuum and index partitions independently, and DETACH/DROP old partitions instead of DELETE.
Software engineer
You ensure queries carry the partition key so pruning fires; without it every partition is scanned.
Feature builder
You use partitioning for retention and rollup features (drop old partitions) rather than to make every query faster.
Shallow answer
'Partitioning makes queries faster by splitting the table.' The common myth — it can be slower without the partition key.
Answer that shows depth
A partitioned table is one logical table over physical children keyed by a partition column (partitioning/). The real wins are: pruning (skip partitions whose key range can't match — at plan time from constants, or at run time for parameters and joins), instant data lifecycle (DETACH/DROP a partition vs a giant DELETE plus vacuum), and independent per-partition vacuum and indexing. It only speeds queries that filter on or join along the partition key; queries without it touch every partition and can be slower than an unpartitioned table.
Pro lab notebook
The captured run behind every query
Every SQL query above is real and the source citations are verified. Pro unlocks the captured psql output — each query re-run against a real PostgreSQL 17.10 lab, with its verbatim result.
1 real lab transcript for this lesson
The explanation and the SQL above are free — so are the verified source citations. What Pro unlocks is the proof: every query in Partitioning: How Declarative Partitioning Prunes Work re-run against a real PostgreSQL 17.10 instance, with its verbatim psql output captured line for line.
What Pro unlocks here
- Every query in this lesson re-run against a real PostgreSQL 17.10 lab
- The verbatim psql output — page headers, row versions, plan nodes, catalog rows
- The corrected form of any query the source lesson got wrong, re-captured
- A copy-ready notebook you can replay yourself to confirm every claim
How this was verified
This explanation is anchored to PostgreSQL's source tree — the files linked above, on the REL_17_STABLE branch — and cross-checked against the official documentation for versions 15 through 18. Every SQL query shown was executed and its output captured in a throwaway PostgreSQL 17.10 lab; where a query needed correcting to run, that correction is noted inline.
Official documentation: §5.11 Table Partitioning