What documents make easy
One read for one thing. Fetching an order means retrieving one document. No joins, no round trips, and the data is contiguous on disk. For a read-this-whole-object access pattern, this is genuinely faster and the gap widens as the object gets more parts.
A shape that matches the code. The document deserialises straight into an object. No object-relational mapping layer reassembling a graph from five result sets.
Schema flexibility. Adding a field to some documents and not others requires no migration. For genuinely heterogeneous data — product catalogues where a book and a fridge share almost no attributes — this is a real advantage rather than laziness.
What rows make easy
Querying from any direction. "Which customers bought product 902" is straightforward relationally and awkward in a document store, because the data is organised around orders, not products. Documents optimise one access path and make the others harder.
Updating a shared fact once. If the customer's address is copied into every order document, changing it means finding and rewriting every one. Relationally it is a single UPDATE. This is the classic normalisation argument and it has not stopped being true.
Integrity the database enforces. Foreign keys, uniqueness and check constraints are declared once and cannot be bypassed. In a document store these usually become application code, which means they hold until some other code path forgets.
Ad-hoc analysis. Aggregating across documents means either a scan or a purpose-built index, and the queries are harder to write.
Denormalisation is the actual trade
Nesting the items inside the order is fine — an order item belongs to exactly one order and is never queried independently. That is not duplication, just co-location.
Copying the customer's name and address into every order is duplication, and it buys read speed at the cost of update cost and the risk of divergence. Sometimes that is right: an invoice arguably *should* record the address as it was at the time, in which case it is not duplication at all but a historical fact.
The question is always whether the copies must agree. If they must, storing them separately means keeping them in sync forever.
The distinction has mostly dissolved
PostgreSQL's jsonb is indexable, queryable and transactional. MySQL and SQLite have JSON functions — the queries on this page use SQLite's, in the same session as ordinary tables.
So the modern answer is usually neither purely one nor the other: relational tables for the entities that are queried from several directions and must stay consistent, and a JSON column for the parts that are genuinely variable and only ever read alongside their parent.
What a dedicated document database still offers is horizontal scaling and operational tooling built around that model from the start. What it gives up is joins and multi-document transactions — though MongoDB has had the latter since 4.0, which narrowed the gap considerably.
Where it goes wrong
Choosing documents to avoid schema design. The schema still exists; it has moved into the application, where nothing enforces it.
Unbounded arrays. A document that grows without limit — every event appended to one record — eventually exceeds the size limit and rewrites the whole thing on every append.
Duplicating a fact that must stay consistent. Fine for a historical snapshot, a slow disaster for live data.
Using JSON columns for well-structured data. If every row has the same fields, they are columns. Putting them in JSON gives up type checking and constraints for nothing.