Book Deep-Read · Fundamentals of Data Engineering · Chapter 8
Fundamentals of Data Engineering · Ch 8 · Queries, Modeling, and Transformation · Reis & Housley · 2022
The previous chapter moved orders, payments and clicks into the company's data warehouse. But what arrives is raw material, like crates just unloaded in a supermarket's back room. This chapter is about turning raw material into goods on the shelf: how to ask questions (queries), how to agree on the rules (modeling), and how to prepare the data (transformation). This is where data starts to earn its keep.
The boss asks "how much did we sell last month?" Marketing reports one number, finance another, a few percent apart. Nobody made an arithmetic error: marketing counted returned orders, finance counted only money that actually arrived. When numbers disagree, it is usually not a calculation mistake but different ideas of what "an order" is. The most important job in this chapter is pinning those words down first.
Most teams first agree with the business on what the key terms mean, organize data as "ledger plus registers", and keep the preparation steps as code that can be rerun and managed in one place; only where answers are truly needed within seconds do they compute directly on the stream. One honest cost: agreeing definitions and building models up front takes real time and conversation. Skipping it gets the first report out sooner, but you pay it back twice over in every meeting spent arguing about numbers.
Data creates value not when it lands but when it is prepared: query by reading less, model by fixing definitions and keeping history, and transform with recipes you can rerun.
Want the life of a query, column and partition pruning, the three stream windows and watermarks, normalization, the four modeling schools (Inmon, Kimball, Data Vault, wide tables), slowly changing dimensions, the cost of distributed joins and upserts, plus evidence from dbt, Trino and Flink? → Switch to Deep
You think the value of data engineering lies in moving and storing data fast; in fact it lies in this chapter: queries, modeling and transformation turn raw data into something that answers business questions. Three ideas to take away: read the query plan and scan as little as possible; a data model freezes the business's language into structure, so definitions come before technology; and transformations are rerunnable code, with batch and streaming differing only in cadence.
SELECT amount FROM orders WHERE day = '2024-09-01'.The fourth chapter of Part II, "The Data Engineering Lifecycle in Depth". It follows Chapter 7 on ingestion (the data has landed in storage) and leads into Chapter 9 on serving data for analytics and ML. It covers the lifecycle's transformation stage, which the authors describe as where data begins to create value for the business. In practice: warehouse modeling, dbt projects, Spark batch jobs, Flink streaming jobs.
Picture an online retailer: an orders table of 3 billion rows, three years of history, about 2 TB, growing by 5 million rows a day. Analysts run hundreds of queries daily against a cloud warehouse billed by bytes scanned or by compute. Typical consequences of ignoring this chapter:
SELECT * without a date filter scans all 2 TB, when what was needed was three columns from the last seven days.The authors first place the query within SQL's family: DDL defines structure (CREATE), DML manipulates data (SELECT, INSERT), DCL controls access, TCL controls transactions. A submitted query goes through four steps: parsing (check syntax, convert to an internal form) → planning and optimization (the query optimizer decides which table to read first, which join to use, what can be skipped) → execution → returning results. The same SQL can differ by orders of magnitude in runtime under different plans.
The book's checklist for faster queries: optimize joins: pre-join common combinations into tables, beware many-to-many joins that explode row counts, and use CTEs (common table expressions, named sub-queries written step by step) for readability. Use the explain plan: EXPLAIN shows what the optimizer intends and which step is costliest. Avoid full table scans: select only the columns you need (column pruning) and let partitions and clustering skip irrelevant blocks (pruning). Know how your database handles commits: transaction and consistency models differ, and decide whether you see data someone just wrote. Vacuum dead records: updates and deletes leave old row versions behind. Use cached results: many cloud warehouses return a cached answer for a repeated query.
Using the table above: reading 3 of 100 columns cuts the scan to about 3%; querying 7 days out of three years cuts it again to about 0.6%. Together, 2 TB becomes roughly 0.4 GB.
For streaming data the authors describe two architectural approaches. The fast-follower: use CDC to replicate the production database in real time into an analytical database, and point queries at the replica rather than production. The Kappa architecture: treat the event-streaming platform itself as primary storage, keep enough history, and replay from the start when you need to recompute.
Aggregating a stream requires windows: fixed (tumbling) windows, e.g. non-overlapping 5-minute slots; sliding windows, e.g. computing "the last 5 minutes" every minute, so windows overlap; and session windows, cut by user activity and closed after a period of inactivity. Because data arrives late, the engine uses a watermark to judge that data before a given time has mostly arrived, so a window can close and emit; data later than the watermark needs separate handling. Streams also get combined with other data: enrichment adds dimension attributes to each event, while joining two streams means buffering recent data from both sides until matches arrive, and how long that buffer is kept trades cost against accuracy.
The authors define a data model plainly: how data relates to the real world. A good model reflects the business's concepts and rules: who a "customer" is, when "an order" counts. The book stresses that many organizations skip modeling and just pile up data, ending with data swamps and contradictory metrics. Modeling has three levels: the conceptual model (business entities and relationships, often an ER diagram) → the logical model (fields, types, primary and foreign keys) → the physical model (which database, how it is partitioned).
The first modeling decision is the grain: what does one row represent, an order or a single item within an order? Model at the finest grain you can, since fine grain rolls up to coarse but never the other way. Then comes normalization, Codd's rules for removing redundancy: first normal form makes every field atomic, second removes dependence on part of a composite key, third removes transitive dependencies between non-key fields. Normalization stores each fact once so a change touches one place, at the cost of more joins at query time.
Inmon: Bill Inmon, often called the father of the data warehouse, defines it as a "subject-oriented, integrated, nonvolatile, and time-variant" collection of data supporting management's decisions. You first build a highly normalized (third normal form) enterprise warehouse as the single source of truth, then derive department-facing data marts from it. Top-down, strongly consistent, heavy up-front investment.
Kimball: bottom-up, modeling directly for analysis with a star schema. At its center is the fact table: one row per business event, holding numbers and keys to dimensions, append-only and immutable, narrow and long. Around it sit dimension tables describing who, what, where and when, wide and short; a date dimension is nearly universal. Dimensions shared across several fact tables are conformed dimensions, keeping definitions consistent across subjects. Dimensions change, hence slowly changing dimensions (SCD): Type 1 overwrites and keeps no history; Type 2 adds a new row with validity dates, preserving full history; Type 3 adds a column holding the previous value, keeping one level of history.
Data Vault: proposed by Dan Linstedt, it splits data into three kinds of tables: hubs (business keys only, such as customer number), links (relationships between hubs only), and satellites (descriptive attributes and their history). Insert-only, never updated; when sources change you just add tables. It suits enterprises with many changing sources and audit requirements; the cost is many tables, and usually another analysis-friendly layer on top before querying.
Wide denormalized tables: flatten facts and dimensions into one table that may have thousands of columns. This used to mean redundant storage and painful updates; with columnar storage, empty columns cost almost nothing and only the used columns are read, so wide tables query fast and are easy for analysts. The authors are pragmatic: wide tables are good for fast iteration, but adding columns ad hoc strips business logic of structure, so they still need governance.
The authors separate queries from transformations: a query retrieves data to answer a question, while a transformation persists its result for repeated downstream use, usually orchestrated as a scheduled pipeline.
Distributed joins. When one table is small enough to fit in each node's memory, use a broadcast join: copy the small table to every node and leave the big one in place. When both are large, use a shuffle hash join: redistribute both sides across nodes by join key, at much higher network cost. Hence the common optimization: filter and aggregate first to shrink one side until it can be broadcast.
SQL or code. SQL is declarative and can express far more complex transformations than people assume, so it should be the default; but complex procedural logic or ML feature processing fits code frameworks like Spark better. With Spark the authors advise filtering early, relying on the built-in APIs, and avoiding Python user-defined functions (UDFs), which shuttle data between the JVM and Python.
Update patterns. (1) Truncate and reload: wipe and recompute everything; simple but expensive. (2) Insert only: append new versions as new rows and read the latest; avoid single-row inserts into columnar stores. (3) Delete: hard deletes remove data, soft deletes only flag it; compliance may require hard deletes. (4) Upsert / merge: update if the key exists, insert otherwise. In columnar storage an update often means rewriting a whole file block, so frequent small merges are costly and should be batched. Schemas evolve with the business too, so design to allow added columns.
Views, materialized views, federated queries. A view stores only the query and computes on each use; a materialized view stores the result and refreshes periodically or incrementally, trading storage for speed. A federated query lets one engine query external sources directly; data virtualization goes further, fetching across systems at query time without moving data. Flexible, but it pushes query load onto the source systems.
Streaming transformations. These process data continuously in flight, often as a directed acyclic graph (DAG) of steps. On the debate between micro-batch (e.g. Spark Streaming batching every few seconds to minutes) and true streaming (e.g. Flink processing event by event), the authors' answer is: ask how fast the business really needs results. If seconds are fine, micro-batch; if milliseconds are required, true streaming, verified by testing.
Table 1 · Four schools of batch analytical modeling
| Approach | Fits | Cost |
|---|---|---|
| Inmon (3NF warehouse + marts) | Large enterprises needing one source of truth across departments | Long up-front modeling, slow to show value |
| Kimball (star schema) | Analytics and BI first, delivered business process by process | Without well-managed conformed dimensions, marts drift apart |
| Data Vault | Many sources, frequent change, audit and traceability | Many tables; needs another analysis layer before querying |
| Wide table | Columnar cloud warehouses, fast iteration, analyst self-service | Redundant; ad hoc columns erode business logic |
Table 2 · Slowly changing dimensions and update patterns
| Approach | Benefit | Cost |
|---|---|---|
| SCD Type 1 (overwrite) | Simple, smallest dimension | History is rewritten; old reports change |
| SCD Type 2 (new row) | Full history, attribution as of the time | Larger dimension; joins need date conditions |
| Truncate and reload | Simplest logic, self-consistent result | Slow and costly at volume |
| Insert only + latest | Cheap writes, built-in audit trail | Dedupe on read; storage grows |
| Upsert / merge | Table holds only current state | Rewrites file blocks in columnar storage; small merges are expensive |
Table 3 · Choices in querying and compute
| Choice | When | Cost |
|---|---|---|
| View vs materialized view | View for small data needing freshness; materialize repeated heavy queries | Views recompute every time; materialized ones need refresh and can go stale |
| Federated query vs ingest first | Occasional cross-source or exploratory analysis | Load lands on sources; performance unpredictable |
| Micro-batch vs true streaming | Seconds are fine: micro-batch; milliseconds: true streaming | True streaming has harder state management and operations |
Rule of thumb: fix the grain and definitions first, then pick a modeling approach. For analytics on a columnar cloud warehouse, a Kimball star or wide table is the usual starting point; consider Data Vault only with many sources and audit needs. Any dimension used for historical attribution defaults to Type 2.
A strong answer to the interview question "design a data warehouse for an online retailer" lands like this: a raw layer keeps ingested data as is; a middle layer cleans, deduplicates and unifies definitions; the serving layer follows Kimball with an order-lines fact table and customer, product and date dimensions, the customer dimension as Type 2; every transformation is a SQL model under version control with data tests, run incrementally each hour in dependency order; tables are partitioned by date and clustered by customer; fraud metrics run separately as Flink sliding windows. Each step maps to a concept in this chapter.
Open-source evidence
1. This chapter is where the lifecycle creates value: queries answer questions, models fix definitions, transformations persist results for reuse.
2. A query is parsed, optimized, executed and returned; read the EXPLAIN plan; column plus partition pruning can cut scans by orders of magnitude.
3. Stream queries rely on windows (fixed, sliding, session) and watermarks; stream-to-stream joins buffer data while waiting for matches.
4. Model by fixing the grain first, as fine as possible; normalization removes redundancy, while analytics often denormalizes on purpose.
5. Inmon: top-down 3NF plus marts. Kimball: star schema of facts and dimensions. Data Vault: hubs, links, satellites. Wide tables: back in fashion thanks to columnar storage.
6. Slowly changing dimensions: Type 1 overwrites and loses history, Type 2 adds rows and keeps it, Type 3 adds a column for one level.
7. Distributed joins: broadcast small tables, shuffle big ones; filter and aggregate first. Every update pattern has a cost, and columnar stores hate frequent small merges.
8. Views compute on demand, materialized views trade storage for speed, federated queries avoid moving data but load the sources; choose micro-batch or true streaming by the latency the business needs.