All quickstartsDocumentation Index
Fetch the complete documentation index at: https://private-7c7dfe99-page-updates.mintlify.app/llms.txt
Use this file to discover all available pages before exploring further.
BeginnerReal-Time AnalyticsData WarehousingObservabilityAI/MLCloudOss
Prerequisites
To successfully follow this guide, you’ll need the following:- A running ClickHouse Cloud service. If you don’t have one yet, complete the Create your first Cloud service quickstart first.
uk_price_paid table and concepts introduced there:
What you’ll build
In the MergeTree quickstart you saw that queryinguk_price_paid by town or county requires a full table scan because the table is sorted by (postcode, addr1, addr2).
In this quickstart you’ll solve that problem by creating a projection - an additional sorted representation of your data stored inside the same table. Unlike materialized views, projections require no separate destination table, stay in sync with mutations (deletes and updates), and are used transparently by the query optimizer - you continue querying the same table name.
By the end, you’ll understand how to add and materialize a projection, how ClickHouse automatically selects it, and when to choose projections over materialized views.
Understand why you need a projection
Youruk_price_paid table is sorted by (postcode, addr1, addr2). This means ClickHouse can skip large blocks of data when you filter by postcode, addr1 or addr2, but queries that filter by town must scan every row - all 30 million of them.A projection stores an additional sorted copy of (some or all) columns within the same table. When you query the table, the query optimizer automatically checks whether reading from the projection would touch fewer granules than the base data, and uses it transparently if so.Key differences from materialized views:- No separate table - the projection lives inside
uk_price_paiditself - Transparent query optimization - you query
uk_price_paidas normal; ClickHouse picks the projection automatically - Stays in sync with mutations - deletes and updates applied to the table are reflected in the projection
Add a projection to your table
Define a projection onuk_price_paid that stores town, date, price, and type sorted by (town, date):PROJECTION uk_price_paid_by_town block in the output.Materialize the projection for existing data
Like materialized views, a newly added projection only applies to future inserts. To populate it with the 30 million rows already in the table, materialize it explicitly:is_done = 1, the projection is fully materialized. You can also verify by checking system.projection_parts:Query the table and observe automatic projection usage
Now run a query filtering bytown - on the same table you’ve always queried:uk_price_paid_by_town projection instead of scanning the base data.You can confirm the projection was used with EXPLAIN:ReadFromMergeTree referencing the projection name in the output. If you want to compare performance explicitly, you can disable projection optimization for a single query:Compare projections to materialized views
Projections and materialized views both solve the same problem - faster reads on alternative access patterns - but they make different trade-offs. In short, projections are best when you just need a different sort order on the same data; materialized views are more flexible when you need to transform, aggregate, or route data to a different schema. For a detailed comparison, see Materialized views versus projections.Observe the storage overhead
Projections store a second copy of the selected columns inside the same table, increasing disk usage. Querysystem.parts to see the total size of uk_price_paid (which now includes the projection data):Next steps
In this quickstart you added a projection touk_price_paid that stores data sorted by (town, date), enabling fast lookups by town without creating a separate table. You learned that projections are transparently chosen by the query optimizer, stay in sync with mutations, and trade disk space for read performance.
Check out the following quickstarts next:
Or go deeper with the reference documentation:
