Documents, BSON, _id, flexible schema β and the one modelling decision that determines whether your database is fast or miserable.
Lecture 11 argued that document databases exist because applications think in nested objects and tables do not. Today we stop arguing and start typing.
A free Atlas M0 cluster, a bdma_student user, and a connection string you saved somewhere safe.
15,000 Delhi NCR ride bookings in bdma.rides β the dataset every remaining lecture uses.
{
_id: ObjectId("65f1a2c4e8b9d4a1c2f30142"),
booking_id: "CNR3000142",
booked_at: ISODate("2024-03-14T18:22:07Z"),
status: "Completed",
customer_id: "CID482910",
vehicle_type: "Go Sedan",
route: { pickup: "Connaught Place", drop: "Aerocity" },
booking_value: 412.50,
ride_distance: 18.4,
ratings: { driver: 4.6, customer: 4.8 },
payment_method: "UPI"
}
| MongoDB | Relational | In This Course |
|---|---|---|
| Deployment | Server / instance | Your Atlas M0 cluster |
| Database | Database | bdma |
| Collection | Table | rides |
| Document | Row | CNR3000142 |
| Field | Column | booking_value, route.pickup |
CREATE TABLE.{ status: "Completed",
avg_vtat: 7.4,
avg_ctat: 31.2,
booking_value: 412.50,
ride_distance: 18.4,
ratings: {driver:4.6,
customer:4.8},
payment_method: "UPI" }
{ status:
"Cancelled by Driver",
avg_vtat: 2.84,
cancellation: {
by: "driver",
reason: "Personal & Car
related issues" } }
booking_value.| JSON | BSON | |
|---|---|---|
| Form | Text | Binary |
| Types | 6 | ~20 β ObjectId, Date, Decimal128, Int32/64, Binary |
| Field order | Undefined | Preserved |
| Reading one field | Parse the whole string | Length-prefixed β skip straight to it |
Double cannot hold βΉ0.10 exactly. Sum a million fares and the ledger drifts. Money gets Decimal128; distances and ratings are fine as doubles._idObjectId("65f1a2c4 e8b9d4a1c2 f30142")
-------- ---------- ------
4 bytes 5 bytes 3 bytes
timestamp random counter
(seconds) per process per process
| Component | Consequence |
|---|---|
| Timestamp | Sorting by _id β sorting by creation time, free, no extra index |
| Random | Twenty app servers can generate IDs at once without colliding |
| Counter | One process can generate thousands per second, still unique |
AUTO_INCREMENT needs a round trip to a single shared counter. An ObjectId needs no coordination at all.The schema does not disappear. It moves from the database into your application code, your analysts' queries, and your dashboards.
Additive change with no downtime Β· heterogeneity that matches reality Β· fast iteration while the record shape is still unknown.
payment_method vs paymentMethod β both writes succeed, reports quietly undercount. 412.5 vs "412.5" β range queries silently skip the strings.
$jsonSchema β strictness you opt intodb.createCollection("rides", {
validator: { $jsonSchema: {
bsonType: "object",
required: ["booking_id", "booked_at", "status", "route"],
properties: {
booking_id: { bsonType: "string", pattern: "^CNR[0-9]{7}$" },
status: { enum: ["Completed", "Cancelled by Driver",
"Cancelled by Customer", "No Driver Found",
"Incomplete"] },
booking_value: { bsonType: ["double","int"], minimum: 0 }
} } },
validationLevel: "moderate",
validationAction: "error"
})
booking_value. For 4,756 bookings its absence is correct. validationAction: "warn" first, "error" once the backlog is clean.{ booking_id: "CNR3000142",
route: { pickup: "CP",
drop: "Aerocity" },
ratings: { driver: 4.6,
customer: 4.8 } }
One query. No join. Atomic single-document write.
// rides
{ booking_id: "CNR3000142",
customer_id: "CID482910" }
// customers
{ _id: "CID482910",
name: "Priya Sharma" }
Two queries, or one $lookup in Lecture 14.
| Test | Embed when⦠| Reference when⦠|
|---|---|---|
| Access | Read together with the parent | Read on its own |
| Size | Small and bounded | Large or unbounded |
| Change | Changes with the parent | Changes independently |
| Sharing | Belongs to one parent | Shared by many parents |
| Cardinality | One-to-one, one-to-few | One-to-many, many-to-many |
| Relationship | Call | Why |
|---|---|---|
| Booking β route | Embed | Two fields, fixed at booking time, never read alone |
| Booking β ratings | Embed | Two scalars, written once at trip end |
| Booking β cancellation | Embed | Same business event, present only when relevant |
| Booking β customer | Reference | One customer, hundreds of rides; a phone change must not rewrite history |
| Customer β bookings | Reference | Unbounded β a commuter adds 500 a year, forever |
customer: { id, name } to skip the join. The name on an old receipt staying old is arguably correct, not a bug.// ANTI-PATTERN
{ _id: "CID482910",
name: "Priya Sharma",
bookings: [
{ booking_id: "CNR3000142", value: 412.50 },
{ booking_id: "CNR3000288", value: 180.00 },
// ...one more every day, forever
] }
Grows without bound Β· reading the name drags the whole history Β· every write relocates the document Β· a multikey index gets 5,000 entries per doc.
rides as its own collection with customer_id on each booking β exactly how our dataset is shaped.
tags, saved_addresses, fare_breakdown. If you cannot state the maximum with a straight face, reference.JavaScript REPL with the database API attached. Everything in Lectures 13β16 is written for it.
GUI: browse, build queries visually, read the inferred schema, chart explain plans.
Node, Python, Java, C#, Go. Production code β and the Lecture 20 dashboard.
mongosh "mongodb+srv://bdma_student:<password>@cluster0.xxxxx.mongodb.net/"
# safer β prompt for the password instead of typing it
mongosh "mongodb+srv://cluster0.xxxxx.mongodb.net/" --username bdma_student
.env, listed in .gitignore. Never a commit, a screenshot, or a Slack message.use bdma
db // -> bdma
show collections // -> (empty)
db.rides.insertOne({
booking_id: "CNR9999001",
booked_at: new Date("2024-03-14T18:22:07Z"),
status: "Completed",
customer_id: "CID482910",
vehicle_type: "Go Sedan",
route: { pickup: "Connaught Place", drop: "Aerocity" },
booking_value: 412.50,
ride_distance: 18.4,
ratings: { driver: 4.6, customer: 4.8 },
payment_method: "UPI"
})
db.rides.find()
show collections // -> rides
use creates nothing. The database and collection spring into existence on the first write.# in mongosh β clear your test docs first
db.rides.deleteMany({})
# in your terminal, from the repo root
mongoimport --uri "mongodb+srv://bdma_student:<password>@cluster0.xxxxx.mongodb.net/bdma" \
--collection rides --file data/uber_bookings.json --jsonArray
db.rides.countDocuments() // 15000
db.rides.countDocuments({ status: "Completed" }) // 9343
// turn booked_at from string into a real BSON Date
db.rides.updateMany(
{ booked_at: { $type: "string" } },
[ { $set: { booked_at: { $toDate: "$booked_at" } } } ]
)
booking_value appears in only ~68% of documents. Not an error β the hook for Lecture 13.Decimal128._id is mandatory; ObjectId is 12 bytes β timestamp + random + counter, so it is time-sortable and needs no coordination.$jsonSchema is how you take it back.