SQL vs NoSQL
Relational, document, key-value, graph — tradeoffs.
The choice isn't "SQL or NoSQL" — it's "which shape matches my access pattern?" Relational, document, key-value, and graph databases each excel at one access pattern and trip over the others. Pick by the queries you actually run.
The big idea
A database has two jobs: store your data and serve your reads. The shape that makes the reads easy decides the database.
Relational (Postgres, MySQL, SQLite)
Strict schema, normalised tables, SQL for flexible querying, ACID transactions. You don't know up front which queries you'll need? Use this. The cost is migrations when the schema changes and JOIN performance when tables get huge.
SELECT u.email, COUNT(o.id) AS orders
FROM users u
LEFT JOIN orders o ON o.user_id = u.id
WHERE u.signup_at > NOW() - INTERVAL '30 days'
GROUP BY u.id
ORDER BY orders DESC;Best for: transactional systems, anything you might want to slice and dice ad-hoc.
Document (MongoDB, CouchDB, DynamoDB)
Self-contained JSON-shaped documents in collections. No schema enforced; flexible writes; queries within a document are cheap, queries across documents are awkward.
{
"_id": "ord_9001",
"user": { "id": 42, "email": "[email protected]" },
"items": [
{ "sku": "X1", "quantity": 2 },
{ "sku": "Y2", "quantity": 1 }
],
"status": "paid",
"paid_at": "2025-11-12T08:00:00Z"
}Best for: product catalogues, content management, anything aggregate-shaped where one document is read or written together.
Key-value (Redis, DynamoDB, etcd)
You give it a key, it gives you back a value. That's the API. Insanely fast, but the only question you can ask is "what's stored under this key?"
SET session:abc123 '{"user_id":42,"expires":1733000000}' EX 3600
GET session:abc123
INCR rate:42:minute
EXPIRE rate:42:minute 60Best for: sessions, caches, rate-limit counters, leaderboards, anywhere lookup-by-id dominates.
Graph (Neo4j, Memgraph, ArangoDB)
Nodes and edges, queryable by traversal. The query language asks "from this node, walk N hops and tell me what you find."
MATCH (me:User {id: 42})-[:FRIEND*1..3]-(friend:User)-[:LIKES]->(movie:Movie)
WHERE NOT (me)-[:WATCHED]->(movie)
RETURN movie.title, COUNT(*) AS friends_who_liked
ORDER BY friends_who_liked DESC
LIMIT 10;Best for: social networks, fraud detection, knowledge graphs, recommendation systems — anywhere "who connects to whom via what" is the question.
Side-by-side
| Attribute | SQL (relational) | NoSQL (document/KV/graph) |
|---|---|---|
| Schema | Enforced upfront | Flexible / per-document |
| Joins | First-class | Application-side or denormalised |
| Ad-hoc query | Strong | Limited (depends on index) |
| Horizontal scale | Harder (sharding/partitioning) | Built-in for many |
| Transactions | Multi-row ACID | Often single-row / single-doc |
| When to pick | Don't know your queries yet | Know the access pattern; need scale or shape |
In practice
The biggest mistake is using a NoSQL store because it's "fast" without knowing which access pattern. The second biggest is sticking with Postgres at the scale where a specialised store would be 10× simpler. You only learn which is which by watching real traffic.
Key takeaways
- Pick a database by the *shape* of your queries, not by hype.
- Relational excels when queries are flexible and joins are common.
- Document fits aggregates read or written together as one unit.
- Key-value gives you raw speed for "fetch by id" access patterns.
- Graph wins for multi-hop relationship traversal.
- Postgres is the safe default for "I don't know yet."
Checkpoint questions
Use these to test whether the lesson is clear enough to explain without rereading.
- 1Which workload would push you toward relational, document, key-value, or graph storage?
- 2What trade-off do you accept when duplicating data in a document database?
- 3Why is Postgres often a good default even when NoSQL options exist?
- 4How would you explain the query pattern before choosing a database type?
References
External resources for going deeper after the lesson above.