Module 14 of 52 · Modelling and statistics

Relational and dimensional modelling

30 min 4 outcomes Query walkthrough + design-order challenge 6 sources cited

By the end of this module you will be able to:

  • Normalise a small schema to third normal form and say what it buys
  • Design a star schema with one fact table and three dimensions
  • State where one big table belongs in a modern stack, and where it does not
  • Say when a data vault layer earns its complexity

One fact table, four dimensions, and one grain stated first

One fact table states the grain, one row per order line per day, and four dimensions hang off its join keys, so the grain settles both which measures may be added up and which dimension is allowed on the table at all.

A star schema is one fact table at a stated grain with one dimension on each join key. State the grain first: it settles which measures may be added up and which dimensions can join the fact at all.

One fact table, four dimensions, and one grain stated first A hub board in two regions. The star region holds a red-tinted fact table card in the centre named sales_order_line, stating its grain as one row per order line per day and listing the measures quantity sold, net sales value and discount amount. Four dimension cards surround it: dim_date above, dim_store below, dim_customer to the left, dim_product to the right, each listing the attributes it carries. Four brand-red arrows leave the fact card, one to each dimension, labelled date_key, store_key, customer_key and product_key, the foreign key that carries the join. The second region is a red-accent note card reading state the grain first, everything else follows from it. STAR SCHEMA · ONE FACT, FOUR DIMENSIONS, ONE STATED GRAIN FACT TABLEsales_order_lineGrain: one row perorder line, per dayMeasures:quantity soldnet sales valuediscount amount DIMENSIONdim_dateday, month, yearholiday flag DIMENSIONdim_storesite, region, typetrading hours DIMENSIONdim_customersegment, regionsigned-up date DIMENSIONdim_productcategory, brandunit of measure date_key store_key customer_key product_key State the grain first; everything else follows from itA dimension that cannot join at the stated grain does not belong on this fact table.

Four modelling styles and when each one earns its keep

The two axes are how much of the model survives a change and how many joins the query author carries, so the four styles are placed rather than ranked: data vault absorbs new sources, one big table is trivial to query and expensive to alter.

Third normal form, star schema, one big table and data vault trade the same two things: how much of the model survives a change, and how many joins the query author must carry. Place the workload on those two axes before choosing a style.

Four modelling styles and when each one earns its keep A quadrant board. A vertical arrow on the left is labelled tolerance of change rises; a horizontal arrow beneath is labelled the query author's job gets simpler. Four cells fill the quadrants. Top left, Data vault: new sources land without reshaping what exists, use when sources keep changing. Top right, Star schema, tinted red: a dimension bolts on sideways, one join from fact to label, use when analysts write the queries. Bottom left, Third normal form: one fact in one place, a change touches many tables, use when writes must stay consistent. Bottom right, One big table: every column on one wide row, use when one team owns one question. A legend explains the red tint. FOUR MODELLING STYLES · PLACED, NOT RANKED tolerance of change rises the query author's job gets simpler ABSORBS CHANGE · JOINS HARDData vaultHubs, links and satellites take newsources without reshaping what existsUse when sources keep changing ABSORBS CHANGE · JOINS EASYStar schemaA new dimension bolts on sideways;one join from fact to any labelUse when analysts write the queries CHANGE RIPPLES · JOINS HARDThird normal formOne fact in one place, so a businesschange touches many tables at onceUse when writes must stay consistent CHANGE REBUILDS · JOINS EASYOne big tableEvery column on one wide row; a newcolumn means rebuilding the whole setUse when one team owns one question Red tint marks the style this course treats as the default for a shared analytics layer.

Two dashboards, one metric, and a difference nobody could explain

A retailer ran two sales dashboards. Finance read net sales from a wide table built by the reporting team; the trading team read net sales from a table built by an analyst who had joined the same sources in a different order. For eleven months the two numbers agreed closely enough that nobody looked. In the twelfth month they were four per cent apart, and the review took a fortnight.

The cause was not a broken pipeline. One table held one row per order line and the other held one row per order, and both applied the same discount amount. On orders with more than one line, the discount was counted once in one table and once per line in the other. Neither table stated what a row represented, so neither team could see the clash until the arithmetic diverged.

Both figures came from the same warehouse. Why did they disagree by four per cent?

Modelling is the discipline that would have caught that in an afternoon. This module covers the two shapes that matter most in practice: the normalised relational schema that protects writes, and the dimensional schema that serves reads. It then places two shapes that arguments tend to form around, one big table and the data vault, so you can decide between them on evidence rather than on preference.

Start with the shape designed for correctness under change, because the analytical shapes are all departures from it and are easier to judge once you know what they gave up.

14.1 Third normal form, and what it buys

The relational model was proposed to separate what users know about data from how a machine stores it, and that separation is still the reason the model has outlasted every storage technology it has run on.

Future users of large data banks must be protected from having to know how the data is organized in the machine (the internal representation).

E. F. Codd, A Relational Model of Data for Large Shared Data Banks - Communications of the ACM, 1970, opening paragraph

Codd's target was data independence: applications should survive changes to storage layout, indexing and access paths. This 1970 paper introduces normalisation only as far as first normal form, removing nested relations so that every cell holds a single value. Second and third normal form arrive in Codd's follow-up, Further Normalization of the Data Base Relational Model, IBM Research Report RJ909 of 1971, which is where partial and transitive dependencies are defined. Hold the frame from both: normalisation is not tidiness for its own sake; it is what stops a stored fact from having two versions in the same database.

organises a schema so that each fact is stored in exactly one place. Take a single table holding one row per order line, with columns for order id, line number, customer id, customer name, customer postcode, customer city, product id, product name, quantity and unit price. It works until something changes.

  • First normal formrequires each cell to hold a single value. A column holding "blue, large, cotton" or a repeating group of columns named product_1, product_2, product_3 breaks it, and every query against such a column has to parse text rather than compare values.
  • Second normal form requires every non-key column to depend on the whole rather than on part of it. In our table the key is order id plus line number, and customer_id is fixed by the order id alone: every line of the same order repeats it, and the customer name, postcode and city travel with it. That is a partial dependency, so the customer columns move to an Order table keyed by order id, and the order line keeps only what genuinely varies line by line.
  • requires that no non-key column is determined by another non-key column. Product id is fixed by the whole key, so it survives second normal form, but product_name is fixed by product id rather than by the order line. That is a transitive dependency, and product_name moves to a Product table with the order line holding the foreign key. The same test applied to the Order table catches city, which is determined by postcode rather than by the order, so the address detail moves out in turn.

What that buys is specific and worth naming, because "less duplication" is only the surface. It removes three classes of anomaly. An update anomaly is a customer changing city and the change landing on some of their order lines but not all, leaving two answers to one question. An insertion anomaly is being unable to register a new product until somebody orders it, because product details only exist on an order line. A deletion anomaly is losing the last record of a product when the final order referencing it is removed. Each of those is a case of one fact stored in many rows, and third normal form is the point at which the common ones disappear.

It also buys write efficiency and enforceable constraints. A change to a customer address touches one row. A foreign key can be declared and the database can refuse an order line pointing at a product that does not exist. This is why systems, which take payments and update records all day, are normalised as a matter of course.

What it costs is joins. A question such as sales by city by product category now crosses four or five tables, and the query planner has to be right about all of them. On a transactional workload that is a fair trade, because the reads are small and keyed. On an analytical workload that scans hundreds of millions of rows, it stops being fair.

Common misconception

Higher normal forms are always better, so a good designer normalises as far as the theory goes.

Normal forms are a tool for controlling redundancy in schemas that are written to frequently, and each one costs joins on read. Beyond third normal form the remaining anomalies are rare in ordinary business schemas, and analytical models deliberately reverse the process by folding descriptive attributes back into dimensions. The question is never how normalised a model is; it is whether the workload is dominated by writes that must stay consistent or by reads that must stay fast.

A schema tuned to keep writes correct answers a different question from one tuned to aggregate a billion rows, and the second question is what the warehouse exists for.

14.2 Why analysis wants a different shape

workloads scan and aggregate large numbers of rows to answer questions about patterns: sales by region by month, demand by substation by season, claims by policy type by quarter. They touch few columns and enormous numbers of rows, which is the mirror image of the transactional pattern.

Three consequences follow. Column-oriented storage wins, because a query reading three columns should not pay to read the other forty. Joins become the expensive part rather than the cheap part, so the model tolerates duplication that a transactional schema would forbid. And the design has to be legible to the people writing the queries, who are analysts rather than application developers.

Query-first modelling is not unique to warehouses. Distributed stores make the same trade openly, and their documentation says so.

Data is duplicated across multiple tables in a process known as denormalization. Data duplication and a high write throughput are used to achieve a high read performance.

Apache Cassandra, Data modelling documentation - Introduction, query-driven modelling

Cassandra states that its data modelling is query-driven: the access patterns and application queries determine the structure of the tables. That is the same instinct behind dimensional modelling, arrived at from a different direction. It is worth seeing the trade stated so plainly, because it makes clear that denormalisation is a deliberate purchase of read performance with storage and write effort, rather than a failure to normalise.

Dimensional modelling turns that trade into a repeatable design, and every version of it starts by settling what a single row means.

14.3 The star schema, grain first

splits analytical data into two kinds of table. A holds the measurements produced by a business event, one row per event, alongside foreign keys. A holds the descriptive context: who, what, where, when and how. Drawn out, one fact table sits in the middle with dimensions around it, which is why the arrangement is called a . The primary figure above shows the geometry, and it is deliberate that nothing joins dimension to dimension.

The first decision is the : a written statement of exactly what one row of the fact table represents. One row per order line per day. One meter reading per meter per half hour. One claim payment per claim per event. The grain is declared before any column is chosen, and it is written on the face of the model rather than buried in a wiki, because it settles two things that nothing else can. It decides which measures may be added up, and it decides which dimensions can join the fact at all.

The retailer in the opening lost a fortnight to an undeclared grain. Order-level discount cannot be added up across a table at order-line grain, because it is recorded once per order and would be counted once per line. Stating the grain makes that visible at design time as an obvious mismatch rather than at month twelve as a four per cent gap.

Here is a worked design at that grain, with one fact and three dimensions.

  • fact_sales_line. Grain: one row per order line. Foreign keys to date, product and customer. Measures: quantity, net sales value, line discount amount, cost of goods. Every measure is additive at this grain, which is the test the design must pass.
  • dim_date. One row per calendar day, holding day, week, month, quarter, year, financial period, public holiday flag and season. A date dimension is worth building even though a database has date functions, because financial calendars and trading definitions live in it and stop every analyst reimplementing them.
  • dim_product. One row per product, holding name, category, sub-category, brand, pack size and unit of measure. The category hierarchy is flattened into columns rather than split into further tables, which is the deliberate denormalisation that keeps the query one join deep.
  • dim_customer. One row per customer, holding segment, region, channel of acquisition and signed-up date. Personal identifiers are held under access control rather than in the analytical dimension, because a dimension is read widely by design.

The result is one join hop from any measure to any label. Sales by brand by financial period is a single query that an analyst can write, read and check, and the model can absorb a fourth dimension later without reshaping anything that already exists.

Common misconception

A star schema is just a denormalised copy of the transactional tables.

A copy preserves the source structure and inherits its grain by accident. A dimensional model declares a grain that matches a business process, conforms dimensions so that the same product and the same calendar are used by every fact table, and chooses measures that are additive at the declared grain. Two models can hold identical values and only one of them will answer a question consistently across subject areas.

Dimensions are not static reference lists. A customer moves region and a product changes category, and how the model handles that decides whether last year's report still reproduces.

14.4 Dimensions that change over time

A is one whose descriptive attributes change: a customer moves address, a product is reassigned to a different category, a site transfers to another region. The design choice is whether to overwrite the old value or keep it.

Overwriting is simple and destroys history. Every past sale to that customer is now reported against the new region, so last quarter's regional report no longer reproduces. Keeping history means adding a new dimension row when an attribute changes, with a validity period, and pointing new facts at the new row. Past facts keep pointing at the old row, so the regional report reproduces exactly as it was published.

Neither is correct in the abstract. The question is whether the business analyses history as it was or as it is now. Regulatory reporting and financial restatement usually need as it was. Current-state operational reporting usually wants as it is now. Many models carry both, with a current attribute alongside the versioned one. What is never acceptable is leaving the choice unmade, because the model will then behave one way in some places and another way elsewhere, and nobody will be able to say which report is right.

The flattest possible shape removes joins entirely, and modern engines make it far more defensible than it once was, which is exactly why it needs placing carefully.

14.5 One big table, and where it belongs

takes the star and folds it flat: the fact is pre-joined to every dimension, so a query reads a single wide table and performs no joins at all. Every label a reader might filter on is already a column on the row.

The case for it has strengthened. Columnar engines only read the columns a query touches, so a table three hundred columns wide costs a three-column query almost nothing to scan. Compression handles the repetition well, because a column holding the same region string a million times compresses hard. Storage is cheap relative to analyst time. And self-service tools and language models both do noticeably better against one flat table than against a join graph they have to navigate correctly.

The case against it has not gone away, and it is a change cost rather than a query cost. The support figure above places the four styles on the two axes that actually trade against each other: how much of the model survives a change, and how many joins the query author has to carry. One big table sits in the easy-to-query, expensive-to-change corner. Adding an attribute means rebuilding the whole set. Correcting a dimension value means rewriting every row that carried it. There is no shared definition of product for a second subject area to conform to, so the second team builds their own wide table and the organisation is back to two answers.

The position that holds up in practice is not a choice between the two, it is a layering. Keep a modelled core with declared grain and conformed dimensions as the place where meaning is defined, and materialise wide flat tables from it for specific consumption: a dashboard, a feature set, a retrieval index. The wide table is then a derived serving artefact that can be dropped and rebuilt, rather than the only record of what the business means. Building the wide table first and treating it as the source of truth is what makes it expensive, not the shape itself.

Two practical points support that layering. Modern make rebuilding a derived table routine, with atomic commits and schema evolution rather than a weekend outage. And transformation tools have made derived tables cheap to define and re-derive, so a serving table stops being a permanent commitment.

Common misconception

Columnar engines are fast enough now, so dimensional modelling is obsolete.

Performance was only one of the arguments for dimensional modelling, and not the most important one. The others were a declared grain, so measures cannot be added up wrongly, and conformed dimensions, so two subject areas mean the same thing by customer. A faster engine does not supply either. It removes the penalty for joins, which changes how the model is served, not whether meaning has been defined.

The remaining style solves the opposite problem: not how to query the model, but how to keep loading it while the sources underneath it keep changing.

14.6 When a data vault earns its complexity

A splits the warehouse into hubs holding business keys, links holding the relationships between those keys, and satellites holding descriptive attributes with their history and their source. Nothing is overwritten; new records are appended with a load timestamp and the system they came from.

The property that decision rests on is this: a new source can be attached to an existing hub by adding a satellite, without reshaping anything already in place, and every attribute stays traceable to the system and load that produced it. That is what the top-left corner of the support figure is recording. The same decomposition is why an analyst never queries the vault directly. Answering an ordinary business question from raw hubs, links and satellites takes many joins, so a vault is normally loaded into a star-shaped presentation layer for consumption.

It earns its complexity when several conditions hold together. Many source systems describe the same entities and are expected to keep changing or multiplying. The organisation must be able to show which source and which load produced any attribute value, typically because of regulatory or merger-integration pressure. Full history must be retained, including corrections, rather than the current state only. And there is a team with the capacity to run a modelled core and a presentation layer.

It does not earn its complexity on a single well-governed source, on a small team, or where the driver is that the pattern is fashionable. The cost is real: two layers to build, two layers to test, and a shape that no analyst can read. A warehouse that loads three stable sources and serves one department will move faster with a normalised staging layer and a star schema over it.

Loading interactive component...
14.7 Check your understanding

A table holds one row per order line with columns order_id, line_no, customer_id, customer_postcode, customer_city, product_id and product_name. The primary key is order_id plus line_no. Which statement about its dependencies is correct?

A fact table is declared at the grain of one row per order line. The design team wants to add a shipping charge that the source system records once per order. What is the correct treatment?

A team proposes replacing their star schema with one wide flat table because their columnar engine makes joins cheap. Which objection is the strongest?

Loading interactive component...

Core distinctions

  • Third normal form stores each fact in exactly one place, which removes update, insertion and deletion anomalies and lets the database enforce constraints. It buys write correctness under change and pays for it in joins on read.
  • Analytical workloads scan many rows and few columns, so they tolerate duplication that a transactional schema would forbid. Dimensional modelling turns that trade into a repeatable design of fact tables and dimension tables.
  • The grain is a written statement of what one fact row represents, and it is declared before any column is chosen. It decides which measures may be summed and which dimensions can join, and an undeclared grain is the most common cause of two dashboards disagreeing.
  • One big table is best treated as a derived serving layer materialised from a modelled core, not as the source of truth. Columnar engines removed the join penalty, but they did not supply a declared grain or conformed dimensions.
  • A data vault earns its complexity where many changing sources describe the same entities, every attribute must be traceable to source and load, and full history is required. On a small team with stable sources it is cost without return.

Standards and sources cited in this module

  1. E. F. Codd, A Relational Model of Data for Large Shared Data Banks

    Communications of the ACM 13(6), 1970

    The origin of the relational model, data independence and normalisation to first normal form, and still the clearest statement of why the model exists.

  2. E. F. Codd, Further Normalization of the Data Base Relational Model

    IBM Research Report RJ909, San Jose, 1971

    Where second and third normal form are defined, along with the partial and transitive dependency tests. The 1970 paper normalises only as far as first normal form.

  3. ISO/IEC 9075-1:2023, SQL framework

    Part 1, Framework

    The current SQL standard, which defines the language the relational and dimensional models are expressed and queried in.

  4. Apache Cassandra data modelling documentation

    Introduction to query-driven data modelling

    A plain statement of the query-first, denormalised trade from a distributed store, useful as a contrast with the normalised relational default.

  5. Apache Iceberg

    Table format specification and schema evolution

    Shows why rebuilding a derived serving table is now routine: atomic commits and schema evolution over files in object storage.

  6. DAMA-DMBOK, Second Edition

    Data Modelling and Design, and Data Warehousing and Business Intelligence

    Practitioner framing of modelling levels, notation and the warehousing patterns these shapes sit inside.

Module 14 of 52 · Modelling and statistics