← All cheatsheets
Data Engineer · #070 · October 9, 2026 · 3 min read

What is partition pruning? Why your query scans the whole table to read one day

What a partition is, how pruning skips the days you do not need, the filters that silently turn it off in BigQuery, clustering as the second lever, and the guardrails that protect the bill: one page.

Get the free PDF

One page, print-ready, free to share. No signup needed.

Download the PDF

Your query scans 2 TB to read one day. The table is partitioned, the filter is there, and the engine still reads everything, because the filter is written in a way it cannot use. One page on what pruning is, what silently turns it off, and the guardrails that protect the bill. The examples are BigQuery; the idea carries to other warehouses, the exact rules do not. The print-ready A4 PDF is at the bottom.

What it is

  • A partition is one chunk of the table, usually one per day.
  • Pruning means the engine reads only the days you need.
  • No date filter: you pay for every day.

What kills pruning

  • Wrapping the column: CAST, FORMAT_DATE and similar hide it from the planner.
  • A filter that comes from a subquery is not a constant, so nothing is pruned.
  • Filtering on the wrong column: created_at only, when the table is partitioned on event_date.

Write it right

  • Use the raw column: event_date, untouched.
  • Compare it to a constant: a literal or CURRENT_DATE().
  • For ranges: >= start AND < end.
  • Isolate it: the column alone on the left side of the comparison.

Cluster: lever 2

  • Partition is the coarse lever: the date.
  • Cluster is the fine lever: user_id, country.
  • Up to 4 clustering columns, and their order matters.

Guardrails

  • require_partition_filter rejects queries with no usable partition filter.
  • Dry run: read the bytes before you run.
  • Maximum bytes billed: the query fails, not the budget.

Coarse partition, fine cluster

-- BigQuery: one partition per day
CREATE TABLE shop.events (
  event_date DATE, user_id STRING, amount NUMERIC
) PARTITION BY event_date
CLUSTER BY user_id
OPTIONS (require_partition_filter = TRUE);

SELECT SUM(amount) FROM shop.events
 WHERE event_date = '2026-10-08' -- 1 partition
   AND user_id = 'u_42';         -- few blocks

With require_partition_filter, a query that has no usable filter on event_date is rejected instead of billed. Before any big query, read the dry run estimate: it is free.

Gotchas

  • LIMIT 10 is not a partition filter.
  • SELECT *: every column is billed.
  • Other engines: same idea, other rules.

The trap: the filter is there, the scan is full

The filterWhat happensWrite instead
FORMAT_DATE('%Y%m', event_date) = '202610'column hidden, full scanevent_date range, raw
event_date = (SELECT MAX(...))not a constant, full scancompute it, pass a literal
created_at >= ... onlywrong column, full scanadd the event_date filter

The quiz

Which query scans less?

-- events: partitioned by day
A) SELECT * FROM events
   WHERE day = (SELECT MAX(day)
                FROM events)

B) SELECT * FROM events
   WHERE day >= '2026-10-08'
     AND day <  '2026-10-09'

B. In BigQuery, B compares the raw partition column to constants, so only the Oct 8 partition is read. In A the filter depends on a subquery, which is not a constant expression, so every partition is scanned. Check with a dry run.

Interview phrasing worth memorizing: pruning needs the raw partition column compared to a constant. Some wrappers still prune, like DATE(ts) on a TIMESTAMP partition, so I never guess: I dry run and read the bytes.

Frequently asked questions

What is partition pruning?
A partitioned table is stored as separate chunks, usually one per day. Partition pruning is the engine skipping every chunk the filter rules out, so a query about one day reads one partition instead of the whole table. On a warehouse that bills by bytes scanned, like BigQuery on-demand, pruning is the difference between paying for one day and paying for every day.
What stops partition pruning in BigQuery?
Pruning needs the raw partition column compared to a constant expression. It is lost when the filter depends on a subquery or another table, because that is not a constant. It is lost when you filter on a different column than the partition column. And wrapping the column in a cast or a formatting function hides it from the planner. Some simple wrappers still prune, such as DATE(ts) on a TIMESTAMP partition, so check the bytes estimate instead of guessing.
What is the difference between partitioning and clustering?
Partitioning is the coarse lever: it splits the table into chunks, typically by date. Clustering is the fine lever: inside each partition it sorts the data by up to four columns, such as user_id or country, so a filter on those columns reads fewer blocks. Partition on the date everyone filters by, cluster on the columns they filter next. Column order in the cluster matters.
How do you check what a BigQuery query will cost before running it?
Use a dry run: the console shows the bytes the query will process before you run it, and the bq tool has a --dry_run flag. A dry run is free. Add two guardrails: require_partition_filter on the table, so queries without a usable partition filter are rejected, and a maximum bytes billed limit, so an oversized query fails instead of being billed.

Get the free PDF

One page, print-ready, free to share. No signup needed.

Download the PDF

More cheatsheets