NO SQL Lesson 4 – SQL vs NOSQL | Dataplexa
NoSQL Fundamentals · Lesson 4

SQL vs NoSQL

Two engineers. Same brief: build a social platform where users have profiles, write posts, and follow each other. Engineer A reaches for PostgreSQL. Engineer B reaches for MongoDB. Same data. Same features. Completely different experience building, querying, and scaling it. This lesson puts both solutions side by side — same problem, line by line — so you can see exactly where each one wins and where each one struggles.

The Data Model — Designing the Same Thing Twice

Before writing a single query, both engineers have to decide how to structure their data. This is where the philosophies immediately diverge.

SQL PostgreSQL Schema
-- 4 separate tables needed
TABLE users
  id, name, email, bio, avatar_url

TABLE posts
  id, user_id, body, created_at

TABLE follows
  follower_id, following_id

TABLE post_tags
  post_id, tag
NoSQL MongoDB Collections
// 1 collection — everything embedded
collection users {
  _id, name, email, bio,
  avatar_url,
  following: [user_ids],
  posts: [
    { body, created_at,
      tags: ["tech","news"] }
  ]
}

The key design difference:

SQL normalises — it splits data into the smallest possible pieces and links them with foreign keys. Less duplication. Strict consistency. But reading requires JOINs.

MongoDB denormalises — it keeps related data together in one document. More duplication is accepted. But reading is one operation. No JOINs needed.

Round 1 — Writing Data

The scenario: A new user signs up and immediately writes their first post with two tags. Let's see how many operations each system needs.

SQL 4 separate operations
-- Operation 1: insert the user
INSERT INTO users (name, email, bio)
VALUES ('Priya', 'p@mail.com', 'Dev');

-- Operation 2: insert the post
INSERT INTO posts (user_id, body)
VALUES (1, 'Hello world!');

-- Operation 3: tag 1
INSERT INTO post_tags (post_id, tag)
VALUES (1, 'tech');

-- Operation 4: tag 2
INSERT INTO post_tags (post_id, tag)
VALUES (1, 'intro');
NoSQL 1 operation
// One insertOne — everything together
db.users.insertOne({
  name:  "Priya",
  email: "p@mail.com",
  bio:   "Dev",
  posts: [{
    body: "Hello world!",
    created_at: new Date(),
    tags: ["tech", "intro"]
  }]
})

Line by line — what each approach costs:

SQL — 4 round trips to the database

Each INSERT is a separate database call. In production, you'd wrap these in a BEGIN / COMMIT transaction block — that's 6 operations total. If the post insert succeeds but tag insert fails, you need rollback logic. More code. More failure points.

NoSQL — 1 round trip, atomic by default

insertOne writes the entire document — user, post, and tags — in a single atomic operation. Either the whole thing is saved or nothing is. No transaction block needed. No rollback logic. The document is the transaction boundary.

tags: ["tech", "intro"]

In SQL, each tag is a separate row in a separate table. In MongoDB, tags are just an array inside the document. To add a new tag later: $push. To find all posts with tag "tech": find({tags: "tech"}). One line.

Round 2 — Reading Data

The scenario: Load a user's profile page — their name, bio, and their last 3 posts with tags. This is a page that gets loaded millions of times a day.

SQL JOIN across 3 tables
SELECT
  u.name, u.bio,
  p.body, p.created_at,
  pt.tag
FROM users u
JOIN posts p
  ON p.user_id = u.id
JOIN post_tags pt
  ON pt.post_id = p.id
WHERE u.id = 42
ORDER BY p.created_at DESC
LIMIT 3;
NoSQL One document lookup
db.users.findOne(
  { _id: 42 },
  {
    name: 1,
    bio: 1,
    posts: { $slice: -3 }
  }
)
-- SQL result (multiple rows, needs client-side grouping):
name   | bio    | body              | created_at          | tag
-------+--------+-------------------+---------------------+------
Priya  | Dev    | Shipped v2 today! | 2024-01-15 14:22:00 | tech
Priya  | Dev    | Shipped v2 today! | 2024-01-15 14:22:00 | work
Priya  | Dev    | Hello world!      | 2024-01-10 09:00:00 | intro

-- MongoDB result (one object, ready to use):
{
  name: "Priya",
  bio: "Dev",
  posts: [
    { body: "Shipped v2 today!", tags: ["tech","work"], ... },
    { body: "Hello world!",      tags: ["intro"],       ... }
  ]
}

Breaking down both results:

SQL — flat rows, duplicated data

Notice "Priya / Dev / Shipped v2 today!" appears twice — once per tag. Your app code has to loop through the rows and group them back into a post-with-tags structure. Extra work every time this page loads.

posts: {"$slice": -3}

$slice: -3 returns the last 3 items from the posts array. MongoDB does the slicing inside the database — you don't get all posts and discard them in your app. Efficient and clean.

MongoDB result shape

The result is already the exact shape your frontend needs. No grouping. No transformation. Hand it straight to your API response. This is called query-driven design — your data is stored in the shape you read it.

Round 3 — Changing the Schema

The scenario: Six months after launch, the product team wants a "verified" badge for trusted users. Engineer A and Engineer B both get the ticket at 9am. Here's how their day goes:

SQL Engineer A's morning

9:00am — Write the migration script:
ALTER TABLE users ADD COLUMN is_verified BOOLEAN DEFAULT FALSE;
9:05am — Run on staging. Table has 8M rows. Migration takes 22 minutes.
9:27am — Test passes. Schedule production migration for 2am (lowest traffic).
2:00am — Wake up. Run migration on prod. App in maintenance mode. 18 minutes.
2:18am — Done. Back to sleep. Feature ships next morning.

Total time: 1 day + a 2am wake-up

NoSQL Engineer B's morning

9:00am — Add is_verified: true to the user document when verifying someone.
9:02am — Deploy code. Old user documents simply don't have the field — that's fine. New verified users get it.
9:05am — Feature is live. Zero downtime. Zero maintenance window.
9:06am — Gets coffee ☕

Total time: 6 minutes, no wake-ups

// MongoDB — mark a user as verified
// No migration. No ALTER TABLE. Just update the document.
db.users.updateOne(
  { _id: ObjectId("64f1a2b3c4d5e6f7a8b9c001") },
  { $set: { is_verified: true, verified_at: new Date() } }
)

What each line does:

updateOne({ _id: ... })

Find the specific user document by their unique ID. ObjectId is MongoDB's built-in ID type — a 12-byte value that encodes the creation timestamp, machine ID, and a counter. Globally unique without a central sequence.

$set: {"{ is_verified: true }"}

$set is a MongoDB update operator. It adds or updates only the fields you specify — it doesn't touch anything else in the document. If is_verified didn't exist before, it's created now. Other users who don't get this update simply don't have the field. No NULLs. No table-wide changes.

Head-to-Head — 6 Criteria

⚡ Write Speed

SQL
moderate
NoSQL
very high

NoSQL skips strict locking and WAL synchronisation. Designed for high-throughput ingestion.

🔄 Schema Flexibility

SQL
rigid
NoSQL
fully flexible

Every field change in SQL is a migration. In NoSQL, just start using the new field.

🔗 Complex Queries

SQL
excellent
NoSQL
limited

Multi-table aggregations, window functions, complex GROUP BY — SQL dominates here.

🏦 Data Consistency

SQL
ACID
NoSQL
eventual

For bank transfers, stock deductions — SQL's ACID guarantees are non-negotiable.

📈 Horizontal Scale

SQL
difficult
NoSQL
built-in

NoSQL was designed to shard across nodes from day one. SQL requires extra tooling.

🎓 Learning Curve

SQL
one language
NoSQL
per-database API

SQL is one universal language. Each NoSQL database has its own query API to learn.

Decision Tree — Which One Should You Use?

Run your project through this. Be honest with the answers — the most common mistake is answering based on what sounds exciting rather than what the data actually looks like.

Does your data have a fixed, stable structure?
(same fields for every record, unlikely to change)
YES
Do you need complex multi-table queries or strict transactions?
YES
✅ Use SQL
PostgreSQL / MySQL
NO
⚖️ Either works
choose by scale
NO
Is it JSON-like, graph-shaped, or high write volume?
YES
✅ Use NoSQL
Mongo/Cassandra/Redis
NO
🤔 Re-examine
what does data look like?

Real Company Stacks — What They Actually Run

The best engineers don't pick one side. Here's proof — real companies running both SQL and NoSQL in the same product:

Company NoSQL — used for SQL — still used for
Twitter / X Manhattan (Redis-like) for timelines. Cassandra for tweet storage. MySQL for user accounts and billing.
Airbnb MongoDB for listing content. Elasticsearch for search. MySQL for bookings and payments.
Uber Cassandra for trip data and driver location history. PostgreSQL for financial transactions.
GitHub Redis for caching, sessions, background jobs. MySQL for repos, issues, pull requests.
Shopify Redis for cart sessions. Kafka for event streaming. MySQL for orders, products, customer records.

The pattern is clear: NoSQL handles the high-volume, flexible, fast-changing data. SQL handles the financial and relational core. They work side by side, not against each other.

Teacher's Note

Every junior engineer I've seen struggle with this choice was trying to find "the winner." There isn't one. The question is never SQL vs NoSQL — it's always what does this specific data look like, and how will I read it? Answer that honestly and the right tool becomes obvious.

Practice Questions — You're the Engineer

Scenario:

You're building a recipe app in MongoDB. Each recipe has a list of ingredients. The ingredients are never queried separately — you always load a recipe and its ingredients together. Should you embed the ingredients inside the recipe document, or store them in a separate collection and reference them by ID?


Scenario:

You want to add a last_login timestamp to a user document in MongoDB without overwriting any of their other existing fields — name, email, posts, everything else stays exactly as it is. Which MongoDB update operator do you use?


Scenario:

You're building a payroll system. Every payslip must reference a valid employee record. If an employee is deleted, all their payslips must be blocked from deletion first. You need guaranteed referential integrity and multi-step atomic transactions. Should you use SQL or NoSQL?


Quiz — You're the Architect

Scenario:

You're building a marketplace that sells everything — cars, software licences, handmade jewellery, consulting services. Each listing has completely different attributes. New product types are added every month. Your current SQL listings table has 80 columns, most NULL. Migrations are getting dangerous. What is the right fix?

Scenario:

A colleague suggests migrating your bank transfer system from PostgreSQL to MongoDB because "NoSQL is faster." The system debits one account and credits another — both must succeed or neither happens. What is the correct response?

Scenario:

You're building a checkout flow. Two things need storing: (1) the active shopping session — needs to be read in under 1ms, expires after 30 minutes, (2) the confirmed order — needs to be stored permanently with customer and payment details, must never be lost. What is the right architecture?

Up Next · Lesson 5

CAP Theorem

The rule that governs every distributed database on the planet — and why you can never have consistency, availability, and partition tolerance all at once.