skip to content

In an Iceberg table PARTITIONED BY (days(event_ts)), which WHERE clause actually prunes partitions?

level: juniorimportance: should knowfreq 60%

answer

  1. there is no extra column to filter
  2. filter the column named inside the transform
  3. a function around the column hides it from the planner
  4. range on the timestamp, pruning on the day

basics

~20 s

A predicate on event_ts itself, such as a timestamp range or equality. Iceberg projects it through the days transform onto each file's stored partition value. There is no event_ts_day column to filter, and wrapping event_ts in a function can block the projection.

solid answer

~40 s

Filter the raw column: `WHERE event_ts >= TIMESTAMP '2026-05-01 00:00:00' AND event_ts < TIMESTAMP '2026-05-02 00:00:00'`. The planner projects that predicate through the `days` transform onto the partition value recorded for each data file, so only that day's files are planned. There is no `event_ts_day` column in the table — the partition value is metadata, so a query referencing it fails to resolve. Wrapping the column in a function, as in `WHERE date(event_ts) = DATE '2026-05-01'`, hands the planner an expression rather than a column reference and commonly prevents the projection, leaving the scan to fall back on per-file column bounds. A filter on a non-partition column such as `region` prunes only through those bounds. Verify with `EXPLAIN` or by checking how many files the scan actually planned.

code

sql · 14 lines
sql
-- table: PARTITIONED BY (days(event_ts))

-- prunes: predicate is on the transform's source column
SELECT count(*) FROM prod.db.events
WHERE event_ts >= TIMESTAMP '2026-05-01 00:00:00'
  AND event_ts <  TIMESTAMP '2026-05-02 00:00:00';

-- fails to resolve: the partition field is not a table column
SELECT count(*) FROM prod.db.events
WHERE event_ts_day = DATE '2026-05-01';

-- correct results, but the planner sees a function over the column
SELECT count(*) FROM prod.db.events
WHERE date(event_ts) = DATE '2026-05-01';

go deeper

for a junior

Remember the rule: filter the column that appears inside the partition transform, using a plain range or equality. Do not invent a partition column name and do not wrap the column in a function.

for a middle

Explain why the function-wrapped predicate still returns correct rows but stops pruning: the planner needs a predicate on the column itself to project it through the transform onto stored partition values.

for a senior

Show how you would confirm pruning from EXPLAIN output and planned file counts, and name the usual causes when it fails, including time-zone boundaries that make a local day span two UTC partitions.

for a principal

The strategic point is that hidden partitioning hides the layout from queries, so pruning correctness has to be observable. Expect to argue for scan-level metrics or query linting so a silently unpruned query is caught before it becomes the platform's cost problem.

## The short rule Filter the column named inside the transform. If the table was created with `PARTITIONED BY (days(event_ts))`, then predicates on `event_ts` are what prune. Nothing else in the query needs to change, and there is no second column to add. ## Why that works When a writer creates a data file it computes `day(event_ts)` for the rows in that file and stores the resulting value in the file's manifest entry. At plan time Iceberg takes each predicate on `event_ts` and projects it through the same transform to produce a predicate on that stored value — an inclusive projection, meaning it may keep a file that turns out to contain no matching rows, but it will never drop a file that does. Manifest entries that fail the projected predicate are discarded without opening the data file, and the surviving files are handed to the engine, which applies the original predicate row by row. ## Walking three predicates ```sql -- 1. prunes to one day's files WHERE event_ts >= TIMESTAMP '2026-05-01 00:00:00' AND event_ts < TIMESTAMP '2026-05-02 00:00:00' -- 2. does not resolve: there is no such column WHERE event_ts_day = DATE '2026-05-01' -- 3. resolves, but the planner sees a function, not the column WHERE date(event_ts) = DATE '2026-05-01' ``` The first is the canonical form and is what a well-written query looks like against any Iceberg table, partitioned or not. The second is the mistake made by someone carrying Hive habits over: the generated partition field name appears in file paths and in metadata tables, but it is not part of the table schema, so the query fails at analysis. The third is subtler and is the one that costs money in production — the query returns correct results, but because the planner is given an expression over the column rather than the column itself, the projection through the transform does not happen and the scan falls back to per-file bounds. On a table whose files are tightly bounded by time it may still skip a lot; on one whose files each span months it skips almost nothing. A fourth case is worth knowing: `WHERE region = 'eu'` on a table partitioned only by day cannot prune partitions at all. It can still drop files whose recorded lower and upper bounds for `region` exclude `'eu'`, which is effective only if the data happens to be clustered on that column. ## Time zones If the column is a timestamp with time zone, Iceberg stores it as an instant and the transform is computed in UTC. A partition therefore covers a UTC day. Filtering with local-midnight boundaries is still correct, but the range straddles two partitions, so the scan touches both. This surprises people who expect a business-local day to map to exactly one partition, and it is worth saying out loud when the interviewer asks about a query that reads twice as many files as expected. ## Confirming it rather than believing it Do not assert pruning; measure it. Run `EXPLAIN` on the query and look at the scan node, or read the number of files and bytes the scan actually planned from the engine's metrics. The comparison that convinces an interviewer is running the same query with and without the timestamp predicate and showing the file count drop by the expected factor. If it does not drop, the usual causes are, in order: the predicate is wrapped in a function, the predicate compares against a value the engine could not resolve at plan time, or the column being filtered is simply not a partition source. ## Habits that generalise Write predicates as half-open ranges on the raw column (`>= start AND < end`) rather than as equality on a derived value. Avoid casting or formatting the partition source column inside the WHERE clause. And when a table is handed to you, look at its partition spec before writing the query, so you know which column carries the pruning — with hidden partitioning the query does not reveal it, which is exactly the tradeoff the format makes.

  • How would you prove the query actually pruned rather than assuming it did?
    Read the scan's planning output: `EXPLAIN` shows the pushed filters on the scan node, and the engine reports how many files and bytes the query planned. Compare the same query with and without the timestamp predicate; if the file count does not drop, the predicate was not projected through the transform and you are scanning the table.
  • The table is partitioned by days(event_ts) but a report filters on the event's local business date. What happens?
    For a timestamp with time zone the day transform is computed in UTC, so a local-day range straddles two UTC partitions and the scan touches both. That is correct but reads roughly twice the files. If local-day access dominates, partition on a column that already carries the business date rather than converting at query time.

saying these in an interview costs you the question

  • Adds a WHERE clause on the generated partition field name
  • Wraps the timestamp in a cast or format function and expects pruning
  • Assumes filtering any column prunes partitions
  • Thinks Iceberg needs a Hive-style manual partition predicate
  • Declares the query pruned without checking planned file counts

context