← Back to MongoDB
Lesson 1.2 · MongoDB Fundamentals

SQL vs NoSQL Databases

Store the same order twice: in SQL tables that need a JOIN, and as one MongoDB document. See what each does well - one read with no join, new fields without ALTER TABLE - and what it costs: no rules by default, and copied data that must be updated in many places.

Beginner30 min

What you will be able to do

  • Explain how SQL databases store data in tables with fixed columns
  • Compare a JOIN across tables with one document that holds everything
  • Show what happens when a new field is added in each kind of database
  • Explain why SQL tables enforce rules and MongoDB does not by default
  • Name the four main kinds of NoSQL databases
  • Describe the cost of storing copies of data in documents

The idea, in plain English

SQL databases - like PostgreSQL, MySQL and SQLite - store data in tables. Every table has fixed columns, and every row has a value (or NULL) for each column. Related data is split into several tables and combined with a JOIN when you read it. The language for all of this is SQL. These databases are also called relational databases.

"NoSQL" is a name for databases that do not use tables in this way. It is often read as "not only SQL". MongoDB is a NoSQL database of the document kind: related data can live together in one document.

The best way to see the difference is to store the same data in both. We store one order in SQLite (a small SQL database) and in MongoDB 9.0.2, and then change it in three ways. Neither is "better" - each one is good at different things.

Worked example: One order (a customer, a date and two order lines) stored in three SQLite tables and in one MongoDB document - then a new coupon field, a missing name, and a customer who moves to another city.

comparisonOne order, two waysstep 1 / 3

SQL: split, then JOIN

The order lives in three tables. To show it, SQL joins them - and repeats Ravi and Hyderabad on each order line.

tables
3
query
SELECT ... JOIN ... JOIN
rows back
2 (one per line)
new column
ALTER TABLE first

The same order in SQL tables and in one MongoDB document. Real results from SQLite 3.51 and MongoDB 9.0.2.

Words you will see in this lesson

A few words about the two families of databases.

Small dictionary
SQLStructured Query Language: the language of table databases.
Relational databaseA database of tables linked by ids. PostgreSQL, MySQL, SQLite.
Table, row, columnSQL storage: a table has fixed columns; each record is a row.
JOINA SQL command that combines rows from several tables.
SchemaThe rules for the shape of the data: which fields, which types.
NoSQL"Not only SQL": databases that do not store data in relational tables.
EmbeddingPutting related data inside a document, like order lines inside an order.
ConstraintA rule a SQL table enforces, like NOT NULL.

An everyday example: a shopping receipt

Imagine a shop that keeps its records in three notebooks: one for customers, one for orders, one for order lines. To see one full order, a clerk looks in all three and copies the parts together. That is SQL with a JOIN. The good side: when a customer moves, the clerk changes the address once, in the customer notebook.

Now imagine the shop keeps a copy of each receipt in a folder - customer name, address, items, all on one page. To see an order, you take out one page. That is a MongoDB document. The cost: when a customer moves, every old receipt still has the old address. You decide whether that matters.

Example 1 - the SQL way: three tables

CREATE TABLE defines each table and its columns before any data goes in. customers has a name that is NOT NULL - it may not be empty. orders points to its customer with customer_id. order_lines points to its order with order_id.

To show order 101 we JOIN the three tables. The result has one row per order line, so Ravi and Hyderabad appear twice.

Example 1 - sql.sql (run with sqlite3 shop.db < sql.sql)
-- The same order, stored the SQL way: three tables CREATE TABLE customers (id INTEGER PRIMARY KEY, name TEXT NOT NULL, city TEXT); CREATE TABLE orders (id INTEGER PRIMARY KEY, customer_id INTEGER REFERENCES customers(id), placed_on TEXT); CREATE TABLE order_lines (order_id INTEGER REFERENCES orders(id), product TEXT, qty INTEGER, price REAL); INSERT INTO customers VALUES (1, 'Ravi', 'Hyderabad'); INSERT INTO orders VALUES (101, 1, '2026-10-01'); INSERT INTO order_lines VALUES (101, 'Desk lamp', 1, 24.5), (101, 'Notebook', 4, 3); -- To show one order we JOIN three tables SELECT o.id AS order_id, c.name, c.city, l.product, l.qty, l.price FROM orders o JOIN customers c ON c.id = o.customer_id JOIN order_lines l ON l.order_id = o.id WHERE o.id = 101;
Output
┌──────────┬──────┬───────────┬───────────┬─────┬───────┐ │ order_id │ name │ city │ product │ qty │ price │ ├──────────┼──────┼───────────┼───────────┼─────┼───────┤ │ 101 │ Ravi │ Hyderabad │ Desk lamp │ 1 │ 24.5 │ │ 101 │ Ravi │ Hyderabad │ Notebook │ 4 │ 3.0 │ └──────────┴──────┴───────────┴───────────┴─────┴───────┘

Example 2 - the MongoDB way: one document

The whole order is one document. customer is a document inside it; lines is an array (a list) of documents. There is no separate customers or order_lines collection. We chose _id: 101 ourselves - any unique value works.

findOne({ _id: 101 }) returns the full order, already shaped like the object your app wants: one order with a list of lines - not two rows to put back together.

Example 2 - mongo.js (run with mongosh mongo.js)
// The same order, stored the MongoDB way: one document use("shop12") db.orders.insertOne({ _id: 101, customer: { name: "Ravi", city: "Hyderabad" }, // inside the order placedOn: "2026-10-01", lines: [ // the order lines, as an array { product: "Desk lamp", qty: 1, price: 24.5 }, { product: "Notebook", qty: 4, price: 3 }, ], }) print("--- one order, no join") printjson(db.orders.findOne({ _id: 101 }))
Output
--- one order, no join { _id: 101, customer: { name: 'Ravi', city: 'Hyderabad' }, placedOn: '2026-10-01', lines: [ { product: 'Desk lamp', qty: 1, price: 24.5 }, { product: 'Notebook', qty: 4, price: 3 } ] }

Change 1 - a new field

The shop starts using coupons. In SQL, inserting an order with a coupon failed: "table orders has no column named coupon". We had to change the table with ALTER TABLE first. Now every order has a coupon column - the old order shows it empty.

In MongoDB we just saved the new order with a coupon field. The old order was not touched, and it simply has no coupon field. On a big, busy table, changing the schema needs planning (a "migration"); in MongoDB, new fields are easy. The price: your code must handle documents with and without coupon.

SQL - a new column
INSERT INTO orders (id, customer_id, placed_on, coupon) VALUES (102, 1, '2026-10-02', 'DIWALI10'); Parse error near line 19: table orders has no column named coupon ALTER TABLE orders ADD COLUMN coupon TEXT; INSERT INTO orders (id, customer_id, placed_on, coupon) VALUES (102, 1, '2026-10-02', 'DIWALI10'); SELECT id, placed_on, coupon FROM orders; ┌─────┬────────────┬──────────┐ │ id │ placed_on │ coupon │ ├─────┼────────────┼──────────┤ │ 101 │ 2026-10-01 │ │ │ 102 │ 2026-10-02 │ DIWALI10 │ └─────┴────────────┴──────────┘
MongoDB - just send it
db.orders.insertOne({ _id: 102, customer: { name: "Ravi", city: "Hyderabad" }, placedOn: "2026-10-02", lines: [], coupon: "DIWALI10" }) printjson(db.orders.find({}, { placedOn: 1, coupon: 1 }).toArray()) // Output [ { _id: 101, placedOn: '2026-10-01' }, { _id: 102, placedOn: '2026-10-02', coupon: 'DIWALI10' } ]

Change 2 - a customer without a name

The SQL table refused a customer without a name: "NOT NULL constraint failed: customers.name". The rules live in the table, so no program can break them.

MongoDB accepted an order whose customer name is null. By default a collection has no rules at all. You can add them with schema validation (Lessons 2.19 and 6.10), or check the data in your app (Mongoose, Module 5). But you must choose to - nothing is checked for you at the start.

Output - SQL
INSERT INTO customers (id, name) VALUES (2, NULL); Runtime error near line 25: NOT NULL constraint failed: customers.name (19)
Output - MongoDB
printjson(db.orders.insertOne({ _id: 103, customer: { name: null }, lines: [] })) { acknowledged: true, insertedId: 103 }

Change 3 - the customer moves

Ravi moves to Pune. In SQL his city is stored once, in the customers table: one UPDATE changed 1 row, and every order shows Pune from now on.

In MongoDB we copied Ravi’s city into each order. updateMany had to change 2 documents - and with 1,000 orders it would be 1,000. Sometimes a copy is what you want: an order should probably keep the address it was shipped to. Sometimes it is not. Deciding what to put inside a document and what to keep separate is the most important skill in MongoDB; Lessons 2.15 to 2.18 are about exactly this.

Output - SQL: one row
UPDATE customers SET city = 'Pune' WHERE id = 1; SELECT changes(); 1
Output - MongoDB: every copy
db.orders.updateMany({ "customer.name": "Ravi" }, { $set: { "customer.city": "Pune" } }) { acknowledged: true, insertedId: null, matchedCount: 2, modifiedCount: 2, upsertedCount: 0 }

The four kinds of NoSQL databases

NoSQL is not one thing. There are four main kinds, each built for a different job. MongoDB is a document database, the most general kind.

NoSQL families
DocumentJSON-like documents with rich queries. MongoDB, Couchbase, Firestore.
Key-valueA value stored under a key, very fast. Redis, DynamoDB (also document-like).
Wide-columnHuge tables spread over many servers, for heavy writes. Cassandra, HBase.
GraphThings and the links between them - friends, routes. Neo4j.

Side by side

Today the lines are not as sharp as they were. MongoDB has transactions across documents (Module 6), joins with $lookup (Lesson 3.12) and schema validation (Lesson 2.19). PostgreSQL can store JSON. But each still has a natural way of working, and it pays to follow it.

SQL vs MongoDB
Stores data inSQL: tables and rows. MongoDB: collections and documents.
Shape of dataSQL: fixed columns, defined first. MongoDB: flexible; each document can differ.
Related dataSQL: separate tables + JOIN. MongoDB: often inside one document.
Rules (constraints)SQL: in the table, always on. MongoDB: off by default; add validation.
Changing the shapeSQL: ALTER TABLE / migrations. MongoDB: write the new field.
Shared data changesSQL: one row. MongoDB: one document - or every copy.
Growing bigSQL: mostly a bigger server. MongoDB: built-in sharding across servers.

The same task in both

Create the container

SQL needs columns first.

CREATE TABLE orders (...)
// MongoDB: created on first insert
Add a record

Row vs document.

INSERT INTO orders VALUES (...)
db.orders.insertOne({ ... })
Read related data

JOIN vs one document.

SELECT ... JOIN ...
db.orders.findOne({ _id: 101 })
New field

Change the table vs just write it.

ALTER TABLE orders ADD COLUMN coupon TEXT
db.orders.insertOne({ ..., coupon: "DIWALI10" })
Change many

Both can.

UPDATE customers SET city = 'Pune' WHERE id = 1
db.orders.updateMany(filter, { $set: {...} })

Try it yourself

The code does not change. Swap the content string and the program does something else entirely.

Add a line

“Add a third order line to order 101 in both databases. In MongoDB, try updateOne with { $push: { lines: {...} } }.”

Total

“In SQL, compute the order total with SUM(qty * price). How would you do it in your app with the MongoDB document?”

No rules

“Insert a MongoDB order whose qty is the text "four". Was it accepted?”

Your data

“Think of a blog with posts and comments. Draw it as SQL tables, then as MongoDB documents.”

What usually goes wrong

Copying SQL tables one-to-one

Making a MongoDB collection for every SQL table and joining them all the time loses the main advantage of documents. Store together what is read together.

"NoSQL means no schema"

Your data always has a shape - the question is who checks it. In MongoDB it is your app or validation rules you add.

Forgetting the copies

If a value is copied into many documents, changing it means changing every copy - or deciding the copy should keep its old value.

Choosing by fashion

Choose by how your data is read and changed, not by what is popular. Lesson 1.3 and Lesson 7.15 help.

Practice

Write these yourself before opening anything. Getting them wrong first is most of how this sticks.

1.

A library has members and loans. Write the SQL tables (members, books, loans) and a MongoDB design where each member document holds a list of current loans. Then list one thing that is easier and one thing that is harder in each design.

Show hint

Easier in MongoDB: show a member and their loans in one read. Harder: list everyone who borrowed one particular book, or change a book title that is copied into loans.

Key points

  • SQL databases store rows in tables with fixed columns, and JOIN tables to combine related data.
  • MongoDB stores documents; related data can be stored inside one document and read at once.
  • Adding a field needed ALTER TABLE in SQL; in MongoDB we just wrote it.
  • SQL tables enforce rules like NOT NULL; MongoDB accepts anything until you add validation.
  • Data copied into many documents must be updated in many places.
  • NoSQL has four main kinds: document, key-value, wide-column and graph.

Quick check before you move on

How many rows did the SQL JOIN return for one order with two lines?
Two - one per order line, with the customer repeated.
What did SQL say when we inserted a coupon before ALTER TABLE?
"table orders has no column named coupon".
Did MongoDB accept a customer name of null?
Yes - by default a collection has no rules.
How many documents changed when Ravi moved, and why?
Two, because his city was copied into each of his two orders.

Interview questions

When would you choose MongoDB over a relational database?

When data is naturally hierarchical or varies in shape, is mostly read and written as whole aggregates (a product, a profile, an order), changes shape often, or needs horizontal scaling - and when you do not need many ad-hoc joins across entities.

What is the trade-off of embedding data in MongoDB?

Reads of the aggregate are fast and atomic, but duplicated data must be kept in sync on updates, documents can grow (16 MB limit), and querying the embedded data independently is harder.

Does MongoDB support joins and transactions?

Yes: $lookup performs left outer joins in aggregation, and multi-document ACID transactions are supported on replica sets and sharded clusters - though the document model aims to make them less necessary.

Quiz

  1. 1.

    What does NoSQL mean?

  2. 2.

    Why did findOne({ _id: 101 }) need no JOIN?

  3. 3.

    Which change was easier in SQL?

  4. 4.

    Name two features that make MongoDB and SQL closer today.

Comments

Sign in to leave a comment. Your name and photo come from Google; nothing else is shared.

Loading comments...