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.
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.
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.
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.
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.
-- 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;┌──────────┬──────┬───────────┬───────────┬─────┬───────┐
│ 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.
// 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 }))--- 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.
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 │
└─────┴────────────┴──────────┘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.
INSERT INTO customers (id, name) VALUES (2, NULL);
Runtime error near line 25: NOT NULL constraint failed: customers.name (19)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.
UPDATE customers SET city = 'Pune' WHERE id = 1; SELECT changes();
1db.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.
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.
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 containerSQL needs columns first.
CREATE TABLE orders (...) // MongoDB: created on first insert
Add a recordRow vs document.
INSERT INTO orders VALUES (...)
db.orders.insertOne({ ... })Read related dataJOIN vs one document.
SELECT ... JOIN ...
db.orders.findOne({ _id: 101 })New fieldChange the table vs just write it.
ALTER TABLE orders ADD COLUMN coupon TEXT
db.orders.insertOne({ ..., coupon: "DIWALI10" })Change manyBoth 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 third order line to order 101 in both databases. In MongoDB, try updateOne with { $push: { lines: {...} } }.”
“In SQL, compute the order total with SUM(qty * price). How would you do it in your app with the MongoDB document?”
“Insert a MongoDB order whose qty is the text "four". Was it accepted?”
“Think of a blog with posts and comments. Draw it as SQL tables, then as MongoDB documents.”
What usually goes wrong
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.
Your data always has a shape - the question is who checks it. In MongoDB it is your app or validation rules you add.
If a value is copied into many documents, changing it means changing every copy - or deciding the copy should keep its old value.
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.
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 hintHide 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
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.
What does NoSQL mean?
- 2.
Why did findOne({ _id: 101 }) need no JOIN?
- 3.
Which change was easier in SQL?
- 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...