SQL → MongoDB.

Terminology

SQLMongoDB
DatabaseDatabase
TableCollection
RowDocument
ColumnField
IndexIndex
Primary key_id
Foreign keyObjectId reference
JOIN$lookup or embed
TransactionTransaction (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 .