MongoDB Cheatsheet
Insert and Read
Use this MongoDB reference while you build software engineering projects, review code, or refresh the syntax you reach for most.
Inserting
insertOne and insertMany are the only insert methods. MongoDB generates _id as
an ObjectId when you leave it out, and the collection and database are created on the
first successful write.
db.orders.insertOne({ customer_id: ObjectId("65f1a2b3c4d5e6f701234567"), status: "paid", total: NumberDecimal("119.98"), items: [{ sku: "A-1", qty: 2, price: NumberDecimal("59.99") }], placed_at: new Date() }) // { acknowledged: true, insertedId: ObjectId("6612...") } db.orders.insertMany([ { sku: "A-1", qty: 1 }, { sku: "B-2", qty: 3 } ]) // { acknowledged: true, insertedIds: { '0': ObjectId(...), '1': ObjectId(...) } }
insertedIds is keyed by the array position, as a string, not by any field of the
document. Grab the id you need with res.insertedIds[2].
| Option | Effect |
|---|---|
{ ordered: true } | Default. Stop at the first failing document |
{ ordered: false } | Attempt every document, report all failures at the end |
{ writeConcern: { w: "majority" } } | Wait for a majority of replica set members |
{ bypassDocumentValidation: true } | Skip a $jsonSchema validator on this call |
Ordered vs unordered
This is the option people get wrong. Neither mode is a transaction. A failure part way
through an ordered insertMany leaves the earlier documents inserted.
try { db.users.insertMany( [ { _id: 1 }, { _id: 1 }, { _id: 2 } ], // second one is a duplicate key { ordered: false } ) } catch (e) { print(e.result.nInserted) // 2, both _id 1 and _id 2 landed printjson(e.writeErrors.map(w => w.index)) // [1] }
| Mode | On a duplicate at index 1 | Inserted |
|---|---|---|
ordered: true | Stops immediately, throws | Only index 0 |
ordered: false | Skips it, keeps going, throws at the end | Index 0 and index 2 |
Unordered is faster (the server may parallelize) and is what you want for an idempotent bulk load where duplicates are expected and harmless.
find and findOne
find returns a cursor, lazily. findOne returns one document or null and takes
no cursor methods.
db.orders.find() // every document, same as find({}) db.orders.find({}) // identical db.orders.find({ status: "paid" }, { total: 1, status: 1 }) db.orders.findOne({ _id: ObjectId("6612...") }) db.orders.findOne() // some single document, no order guaranteed
find() and find({}) are the same call. There is no implicit LIMIT, so on a large
collection always attach .limit() when you are just looking.
Cursor methods
Chain these on find before the cursor is consumed. Nothing is sent to the server
until you iterate.
| Method | Does |
|---|---|
.sort({ placed_at: -1 }) | Order, 1 ascending and -1 descending |
.limit(n) · .skip(n) | Window. Large skip values are slow |
.toArray() | Materialize everything into a JS array |
.forEach(d => ...) | Stream one document at a time, no full array in memory |
.map(d => ...) | Transform into an array client side |
.hasNext() · .next() | Manual iteration |
.tryNext() | Non-blocking next, returns null rather than waiting. Used with change streams |
.batchSize(n) | Documents per network round trip |
.count() | Deprecated, use countDocuments |
.itcount() | Iterate and count, mostly a debugging tool |
.projection({ a: 1 }) | Same as the second find argument |
.hint({ status: 1 }) | Force an index |
.collation({ locale: "en", strength: 2 }) | Case-insensitive compare and sort |
.maxTimeMS(2000) | Kill the operation server side after a deadline |
.allowDiskUse() | Let a blocking sort spill to disk |
.explain("executionStats") | Plan plus real counters |
.close() | Release the server cursor early |
db.orders.find({ status: "paid" }) .sort({ placed_at: -1 }) .limit(20) .forEach(o => print(`${o._id} ${o.total}`))
Idle cursors are dropped by the server after about 10 minutes. Either finish iterating
or call .close().
Counting and distinct
Two counts exist and they are not interchangeable.
| Call | Accuracy | Cost | Filter |
|---|---|---|---|
db.orders.countDocuments(filter) | Exact, reads data | Scans an index or the collection | Yes |
db.orders.estimatedDocumentCount() | Metadata estimate | Constant time | No filter allowed |
db.orders.countDocuments({ status: "paid" }) db.orders.countDocuments() // exact, still walks an index db.orders.estimatedDocumentCount() // instant, may be stale db.orders.distinct("status") // [ 'cancelled', 'paid', 'pending' ] db.orders.distinct("items.sku", { status: "paid" })
estimatedDocumentCount reads collection metadata, so it can be wrong after an unclean
shutdown, and it is not supported inside a transaction. distinct returns a single
array, so it fails once the result would exceed the 16MB BSON limit. For a big
cardinality use a $group instead.
Dot notation
One quoted string reaches into subdocuments and array positions. Quoting is mandatory,
because a bare address.city is not valid JavaScript as a key.
db.users.find({ "address.city": "Lisbon" }) db.users.find({ "address.geo.lat": { $gt: 38 } }) db.orders.find({ "items.sku": "A-1" }) // any element of items db.orders.find({ "items.0.sku": "A-1" }) // only the FIRST element db.orders.find({ "tags.2": { $exists: true } }) // has at least 3 tags
| Path | Matches |
|---|---|
"items.sku" | Any element of items whose sku matches |
"items.0.sku" | Only element zero |
"tags.0" | The first array entry, by position |
"a.b.c" | Nested subdocuments, any depth |
Numeric path segments are ambiguous by design. On an array they mean the index, and on
a subdocument with a literal "0" key they mean that key. Avoid numeric object keys.