Skip to content
Course contents

Documents, Tables, and the Mismatch Between Them

Objects in memory are trees. Relational tables are not. Everything people love and hate about ORMs lives in that gap.

Lesson 2 of 511 min read

By the end of this lesson you can

  • State the object-relational mismatch precisely and give an example of it in your own code
  • Explain what locality buys a document store and what it costs
  • Use the embed-or-reference trade deliberately rather than by habit

In your program, a résumé is one object. It has a list of positions, each with a company and dates, and a list of qualifications. You can hold the whole thing in one variable and pass it around.

In a relational database it is four tables and three joins.

That gap has a name.

The idea

The object-relational mismatch is the friction between the tree-shaped objects an application works with and the flat, normalised tables a relational database prefers. It is not a bug in either model. They are shaped for different jobs.

Most of an ORM’s code — and most of the frustration with ORMs — is the translation layer this gap requires. The layer is not the problem; the gap is real, and something has to cross it. The problem is when the layer pretends the gap is not there.

What the document model does about it

A document store’s answer is direct: if the application wants a tree, store a tree.

{
  "_id": "ada",
  "name": "Ada",
  "positions": [
    { "company": "Acme", "title": "Engineer", "from": 2021 },
    { "company": "Initech", "title": "Intern", "from": 2019, "to": 2020 }
  ],
  "education": [{ "school": "Bletchley", "degree": "BSc" }]
}

One document. One read. No joins, no translation layer, no impedance.

The property doing the work here is locality: everything you need is stored together, so fetching it is one seek instead of several. On a nested structure you fetch whole, that is a genuine and significant win.

The one-to-many test

The document model fits when the relationship is genuinely one-to-many and owned: the children belong to this parent, only this parent, and nobody else needs to address them independently.

Positions on a résumé pass that test. Line items on an order pass it. Comments on a post usually pass it, until someone asks for “all comments by this user, newest first” and suddenly comments need to be addressable on their own.

The test is not “is it a list?” It is: would anything outside this document ever need to point at one of these children, or query across all of them?

Embed or reference

The moment two documents need the same thing, you have a decision.

Embed a copy in each document: reads are one operation, and every edit to the shared thing must find and rewrite every copy.

Reference by id: one authoritative copy, edits are a single write, and every read pays for an extra lookup.

Put numbers on it and the crossover is easy to find:

Dials

1.0k

How often something fetches a document that includes the shared entity.

5

How often the shared entity itself changes — a company renames, a tag is recoloured.

200

How many documents would embed their own copy of that entity.

Embed a copy2.0k ops/hr

read 1.0k · 1 op per readwrite 1.0k · 200 ops per edit

Reference by id2.0k ops/hr

read 2.0k · 2 ops per readwrite 5 · 1 op per edit

Too close to call on cost alone

Costs are comparable. Decide on which direction your queries run and how much you mind stale copies.

The op counts are a deliberate simplification — a real join is not exactly two reads, and a bulk update is cheaper than 200 separate writes. The shape of the trade is what matters: embedding multiplies the cost of change by the number of copies, and referencing multiplies the cost of reading by the number of hops.

Notice what dominates. When the shared entity is effectively immutable — a country name, a historical exchange rate, the price at the time of purchase — embedding is close to free. When it changes and thousands of copies exist, embedding turns a one-row update into a fan-out write, and a partially failed fan-out leaves documents that disagree with each other.

What each model gives you

Document Relational
Fetching a whole tree One read A join per level
Schema changes Write new shape, read old shape ALTER TABLE, applies to all rows
Many-to-many Awkward; needs references and application joins The thing it was designed for
Ad-hoc queries across children Weak unless anticipated Strong, and often just an index away
Enforcing a rule across records Application’s job Constraints, done once

Check yourself

An e-commerce order stores a full copy of the shipping address at checkout, rather than referencing the customer's address record. Is this a modelling mistake?

What to take away

The mismatch is real and someone always pays for it. Documents pay on relationships; tables pay on assembly. Choose by asking whether your children are owned by exactly one parent — and when you duplicate data, be sure you are copying a fact rather than caching a value.

If you want to go deeper

Finished this one?

skip for now