The full CRUD surface, the five operator families, and the discipline that keeps updateMany from ruining your afternoon.
use bdma
db.rides.countDocuments() // must say 15000
db.rides.countDocuments({ status: "Completed" }) // must say 9343
If either number is wrong, re-run the mongoimport from Lecture 12 before we go any further. Every query today assumes these 15,000 Delhi NCR bookings.
| Verb | Single | Multiple | Returns |
|---|---|---|---|
| Create | insertOne() | insertMany() | insertedId(s) |
| Read | findOne() | find() | Document / cursor |
| Update | updateOne(), replaceOne() | updateMany() | matchedCount, modifiedCount |
| Delete | deleteOne() | deleteMany() | deletedCount |
find() returns a lazy cursor, not a list. A million-document match does not pull a million documents into memory.updateOne vs replaceOne$set changes only the fields you name. replaceOne discards the whole document except _id. Forget a field and it is gone.
insertMany stops at the first failure by default. { ordered: false } attempts all and reports failures โ right for nightly bulk loads.
db.rides.updateOne(
{ booking_id: "CNR3000142" },
{ $set: { "ratings.customer": 5.0 } } // surgical
)
db.rides.insertMany([ /* ... */ ], { ordered: false })
updateOne with $set unless you genuinely mean to overwrite the entire record.| Operator | Against rides |
|---|---|
$eq $ne | { status: { $eq: "Completed" } } |
$gt $gte | { booking_value: { $gt: 500 } } |
$lt $lte | { "ratings.driver": { $lt: 3 } } |
$in $nin | { vehicle_type: { $in: ["Auto","Bike","eBike"] } } |
// A fare band โ both bounds in one operator document
db.rides.find({ booking_value: { $gte: 200, $lte: 400 } })
// Comma-separated conditions are an implicit AND
db.rides.find({ vehicle_type: "Auto", payment_method: "Cash" })
$gt: 500 is genuinely the premium tail, not an arbitrary number.$ne matches documents that lack the fielddb.rides.find({ payment_method: { $ne: "Cash" } })
This returns every non-cash payment plus all 4,756 bookings that never completed โ they have no payment_method at all, and a missing field is not equal to anything, which makes it "not equal" to everything.
"Not cash" โ and got 4,756 phantom rides in a payments report.
{ status: "Completed",
payment_method:
{ $ne: "Cash" } }
// Any failed booking
db.rides.find({ $or: [ { status: "Cancelled by Driver" },
{ status: "Cancelled by Customer" },
{ status: "No Driver Found" } ] })
// Cleaner, and it uses an index better
db.rides.find({ status: { $in: ["Cancelled by Driver",
"Cancelled by Customer",
"No Driver Found"] } })
// $nor โ neither cancelled nor incomplete
db.rides.find({ $nor: [ { status: "Cancelled by Driver" },
{ status: "Cancelled by Customer" },
{ status: "Incomplete" } ] })
$and โ commas already mean AND. Reach for it only when two conditions apply to the same field and cannot share one operator document.$exists โ because absence is datadb.rides.countDocuments({ booking_value: { $exists: false } }) // 4756
db.rides.countDocuments({ ratings: { $exists: true } }) // 9343
db.rides.countDocuments({ avg_vtat: { $exists: false } }) // 1036
db.rides.countDocuments({ booking_value: { $type: "string" } }) // 0
No avg_vtat means no driver was ever assigned. This is a marketplace supply problem.
avg_vtat present but no fare: a driver was assigned and the trip then collapsed. A reliability problem.
NULL and the distinction is lost. Here it is structural โ and the two numbers go to different teams with different fixes.// $regex โ anchored can use an index, unanchored cannot
db.rides.find({ "route.drop": { $regex: "^Gurgaon" } }) // indexable
db.rides.find({ "route.pickup": /Noida/i }) // full scan
// $all / $size / $elemMatch
db.rides.find({ tags: { $all: ["airport", "premium"] } })
db.rides.find({ tags: { $size: 2 } })
db.rides.find({ fare_breakdown:
{ $elemMatch: { component: "surge", amount: { $gt: 50 } } } })
$elemMatch existsWithout it, {"fare_breakdown.component":"surge", "fare_breakdown.amount":{$gt:50}} matches when some element is surge and some other element exceeds 50. A โน60 base plus a โน20 surge would match. Wrong answer, no error.
db.rides.find({ "route.pickup": "Indira Gandhi International Airport" })
db.rides.find({ "ratings.driver": { $lt: 3 } })
db.rides.find({ "cancellation.by": "driver" })
db.rides.find({ "incomplete.reason": "Vehicle Breakdown" })
{ "route.pickup":
"Indira Gandhi
International Airport" }
{ route: { pickup:
"Indira Gandhi
International Airport" } }
drop, so this silently returns nothing.// 1 = include, 0 = exclude. Never mix โ except _id, always suppressible.
db.rides.find(
{ status: "Completed" },
{ _id: 0, booking_id: 1, booking_value: 1, "route.pickup": 1 }
)
// INVALID
// db.rides.find({}, { booking_id: 1, ratings: 0 })
Every field returned is read from disk, serialised, sent across the network, and parsed by the client. Returning 14 fields to a dashboard that renders 3 wastes four-fifths of that work on every single request.
sort runs first. skip does not jump.// The ten most expensive completed rides. 1 = asc, -1 = desc.
db.rides.find({ status: "Completed" },
{ _id: 0, booking_id: 1, booking_value: 1 })
.sort({ booking_value: -1 }).limit(10)
| Page | Query | Docs processed to return 20 |
|---|---|---|
| 1 | .limit(20) | 20 |
| 100 | .skip(1980).limit(20) | 2,000 |
| 500 | .skip(9980).limit(20) | 10,000 |
// Range pagination โ constant work at any depth
db.rides.find({ status: "Completed",
booked_at: { $lt: ISODate("2024-11-18T09:14:00Z") } })
.sort({ booked_at: -1 }).limit(20)
| Operator | Effect | Use on rides |
|---|---|---|
$set $unset | Set / remove a field | Correct a drop point; purge a disputed rating |
$inc $mul | Add / multiply a number | โน25 goodwill credit; 1.2ร surge |
$rename | Rename a field | avg_ctat โ trip_duration_min |
$push $addToSet $pull | Append / append-if-absent / remove | Maintain a tags array |
db.rides.updateOne({ booking_id: "CNR3000142" },
{ $inc: { booking_value: -25 } })
db.rides.updateOne({ booking_id: "CNR3000142" },
{ $addToSet: { tags: "airport" } }) // no-op if already there
upsert โ update if present, insert if notdb.daily_summary.updateOne(
{ date: "2024-03-14", vehicle_type: "Go Sedan" }, // identity
{ $inc: { bookings: 1, revenue: 412.50 } }, // the change
{ upsert: true } // create if absent
)
// First call -> inserts the document
// Every later -> increments it
// upsertedId is set when it inserted, null when it updated
The new document is built from the equality terms only. A filter containing { booking_value: { $gt: 500 } } contributes nothing โ so you can upsert a record that fails your own filter. Keep upsert filters to plain equality on identity fields.
countDocuments(filter) | estimatedDocumentCount() | |
|---|---|---|
| Filter | Yes | No โ whole collection |
| Accuracy | Exact, always | Approximate (cached metadata) |
| Cost | Proportional to the match | Constant โ effectively free |
| Use for | Anything a human acts on | "Roughly how big is this?" |
db.rides.countDocuments() // 15000
db.rides.countDocuments({ payment_method: "UPI" }) // 4613
db.rides.countDocuments({ "route.pickup": "Noida Sector 62" }) // 1209
db.rides.deleteMany({})
// { acknowledged: true, deletedCount: 15000 }
db.rides.updateMany({}, { $set: { status: "Completed" } })
// every booking in the collection, rewritten
No confirmation prompt. No ROLLBACK. No undo. MongoDB has transactions โ an autocommitted updateMany is not inside one.
1. Write the filter as a find + countDocuments first, then swap the verb.
2. Default to the singular form.
3. Read matchedCount afterwards.
{ acknowledged: true,
matchedCount: 1183,
modifiedCount: 1183,
upsertedId: null }
matchedCount is far larger than you expected โ stop. Investigate before the next command.| # | Question | Skill |
|---|---|---|
| 1 | How healthy is the funnel? Total vs completed. | countDocuments |
| 2 | How much demand do we fail outright? | $exists |
| 3 | Our ten highest-value trips. | Projection + sort + limit |
| 4 | Is the airport corridor a premium segment? | Dot notation + $gt |
| 5 | Where is service quality failing? | "ratings.driver" + $lt |
| 6 | How big is the cash-handling problem? | $ne vs $in |
| 7 | Why do drivers cancel? | distinct on a nested field |
| 8 | Tag premium rides, verify, then clean up. | $set / $unset + matchedCount |
find() hands back a lazy cursor, not a list.$ne sweeps in 4,756 phantom bookings; $exists asks what you meant.skip is a trap. Page with a range filter on the sort key.find first; read matchedCount after.find retrieves, it cannot compute. The aggregation pipeline is where โน22,02,449 and 10.87 km become numbers you derive โ and where ranking the 24 hours by failure rate puts 03:00 top at 43.48% on just 46 bookings. A rate without a denominator is noise.