Week 3 — Lesson 2: Document databases and JSON

2 min

The 155 NorthPeak incidents also exist as data/json/incidents.json, where each incident is a single JSON object with its machine nested inside and a list of tags. This is the shape a document database uses: one record, one document, no fixed schema. It removes the join, but it copies the machine data into every incident. This lesson reads one document, walks its nested fields, and weighs that trade against the relational model.

One object per record

JSON is a text format for data. It has objects in braces, lists in square brackets, and values: text, numbers, true, false, null. Python reads it with json.load. An object becomes a dictionary. A list stays a list.

A document is one JSON object stored as a unit. In a relational table the incident and its machine live in two tables. In a document they live together. The machine is a nested object inside the incident.

Document databases, such as MongoDB, store millions of these. They do not force every document to have the same fields. That is a flexible schema. One incident can have a tags list of three, another of five. One can have a photo field the others do not have.

RelationalDocument
Fixed columns, checked by the databaseFields can differ from one document to the next
Related data in other tables, reached by a joinRelated data nested inside, reached by a path
SELECT m.site FROM incidents i JOIN machines m ...doc["machine"]["site"]

On the NorthPeak incidents

data/json/incidents.json holds the 155 incidents as documents. Here is the first one:

json
{
  "_id": "INC-0001",
  "date": "2025-02-20",
  "machine": {"id": "M001", "type": "pump", "site": "Toronto"},
  "category": "overheating",
  "severity": "high",
  "downtime_hours": 29.9,
  "repair_cost_cad": 3465.33,
  "technician": "L. Fortin",
  "description": "Overheating alarm on M001. Cooling fan running but airflow blocked by dust.",
  "tags": ["high", "overheating", "pump"]
}

machine is a nested object. tags is a list. No join is needed to know the site: doc["machine"]["site"] gives Toronto.

DuckDB, a small SQL engine in the venv, reads this file as a table. read_json_auto guesses the types. machine becomes a STRUCT, tags becomes a VARCHAR[], a list of text. Then SQL works on the nested field: GROUP BY machine.site gives Montreal 66, Toronto 56, Quebec City 33.

A common mistake

Flexible does not mean free. When a field is missing in one document, doc["photo"] raises KeyError. Use doc.get("photo") to get None instead. And a nested value that is copied into every document can go stale. If M001 moves to Montreal, its 5 incident documents still say Toronto until you update them all. The relational model would fix it in one place.