Last updated
Embedding vs Referencing Guide
- Embed when: data is always accessed together, child data is small, one-to-few relationship
- Reference when: data is accessed independently, child data is large or unbounded, many-to-many relationship
- Orders + items → embed items (always accessed together)
- Users + orders → reference (orders accessed independently, unbounded count)
Examples
Example 1: Schema Conversion — Embedding vs Referencing
SQL schema (normalized):
CREATE TABLE users (
id BIGSERIAL PRIMARY KEY,
email VARCHAR(255) NOT NULL,
first_name VARCHAR(100) NOT NULL
);
CREATE TABLE orders (
id BIGSERIAL PRIMARY KEY,
user_id BIGINT REFERENCES users(id),
total DECIMAL(10,2),
status VARCHAR(20),
created_at TIMESTAMP
);
CREATE TABLE order_items (
id BIGSERIAL PRIMARY KEY,
order_id BIGINT REFERENCES orders(id),
product VARCHAR(255),
quantity INTEGER,
price DECIMAL(10,2)
);
MongoDB schema (embedded — recommended for orders with items):
// orders collection — items embedded in order document
{
"_id": ObjectId("..."),
"userId": ObjectId("..."), // reference to users collection
"total": 109.97,
"status": "completed",
"createdAt": ISODate("2026-03-17T09:00:00Z"),
"items": [
{ "product": "Widget A", "quantity": 2, "price": 29.99 },
{ "product": "Widget B", "quantity": 1, "price": 49.99 }
]
}
// users collection
{
"_id": ObjectId("..."),
"email": "alice@example.com",
"firstName": "Alice"
}
Example 2: Query Conversion — Simple SELECT
SQL:
SELECT * FROM users WHERE email = 'alice@example.com' AND is_active = true;
MongoDB:
db.users.find({
email: "alice@example.com",
isActive: true
});
Example 3: Query Conversion — Range and Comparison
SQL:
SELECT * FROM orders
WHERE total > 100
AND created_at >= '2026-01-01'
AND status IN ('pending', 'processing')
ORDER BY created_at DESC
LIMIT 20;
MongoDB:
db.orders.find({
total: { $gt: 100 },
createdAt: { $gte: new Date("2026-01-01") },
status: { $in: ["pending", "processing"] }
})
.sort({ createdAt: -1 })
.limit(20);