SQL → MongoDB.
Terminology
| SQL | MongoDB |
|---|---|
| Database | Database |
| Table | Collection |
| Row | Document |
| Column | Field |
| Index | Index |
| Primary key | _id |
| Foreign key | ObjectId reference |
| JOIN | $lookup or embed |
| Transaction | Transaction (rs/sharded) |
SELECT
SELECT * FROM users WHERE age > 25 LIMIT 10
db.users.find({ age: { $gt: 25 } }).limit(10)
WHERE
WHERE age = 30 AND status = 'active'
{ age: 30, status: "active" }
WHERE age > 25 OR status = 'admin'
{ $or: [{ age: { $gt: 25 } }, { status: "admin" }] }
ORDER BY
ORDER BY created DESC
.sort({ created: -1 })
LIMIT / OFFSET
LIMIT 10 OFFSET 20
.skip(20).limit(10)
DISTINCT
SELECT DISTINCT status FROM users
db.users.distinct("status")
GROUP BY
SELECT user_id, SUM(amount) FROM orders GROUP BY user_id
db.orders.aggregate([
{ $group: { _id: "$user_id", total: { $sum: "$amount" } } },
])
JOIN
SELECT u.name, p.title
FROM users u JOIN posts p ON p.user_id = u.id
db.users.aggregate([
{
$lookup: {
from: "posts",
localField: "_id",
foreignField: "user_id",
as: "posts",
},
},
{ $unwind: "$posts" },
])
Or embed posts in user doc.
INSERT
INSERT INTO users (name, email) VALUES ('Alice', '[email protected]')
db.users.insertOne({ name: "Alice", email: "[email protected]" })
UPDATE
UPDATE users SET age = 31 WHERE id = 1
db.users.updateOne({ _id: 1 }, { $set: { age: 31 } })
DELETE
DELETE FROM users WHERE age < 18
db.users.deleteMany({ age: { $lt: 18 } })
CREATE INDEX
CREATE INDEX idx_email ON users(email)
db.users.createIndex({ email: 1 })
LIKE
WHERE name LIKE 'Al%'
{ name: /^Al/ }
IN
WHERE id IN (1, 2, 3)
{ _id: { $in: [1, 2, 3] } }
COUNT
SELECT COUNT(*) FROM users
db.users.countDocuments()
db.users.estimatedDocumentCount() // faster, approximate
NULL handling
WHERE email IS NULL
WHERE email IS NOT NULL
{ email: null }
{ email: { $ne: null } }
{ email: { $exists: true } }
TRANSACTION
BEGIN;
UPDATE accounts SET bal=bal-100 WHERE id=1;
UPDATE accounts SET bal=bal+100 WHERE id=2;
COMMIT;
session.withTransaction(() => {
db.accounts.updateOne({_id:1}, {$inc:{bal:-100}}, {session});
db.accounts.updateOne({_id:2}, {$inc:{bal:100}}, {session});
})
CASE
SELECT CASE WHEN age >= 18 THEN 'adult' ELSE 'minor' END FROM users
{ $project: { status: { $cond: [{ $gte: ["$age", 18] }, "adult", "minor"] } } }
When NOT to migrate
If schema highly relational, queries heavily JOIN-based, ACID critical → stay with SQL.
When TO migrate
- Variable schema (each doc different shape).
- High write throughput.
- Geographic / time-series data.
- Embedded data (orders → items naturally embed).
- Operational simplicity (single tech).
Common mistakes
- Translating row-per-row: missing chance to embed.
- $lookup everywhere → slow.
- ObjectId vs string mismatches.
- Forgetting indexes (Mongo isn’t magic).
- Schemaless ≠ no schema (validate).
Read this next
If you want my migration playbook, it’s at rajpoot.dev .
Building something AI-, backend-, or data-heavy and want a second pair of eyes? I do consulting and freelance work — see my projects and ways to reach me at rajpoot.dev .