Storing the data · Topic 2 of 4
Designing a BigQuery Data Warehouse
BigQuery warehouse design: star schemas, nested and repeated fields, how far to normalize, mapping business needs, partitioning and clustering for access.
- 5 min
- Define the grain first, then measures and dimensions.
- Star schemas are fine; nested and repeated fields remove costly one-to-many joins.
- Denormalize for large, read-together data; keep often-changing shared dimensions separate.
What it is
Planning a warehouse means choosing a data model that answers the business questions, deciding how far to normalize, and shaping tables so the common queries read as little data as possible. In BigQuery, storage is cheap and scans cost money or slots, so the design favours fewer joins and pruned scans.
How it works
Mapping business requirements
Start with the questions, not the source tables.
| Ask | Design decision |
|---|---|
| What is one row? (the grain) | Fact table at order, order line, event or daily snapshot level |
| Which measures? | Numeric columns in the fact table: revenue, quantity, duration |
| Sliced by what? | Dimensions: customer, product, date, region |
| Is history needed? | Slowly changing dimension type 2 (valid from and valid to columns) or periodic snapshots |
| How fresh? | Batch loads, micro-batches or streaming ingestion |
| Who uses it? | Data marts or authorized views per audience |
Designing the data model
| Model | Shape | Fits BigQuery when |
|---|---|---|
| Star schema | Fact table plus dimension tables | BI tools expect it; dimensions are shared and change independently |
| Snowflake schema | Dimensions normalized further | Rarely worth it; adds joins |
| Denormalized wide table | Dimension attributes copied into the fact | Very large facts, simple fixed reporting |
| Nested and repeated fields | STRUCT and ARRAY columns inside one row | One-to-many data read together, such as an order and its lines |
CREATE TABLE sales.orders (
order_id STRING,
order_ts TIMESTAMP,
customer_id STRING,
items ARRAY<STRUCT<sku STRING, qty INT64, price NUMERIC>>
)
PARTITION BY DATE(order_ts)
CLUSTER BY customer_id
OPTIONS (require_partition_filter = TRUE);
-- revenue per SKU for one week, without a join
SELECT i.sku, SUM(i.qty * i.price) AS revenue
FROM sales.orders, UNNEST(items) AS i
WHERE DATE(order_ts) BETWEEN '2026-10-01' AND '2026-10-07'
GROUP BY i.sku;
Deciding the degree of normalization
| Choose more normalization when | Choose more denormalization when |
|---|---|
| Dimension attributes change often and must be updated in one place | Tables are huge and the same joins run constantly |
| The dimension is shared by many facts | Data is read together and changes rarely |
| Storage duplication would be very large | Query speed and simplicity matter most |
A common compromise is a star schema with nested child data in the fact. Small dimension tables join cheaply, and nesting removes the most expensive one-to-many join.
Exam answers that "normalize to third normal form" for an analytics warehouse are usually wrong. BigQuery performs best with denormalized or nested data and a few small joins.
Defining architecture to support access patterns
| Access pattern | Feature |
|---|---|
| Filters on a date or timestamp | Partition by that column (time-unit column partitioning) |
| Rows arrive without a usable date column | Ingestion-time partitioning |
| Filters on an integer ID range | Integer-range partitioning |
| Filters or joins on high-cardinality columns such as customer ID | Cluster by up to four columns |
| Users forget the date filter | require_partition_filter = TRUE |
| The same aggregation runs constantly | Materialized view |
| Point lookups of text values in large tables | Search index |
| Different audiences | Layered datasets (raw, curated, marts) and authorized views |
Prefer one partitioned table over many date-sharded tables such as events_20261016. Partitioned tables have one schema, less metadata overhead and simpler queries.
A worked example
A subscription business reports revenue by day, plan and country, and support staff look up one customer's history. Customers change plans, and finance needs the plan as it was at the time of each invoice.
The engineer builds an invoice fact table partitioned by invoice date and clustered by customer ID. A customer dimension uses type 2 history so each invoice joins to the version valid on its date. Invoice lines are nested in the fact to avoid a large join.
How the exam asks about this
- "Queries always filter on event_date but scan the whole table." Partition the table by event_date and require a partition filter.
- "Orders and their line items are always read together and the join is slow." Store line items as a nested repeated field in the orders table.
- "Queries filter by customer_id within a date range on a 50 TB table." Partition by date and cluster by customer_id.
What to remember for the exam
- Define the grain first, then measures and dimensions.
- Star schemas are fine; nested and repeated fields remove costly one-to-many joins.
- Denormalize for large, read-together data; keep often-changing shared dimensions separate.
- Partition by the date filter; cluster by high-cardinality filter columns, up to four.
- Prefer partitioned tables to date-sharded tables.
Clustering alone does not guarantee lower cost the way partition pruning does; its savings show only after a query runs. A dry run estimate reflects partition pruning but not clustering.
Common mistakes
Fully normalizing an analytics model. Many joins add slot time and complexity for no benefit in BigQuery.
Partitioning by a column nobody filters on. Pruning only helps when queries filter on the partitioning column.
Creating one table per day. Sharded tables are harder to query and manage than one partitioned table.
Check yourself
How do you store an order and its line items in one BigQuery row?
Use a repeated STRUCT column (an ARRAY of STRUCTs) for the line items and read it with UNNEST.
How many columns can a BigQuery table be clustered by?
Up to four columns, in order of filter importance.
When should a dimension stay normalized in its own table?
When its attributes change often or it is shared by many fact tables, so updates happen in one place.
Topic 2 of 4 in Storing the data
Test yourself on Storing the data
The full Google Cloud Professional Data Engineer question bank has 733 questions, with timed practice exams and an explanation for every answer.
This study guide is independent and is not affiliated with, endorsed by, or sponsored by Google Cloud. Vendor names, certification names and exam codes are trademarks of their respective owners and are used for identification only.