title_wip

3 rows in set. Elapsed: 0.017 sec. Processed 2.31 million rows, 6.74 MB (135.29 million rows/s., 394.44 MB/s.)
Peak memory usage: 132.11 MiB.
```

The same number of rows was processed (2.31 million), but thanks to PREWHERE, ClickHouse read over three times less column data—just 6.74 MB instead of 23.36 MB—which cut the total runtime by a factor of 3.

For deeper insight into how ClickHouse applies PREWHERE behind the scenes, use EXPLAIN and trace logs.

We inspect the query's logical plan using the [EXPLAIN](/docs/sql-reference/statements/explain#explain-plan) clause:

```
EXPLAIN PLAN actions = 1
SELECT
    street
FROM
   uk.uk_price_paid_simple
WHERE
   town = 'LONDON' and date > '2024-12-31' and price < 10_000;
```

```
...
Prewhere info                                                                                                                                                                                                                                          
  Prewhere filter column: 
    and(greater(__table1.date, '2024-12-31'_String), 
    less(__table1.price, 10000_UInt16), 
    equals(__table1.town, 'LONDON'_String)) 
...
```

We omit most of the plan output here, as it's quite verbose. In essence, it shows that all three column predicates were automatically moved to PREWHERE.

When reproducing this yourself, you'll also see in the query plan that the order of these predicates is based on the columns' data type sizes. Since we haven't enabled column statistics, ClickHouse uses size as the fallback for determining the PREWHERE processing order.

If you want to go even further under the hood, you can observe each individual PREWHERE processing step by instructing ClickHouse to return all test-level log entries during query execution:

```
SELECT
    street
FROM
   uk.uk_price_paid_simple
WHERE
   town = 'LONDON' AND date > '2024-12-31' AND price < 10_000
SETTINGS send_logs_level = 'test';
```

```
...
<Trace> ... Condition greater(date, '2024-12-31'_String) moved to PREWHERE
<Trace> ... Condition less(price, 10000_UInt16) moved to PREWHERE
<Trace> ... Condition equals(town, 'LONDON'_String) moved to PREWHERE
...
<Test> ... Executing prewhere actions on block: greater(__table1.date, '2024-12-31'_String)
<Test> ... Executing prewhere actions on block: less(__table1.price, 10000_UInt16)
...
```

## Key takeaways[​](#key-takeaways "Direct link to Key takeaways")

- PREWHERE avoids reading column data that will later be filtered out, saving I/O and memory.
- It works automatically when `optimize_move_to_prewhere` is enabled (default).
- Filtering order matters: small and selective columns should go first.
- Use `EXPLAIN` and logs to verify PREWHERE is applied and understand its effect.
- PREWHERE is most impactful on wide tables and large scans with selective filters.

- [Query processing without PREWHERE optimization](#query-processing-without-prewhere-optimization)- [How PREWHERE improves query efficiency](#how-prewhere-improves-query-efficiency)- [PREWHERE optimization is automatically applied](#prewhere-optimization-is-automatically-applied)- [How to measure PREWHERE impact](#how-to-measure-prewhere-impact)- [Key takeaways](#key-takeaways)

Was this page helpful?