π MongoDB Mastery: From Basics to Advanced
Master Every MongoDB Concept Through 150+ Real-World Examples Across 3 Production Datasets
π― Complete MongoDB Query Guide
This comprehensive guide covers EVERY MongoDB query operation with 4-5 examples per topic across 3 real datasets:
- π½οΈ Restaurants - 3,772 NYC restaurants with grades & locations
- π¬ Movies - 500+ films with ratings, cast, genres
- π Library - Books, authors, borrowers, loans
Download these sample datasets to follow along with all the examples in this guide. Each dataset is in JSON format and can be imported directly into MongoDB.
mongoimport --db practice --collection restaurants --file primer-dataset.json --jsonArray
π Quick Topic Navigation
π₯ 1. Retrieve All Documents
To get every document in a collection, pass an empty document {} as the filter parameter to the find() method. The query returns a cursor, which can be iterated through to access each document.
Goal: Get all restaurant documents from the collection.
// Retrieve all restaurants db.restaurants.find({}); // Same as above (empty filter is default) db.restaurants.find(); // Limit to first 10 for readability db.restaurants.find({}).limit(10); // Pretty print for better formatting db.restaurants.find({}).limit(5).pretty();
// Returns array of all 3,772 restaurant documents { "_id": ObjectId("..."), "name": "Morris Park Bake Shop", "borough": "Bronx", "cuisine": "Bakery", "address": {...}, "grades": [...] } // ... 3,772 total documents
Always use limit() when exploring data to avoid overwhelming output. In production, retrieving ALL documents can cause performance issues with large collections.
Goal: Get all movie documents.
// Get all movies db.movies.find({}); // Count total movies db.movies.countDocuments({}); // Output: 500+ // Get first 5 movies db.movies.find({}).limit(5);
{
"_id": ObjectId("..."),
"title": "The Shawshank Redemption",
"year": 1994,
"rated": "R",
"genres": ["Drama"],
"director": "Frank Darabont",
"cast": ["Tim Robbins", "Morgan Freeman"],
"imdb": { "rating": 9.3, "votes": 2500000 }
}
Goal: Get all books in the library catalog.
// Get all books db.books.find({}); // Get all authors db.authors.find({}); // Get all borrowers db.borrowers.find({}); // Get all loan records db.loans.find({});
{
"_id": ObjectId("..."),
"title": "To Kill a Mockingbird",
"author_id": ObjectId("..."),
"isbn": "978-0-06-112008-4",
"published_year": 1960,
"genre": ["Fiction", "Classic"],
"available_copies": 3,
"total_copies": 5
}
Goal: Understand how to work with the cursor returned by find().
// Store cursor in a variable var cursor = db.restaurants.find({}); // Check if cursor has more documents cursor.hasNext(); // Output: true // Get next document cursor.next(); // Iterate through all documents var cursor = db.restaurants.find({}).limit(5); while(cursor.hasNext()) { printjson(cursor.next()); } // forEach iteration db.restaurants.find({}).limit(5).forEach(function(doc) { print(doc.name); });
- The cursor is lazy - documents are fetched in batches as you iterate
- Default batch size is 101 documents for the first batch, then 16MB batches
- Cursor times out after 10 minutes of inactivity (use
noCursorTimeout()to prevent) toArray()converts entire cursor to array (use carefully with large datasets!)
Goal: Get the total count of documents without retrieving them.
// Count all restaurants db.restaurants.countDocuments({}); // Output: 3772 // Alternative (deprecated but still works) db.restaurants.count(); // Output: 3772 // Estimated count (faster for large collections) db.restaurants.estimatedDocumentCount(); // Output: ~3772
3772
estimatedDocumentCount() is much faster for large collections as it uses metadata instead of scanning all documents. Use it when exact count isn't critical.
π― 2. Filter Documents with Equality
To find documents where a specific field equals a certain value, provide a filter document with a field: value pair. You can also use the explicit $eq operator for the same result.
Goal: Find all restaurants named "Wendy'S".
// Implicit equality (most common) db.restaurants.find({ name: "Wendy'S" }); // Explicit $eq operator (same result) db.restaurants.find({ name: { $eq: "Wendy'S" } }); // Count matches db.restaurants.countDocuments({ name: "Wendy'S" }); // Output: Multiple Wendy's locations
{
"_id": ObjectId("..."),
"name": "Wendy'S",
"borough": "Brooklyn",
"cuisine": "Hamburgers",
"restaurant_id": "30112340"
}
MongoDB queries are case-sensitive by default. "Wendy'S" β "wendy's". For case-insensitive search, use regex (covered later).
Goal: Find all restaurants in Manhattan.
// Find Manhattan restaurants db.restaurants.find({ borough: "Manhattan" }).limit(10); // Count Manhattan restaurants db.restaurants.countDocuments({ borough: "Manhattan" }); // Output: 1883 // Get unique boroughs db.restaurants.distinct("borough"); // Output: ["Bronx", "Brooklyn", "Manhattan", "Queens", "Staten Island"]
Goal: Find all movies released in 1994.
// Find movies from 1994 db.movies.find({ year: 1994 }); // Count 1994 movies db.movies.countDocuments({ year: 1994 }); // Find by director db.movies.find({ director: "Christopher Nolan" }); // Find by rating db.movies.find({ rated: "PG-13" });
{
"_id": ObjectId("..."),
"title": "The Shawshank Redemption",
"year": 1994,
"director": "Frank Darabont"
}
{
"_id": ObjectId("..."),
"title": "Pulp Fiction",
"year": 1994,
"director": "Quentin Tarantino"
}
Goal: Find a specific book by ISBN.
// Find book by ISBN db.books.findOne({ isbn: "978-0-06-112008-4" }); // Find all books published in 1960 db.books.find({ published_year: 1960 }); // Find books by author (using author_id) db.books.find({ author_id: ObjectId("...") });
Goal: Find restaurants in a specific zipcode.
// Dot notation for nested fields db.restaurants.find({ "address.zipcode": "10462" }); // Find by building number db.restaurants.find({ "address.building": "1007" }); // Find by street db.restaurants.find({ "address.street": "Morris Park Ave" });
Use dot notation to access nested fields: "address.zipcode". Always wrap in quotes to prevent JavaScript errors.
π¦ 3. Querying Embedded Documents
MongoDB documents can contain nested documents (objects within objects). To query fields within embedded documents, use dot notation: "parent.child". Always wrap the field path in quotes.
- Dot Notation: Access nested fields using
"parent.child.grandchild" - Quote Required: Always wrap dotted paths in quotes to avoid JavaScript errors
- Multiple Levels: Can go as deep as needed:
"level1.level2.level3" - Exact Match: Query entire embedded document with exact structure match
Goal: Find restaurants using nested address fields (zipcode, street, building).
{
"address": {
"building": "1007",
"coord": [-73.856077, 40.848447],
"street": "Morris Park Ave",
"zipcode": "10462"
}
}
// Find restaurants in specific zipcode db.restaurants.find({ "address.zipcode": "10462" }); // Find by building number db.restaurants.find({ "address.building": "1007" }); // Find by street name db.restaurants.find({ "address.street": "Morris Park Ave" }); // Combine multiple nested fields db.restaurants.find({ "address.zipcode": "10462", "address.street": "Morris Park Ave" });
{
"_id": ObjectId("..."),
"name": "Morris Park Bake Shop",
"borough": "Bronx",
"cuisine": "Bakery",
"address": {
"building": "1007",
"coord": [-73.856077, 40.848447],
"street": "Morris Park Ave",
"zipcode": "10462"
}
}
WRONG: address.zipcode (JavaScript interprets as object property)
RIGHT: "address.zipcode" (MongoDB interprets as field path)
Goal: Find restaurants with specific grades (array of embedded documents).
{
"grades": [
{ "date": ISODate("2014-10-01"), "grade": "A", "score": 11 },
{ "date": ISODate("2013-05-14"), "grade": "B", "score": 17 }
]
}
// Find restaurants with grade "A" (searches all array elements) db.restaurants.find({ "grades.grade": "A" }); // Find restaurants with score less than 10 db.restaurants.find({ "grades.score": { $lt: 10 } }); // Find restaurants inspected after specific date db.restaurants.find({ "grades.date": { $gt: ISODate("2014-01-01") } }); // Combine conditions in array elements db.restaurants.find({ "grades.grade": "A", "grades.score": { $lt: 5 } });
When querying "grades.grade": "A", MongoDB checks if ANY element in the array matches. It doesn't require the same element to match all conditions (use $elemMatch for that).
Goal: Query nested rating information in movie documents.
{
"title": "Inception",
"imdb": {
"rating": 8.8,
"votes": 2000000,
"id": "tt1375666"
},
"tomatoes": {
"viewer": { "rating": 4.2, "numReviews": 120000 },
"critic": { "rating": 87, "numReviews": 250 }
}
}
// Find highly rated movies on IMDB db.movies.find({ "imdb.rating": { $gte: 8.5 } }); // Find movies with many IMDB votes db.movies.find({ "imdb.votes": { $gte: 1000000 } }); // Find by IMDB ID db.movies.find({ "imdb.id": "tt1375666" }); // Multi-level nesting: viewer ratings db.movies.find({ "tomatoes.viewer.rating": { $gte: 4.0 } }); // Critic score above threshold db.movies.find({ "tomatoes.critic.rating": { $gte: 80 } }); // Combine IMDB and Tomatoes criteria db.movies.find({ "imdb.rating": { $gte: 8.0 }, "tomatoes.critic.rating": { $gte: 75 } });
{
"_id": ObjectId("..."),
"title": "Inception",
"year": 2010,
"imdb": {
"rating": 8.8,
"votes": 2000000,
"id": "tt1375666"
},
"tomatoes": {
"viewer": { "rating": 4.2 },
"critic": { "rating": 87 }
}
}
Goal: Query nested publisher information in book records.
{
"title": "To Kill a Mockingbird",
"publisher": {
"name": "HarperCollins",
"location": "New York",
"year": 1960
},
"metadata": {
"pages": 324,
"language": "English",
"isbn": "978-0-06-112008-4"
}
}
// Find books by specific publisher db.books.find({ "publisher.name": "HarperCollins" }); // Find books published in New York db.books.find({ "publisher.location": "New York" }); // Find books by publication year db.books.find({ "publisher.year": { $gte: 1950, $lte: 1970 } }); // Query book metadata - page count db.books.find({ "metadata.pages": { $gte: 300 } }); // Find by language db.books.find({ "metadata.language": "English" }); // Find by ISBN in metadata db.books.find({ "metadata.isbn": "978-0-06-112008-4" });
Goal: Match an entire embedded document exactly (field order matters!).
// Exact match - ALL fields must match in EXACT order db.restaurants.find({ "address": { "building": "1007", "coord": [-73.856077, 40.848447], "street": "Morris Park Ave", "zipcode": "10462" } }); // β This will NOT match if field order differs db.restaurants.find({ "address": { "zipcode": "10462", // Wrong order! "building": "1007", "street": "Morris Park Ave", "coord": [-73.856077, 40.848447] } }); // β Better: Use dot notation for flexible matching db.restaurants.find({ "address.building": "1007", "address.zipcode": "10462" });
- Field order must be exactly the same
- All fields must be included (no partial matching)
- Generally avoid exact matching - use dot notation instead
- Dot Notation: Use
"parent.child"(in quotes) to query nested fields - Multi-Level: Go as deep as needed:
"level1.level2.level3" - Arrays: Dot notation searches any array element automatically
- Avoid Exact Match: Matching entire embedded docs is fragile; prefer dot notation
- Flexible Queries: Combine multiple nested fields with operators like
$gte,$lt, etc.
βοΈ Comparison Operators
MongoDB offers various comparison operators to filter documents based on value comparisons. These work with numbers, dates, and strings.
| Operator | Meaning | Example |
|---|---|---|
| $eq | Equals | { age: { $eq: 25 } } |
| $gt | Greater than | { age: { $gt: 25 } } |
| $gte | Greater than or equal | { age: { $gte: 25 } } |
| $lt | Less than | { age: { $lt: 25 } } |
| $lte | Less than or equal | { age: { $lte: 25 } } |
| $ne | Not equal | { age: { $ne: 25 } } |
| $in | Matches any value in array | { age: { $in: [25, 30, 35] } } |
| $nin | Matches none of the values | { age: { $nin: [25, 30] } } |
πΌ $gt (Greater Than) & $lt (Less Than)
Goal: Find restaurants where the most recent inspection score is greater than 30 (poor score).
// Find restaurants with score > 30 in most recent inspection db.restaurants.find({ "grades.0.score": { $gt: 30 } }); // Count them db.restaurants.countDocuments({ "grades.0.score": { $gt: 30 } }); // Show only name and score db.restaurants.find( { "grades.0.score": { $gt: 30 } }, { name: 1, "grades.0.score": 1, _id: 0 } ).limit(5);
{
"name": "Brunos On The Boulevard",
"grades": [{ "score": 38, ... }]
}
// High scores mean more violations (bad!)
In the restaurants dataset, lower scores are better. Score > 30 indicates significant health violations. grades[0] or grades.0 refers to the most recent inspection.
Goal: Find movies released after 2010.
// Find movies after 2010 db.movies.find({ year: { $gt: 2010 } }); // Count modern movies db.movies.countDocuments({ year: { $gt: 2010 } }); // Find recent action movies db.movies.find({ year: { $gt: 2010 }, genres: "Action" });
{
"_id": ObjectId("..."),
"title": "Inception",
"year": 2010,
"genres": ["Action", "Sci-Fi", "Thriller"]
}
Goal: Find movies with IMDb rating greater than 8.5.
// Find highly-rated movies (rating > 8.5) db.movies.find({ "imdb.rating": { $gt: 8.5 } }); // Sort by rating descending db.movies.find({ "imdb.rating": { $gt: 8.5 } }) .sort({ "imdb.rating": -1 }) .limit(10); // Show title and rating only db.movies.find( { "imdb.rating": { $gt: 8.5 } }, { title: 1, "imdb.rating": 1, year: 1, _id: 0 } );
{
"title": "The Shawshank Redemption",
"imdb": { "rating": 9.3 },
"year": 1994
}
{
"title": "The Godfather",
"imdb": { "rating": 9.2 },
"year": 1972
}
Goal: Find books published before 1950 (classic literature).
// Find books published before 1950 db.books.find({ published_year: { $lt: 1950 } }); // Count classic books db.books.countDocuments({ published_year: { $lt: 1950 } }); // Find classics with available copies db.books.find({ published_year: { $lt: 1950 }, available_copies: { $gt: 0 } });
{
"_id": ObjectId("..."),
"title": "The Great Gatsby",
"published_year": 1925,
"available_copies": 2
}
Goal: Find excellent restaurants with recent score less than 5.
// Find restaurants with excellent scores (< 5) db.restaurants.find({ "grades.0.score": { $lt: 5 } }); // Find excellent Italian restaurants in Manhattan db.restaurants.find({ "grades.0.score": { $lt: 5 }, cuisine: "Italian", borough: "Manhattan" }).limit(10);
π $gte & $lte (Greater/Less Than or Equal)
Goal: Find all movies from the 1990s (1990-1999).
// Find 90s movies using range db.movies.find({ year: { $gte: 1990, $lte: 1999 } }); // Count 90s movies db.movies.countDocuments({ year: { $gte: 1990, $lte: 1999 } }); // Find 90s action movies with good ratings db.movies.find({ year: { $gte: 1990, $lte: 1999 }, genres: "Action", "imdb.rating": { $gte: 7.0 } }).sort({ "imdb.rating": -1 });
{
"title": "The Matrix",
"year": 1999,
"genres": ["Action", "Sci-Fi"],
"imdb": { "rating": 8.7 }
}
{
"title": "Pulp Fiction",
"year": 1994,
"genres": ["Crime", "Drama"],
"imdb": { "rating": 8.9 }
}
Combine $gte and $lte to create range queries: { field: { $gte: min, $lte: max } }. Works with numbers, dates, and strings.
Goal: Find restaurants with acceptable scores (between 10 and 20).
// Find restaurants with moderate scores (10-20) db.restaurants.find({ "grades.0.score": { $gte: 10, $lte: 20 } }); // By cuisine and borough db.restaurants.find({ "grades.0.score": { $gte: 10, $lte: 20 }, cuisine: "Chinese", borough: "Queens" });
Goal: Find books published between 1950 and 2000.
// Find mid-20th century books db.books.find({ published_year: { $gte: 1950, $lte: 2000 } }); // By genre db.books.find({ published_year: { $gte: 1950, $lte: 2000 }, genre: "Science Fiction" });
β $ne (Not Equal)
Goal: Find all restaurants NOT in Manhattan.
// Find non-Manhattan restaurants db.restaurants.find({ borough: { $ne: "Manhattan" } }); // Count them db.restaurants.countDocuments({ borough: { $ne: "Manhattan" } }); // Find Italian food outside Manhattan db.restaurants.find({ borough: { $ne: "Manhattan" }, cuisine: "Italian" });
// Returns restaurants from: // Bronx, Brooklyn, Queens, Staten Island // Total: ~1,889 restaurants (3,772 - 1,883)
Goal: Find movies that are NOT rated PG-13.
// Exclude PG-13 movies db.movies.find({ rated: { $ne: "PG-13" } }); // Find R-rated or unrated db.movies.find({ rated: { $ne: "PG-13" }, year: { $gte: 2000 } });
π $in & $nin (In Array / Not In Array)
Goal: Find restaurants in Manhattan OR Brooklyn OR Queens.
// Find restaurants in multiple boroughs db.restaurants.find({ borough: { $in: ["Manhattan", "Brooklyn", "Queens"] } }); // Count them db.restaurants.countDocuments({ borough: { $in: ["Manhattan", "Brooklyn", "Queens"] } }); // Find Italian in these boroughs db.restaurants.find({ borough: { $in: ["Manhattan", "Brooklyn", "Queens"] }, cuisine: "Italian" });
$in is shorthand for multiple $or conditions on the SAME field. Use $in when checking one field against multiple values.
Goal: Find Italian, Chinese, or Japanese restaurants.
// Find multiple cuisine types db.restaurants.find({ cuisine: { $in: ["Italian", "Chinese", "Japanese"] } }); // In Manhattan only db.restaurants.find({ cuisine: { $in: ["Italian", "Chinese", "Japanese"] }, borough: "Manhattan" });
Goal: Find restaurants NOT in Manhattan or Bronx.
// Exclude multiple boroughs db.restaurants.find({ borough: { $nin: ["Manhattan", "Bronx"] } }); // This returns: Brooklyn, Queens, Staten Island db.restaurants.countDocuments({ borough: { $nin: ["Manhattan", "Bronx"] } });
Goal: Find Action, Thriller, or Sci-Fi movies.
// Find multiple genres db.movies.find({ genres: { $in: ["Action", "Thriller", "Sci-Fi"] } }); // Recent ones with good ratings db.movies.find({ genres: { $in: ["Action", "Thriller", "Sci-Fi"] }, year: { $gte: 2010 }, "imdb.rating": { $gte: 7.0 } });
π Logical Operators
Logical operators allow you to combine multiple conditions. They work like Boolean logic (AND, OR, NOT).
| Operator | Meaning | Example |
|---|---|---|
| $and | All conditions must be true | { $and: [{ age: 25 }, { city: "NYC" }] } |
| $or | At least one condition must be true | { $or: [{ age: 25 }, { city: "NYC" }] } |
| $not | Inverts the condition | { age: { $not: { $eq: 25 } } } |
| $nor | None of the conditions can be true | { $nor: [{ age: 25 }, { city: "NYC" }] } |
β $and (All Conditions Must Be True)
Goal: Find Italian restaurants in Manhattan with grade A.
// Implicit AND (listing conditions together) db.restaurants.find({ cuisine: "Italian", borough: "Manhattan", "grades.0.grade": "A" }); // Explicit $and (same result, verbose) db.restaurants.find({ $and: [ { cuisine: "Italian" }, { borough: "Manhattan" }, { "grades.0.grade": "A" } ] });
By default, multiple conditions in a query are combined with AND. Use explicit $and only when you need the same field with different operators.
Goal: Find restaurants with score > 10 AND score < 30 (same field, multiple conditions).
// When you need multiple conditions on same field db.restaurants.find({ $and: [ { "grades.0.score": { $gt: 10 } }, { "grades.0.score": { $lt: 30 } } ] }); // More common: combine in one object (preferred) db.restaurants.find({ "grades.0.score": { $gt: 10, $lt: 30 } });
π $or (At Least One Condition Must Be True)
Goal: Find restaurants that are EITHER Italian OR Chinese.
// Find Italian OR Chinese db.restaurants.find({ $or: [ { cuisine: "Italian" }, { cuisine: "Chinese" } ] }); // Note: For same field, $in is more efficient db.restaurants.find({ cuisine: { $in: ["Italian", "Chinese"] } });
Goal: Find restaurants in Manhattan OR with excellent score.
// Manhattan OR excellent score db.restaurants.find({ $or: [ { borough: "Manhattan" }, { "grades.0.score": { $lt: 5 } } ] }); // Find Italian in Manhattan OR Brooklyn db.restaurants.find({ cuisine: "Italian", $or: [ { borough: "Manhattan" }, { borough: "Brooklyn" } ] });
Goal: Find movies that are EITHER after 2015 OR have rating > 8.5.
// Recent OR highly rated
db.movies.find({
$or: [
{ year: { $gt: 2015 } },
{ "imdb.rating": { $gt: 8.5 } }
]
}).sort({ "imdb.rating": -1 });
π― Combined AND + OR Operators
Goal: Find Italian restaurants in (Manhattan OR Brooklyn) with grade A.
// Italian AND (Manhattan OR Brooklyn) AND grade A db.restaurants.find({ cuisine: "Italian", $or: [ { borough: "Manhattan" }, { borough: "Brooklyn" } ], "grades.0.grade": "A" }); // Alternative using $in (cleaner for same field) db.restaurants.find({ cuisine: "Italian", borough: { $in: ["Manhattan", "Brooklyn"] }, "grades.0.grade": "A" });
Goal: Find Action movies from (90s OR 2000s) with rating β₯ 7.0.
// Action AND (90s OR 2000s) AND rating >= 7
db.movies.find({
genres: "Action",
$or: [
{ year: { $gte: 1990, $lte: 1999 } },
{ year: { $gte: 2000, $lte: 2009 } }
],
"imdb.rating": { $gte: 7.0 }
}).sort({ "imdb.rating": -1 });
Goal: Find Fiction OR Mystery books published after 1980 with available copies.
// (Fiction OR Mystery) AND year > 1980 AND available
db.books.find({
$or: [
{ genre: "Fiction" },
{ genre: "Mystery" }
],
published_year: { $gt: 1980 },
available_copies: { $gt: 0 }
});
π’ Limiting Results
The limit() method restricts the number of documents returned by a query. Essential for pagination and preventing overwhelming results.
Goal: Get only the first 10 restaurants.
// Get first 10 restaurants db.restaurants.find().limit(10); // Get first 5 Italian restaurants db.restaurants.find({ cuisine: "Italian" }).limit(5); // Get first 3 Manhattan restaurants with grade A db.restaurants.find({ borough: "Manhattan", "grades.0.grade": "A" }).limit(3);
// Returns exactly 10, 5, and 3 documents respectively // Regardless of how many match the filter
Goal: Get top 10 highest-rated movies (requires sort + limit).
// Top 10 by rating (we'll add sort in next section) db.movies.find({ "imdb.rating": { $exists: true } }) .limit(10); // Top 5 recent movies (after 2015) db.movies.find({ year: { $gte: 2015 } }).limit(5);
π Sorting Results
The sort() method orders documents by one or more fields. Use 1 for ascending, -1 for descending.
Goal: Sort restaurants alphabetically by name.
// Sort A-Z (ascending) db.restaurants.find().sort({ name: 1 }).limit(10); // Sort Z-A (descending) db.restaurants.find().sort({ name: -1 }).limit(10); // Sort Italian restaurants by name db.restaurants.find({ cuisine: "Italian" }) .sort({ name: 1 }) .limit(10);
// Ascending (1): A, B, C... { "name": "1 East 66Th Street Kitchen", ... } { "name": "Bagels N Buns", ... } { "name": "Berkely", ... } // Descending (-1): Z, Y, X... { "name": "Wild Asia", ... } { "name": "Wendy'S", ... }
Goal: Find restaurants with best scores (lowest numbers).
// Best scores first (ascending - lower is better) db.restaurants.find() .sort({ "grades.0.score": 1 }) .limit(10); // Worst scores first (descending) db.restaurants.find() .sort({ "grades.0.score": -1 }) .limit(10); // Manhattan restaurants, best scores first db.restaurants.find({ borough: "Manhattan" }) .sort({ "grades.0.score": 1 }) .limit(10);
Goal: Get top-rated movies sorted by IMDb rating.
// Top rated movies (highest rating first) db.movies.find() .sort({ "imdb.rating": -1 }) .limit(10); // Top rated action movies db.movies.find({ genres: "Action" }) .sort({ "imdb.rating": -1 }) .limit(10); // Recent movies, highest rated first db.movies.find({ year: { $gte: 2015 } }) .sort({ "imdb.rating": -1 }) .limit(10);
{
"title": "The Shawshank Redemption",
"imdb": { "rating": 9.3 }
}
{
"title": "The Godfather",
"imdb": { "rating": 9.2 }
}
Goal: Sort by multiple fields (borough, then name).
// Sort by borough (ascending), then name (ascending) db.restaurants.find() .sort({ borough: 1, name: 1 }) .limit(20); // Sort by cuisine, then score (best first) db.restaurants.find() .sort({ cuisine: 1, "grades.0.score": 1 }) .limit(20); // Italian restaurants: borough, then score db.restaurants.find({ cuisine: "Italian" }) .sort({ borough: 1, "grades.0.score": 1 }) .limit(10);
When sorting by multiple fields, MongoDB sorts by the first field, then uses subsequent fields as tie-breakers. Order matters!
Goal: Sort by year (newest first), then by rating (highest first).
// Newest first, then highest rated db.movies.find() .sort({ year: -1, "imdb.rating": -1 }) .limit(20); // Action movies: year descending, rating descending db.movies.find({ genres: "Action" }) .sort({ year: -1, "imdb.rating": -1 }) .limit(10);
Goal: Sort books by publication year (oldest first).
// Oldest books first db.books.find() .sort({ published_year: 1 }) .limit(10); // Available books, newest first db.books.find({ available_copies: { $gt: 0 } }) .sort({ published_year: -1 }) .limit(10); // Fiction books by title A-Z db.books.find({ genre: "Fiction" }) .sort({ title: 1 }) .limit(20);
βοΈ Skip & Pagination
The skip() method bypasses a specified number of documents. Combine with limit() for pagination.
Goal: Skip the first 10 restaurants, get next 10.
// Skip first 10, get next 10 db.restaurants.find().skip(10).limit(10); // Skip first 20, get next 10 db.restaurants.find().skip(20).limit(10); // Italian restaurants: skip 5, get 5 db.restaurants.find({ cuisine: "Italian" }) .skip(5) .limit(5);
Goal: Implement pagination (10 results per page).
// Page 1: skip 0, limit 10 var page = 1; var pageSize = 10; db.movies.find() .skip((page - 1) * pageSize) .limit(pageSize); // Page 2: skip 10, limit 10 page = 2; db.movies.find() .skip((page - 1) * pageSize) .limit(pageSize); // Page 3: skip 20, limit 10 page = 3; db.movies.find() .skip((page - 1) * pageSize) .limit(pageSize);
skip((page - 1) * pageSize).limit(pageSize)
- Page 1: skip(0), limit(10)
- Page 2: skip(10), limit(10)
- Page 3: skip(20), limit(10)
Goal: Paginate through Italian restaurants in Manhattan.
// Setup pagination variables var page = 1; var pageSize = 20; var query = { cuisine: "Italian", borough: "Manhattan" }; // Get total count for pagination info var totalCount = db.restaurants.countDocuments(query); var totalPages = Math.ceil(totalCount / pageSize); // Get page 1 db.restaurants.find(query) .sort({ name: 1 }) .skip((page - 1) * pageSize) .limit(pageSize); // Result info print("Showing page " + page + " of " + totalPages); print("Total results: " + totalCount);
For large skip values (>1000), skip() can be slow because MongoDB still scans skipped documents. For better performance with large datasets, use range-based pagination (query > lastSeenId).
π¬ Projection (Selecting Fields)
Projection allows you to include or exclude specific fields from query results. Use 1 to include, 0 to exclude.
Goal: Get only name, borough, and cuisine fields.
// Include only specific fields db.restaurants.find( {}, { name: 1, borough: 1, cuisine: 1 } ).limit(5); // Same but exclude _id db.restaurants.find( {}, { name: 1, borough: 1, cuisine: 1, _id: 0 } ).limit(5); // Italian restaurants: name and address only db.restaurants.find( { cuisine: "Italian" }, { name: 1, "address.street": 1, _id: 0 } ).limit(10);
{
"name": "Morris Park Bake Shop",
"borough": "Bronx",
"cuisine": "Bakery"
}
// No _id, address, grades, or restaurant_id
1= include field0= exclude field- Cannot mix inclusion and exclusion (except _id)
_idis included by default - use_id: 0to exclude
Goal: Get all fields EXCEPT grades and restaurant_id.
// Exclude specific fields db.restaurants.find( {}, { grades: 0, restaurant_id: 0 } ).limit(5); // Exclude only _id db.restaurants.find( {}, { _id: 0 } ).limit(5);
Goal: Get name and only the zipcode from address.
// Project nested fields with dot notation db.restaurants.find( {}, { name: 1, "address.zipcode": 1, _id: 0 } ).limit(10); // Name, street, and zipcode db.restaurants.find( {}, { name: 1, "address.street": 1, "address.zipcode": 1, _id: 0 } ).limit(10); // Most recent grade only db.restaurants.find( {}, { name: 1, "grades.0": 1, _id: 0 } ).limit(10);
{
"name": "Morris Park Bake Shop",
"address": {
"zipcode": "10462"
}
}
Goal: Get only title, year, and IMDb rating.
// Movie essentials db.movies.find( {}, { title: 1, year: 1, "imdb.rating": 1, _id: 0 } ).limit(10); // Action movies: title, year, rating, genres db.movies.find( { genres: "Action" }, { title: 1, year: 1, "imdb.rating": 1, genres: 1, _id: 0 } ).sort({ "imdb.rating": -1 }) .limit(10);
Goal: Get only the first 2 grades from the grades array.
// Get first 2 grades only db.restaurants.find( {}, { name: 1, grades: { $slice: 2 }, _id: 0 } ).limit(5); // Get last 2 grades db.restaurants.find( {}, { name: 1, grades: { $slice: -2 }, _id: 0 } ).limit(5); // Get 2 grades, starting from 2nd (skip 1, take 2) db.restaurants.find( {}, { name: 1, grades: { $slice: [1, 2] }, _id: 0 } ).limit(5);
{ $slice: 3 }- first 3 elements{ $slice: -3 }- last 3 elements{ $slice: [2, 3] }- skip 2, return 3
Goal: Get title, author, and availability info only.
// Book catalog view db.books.find( {}, { title: 1, author_id: 1, available_copies: 1, total_copies: 1, _id: 0 } ).limit(10); // Available books only with minimal info db.books.find( { available_copies: { $gt: 0 } }, { title: 1, genre: 1, available_copies: 1, _id: 0 } ).limit(20);
π Array Queries
MongoDB provides specialized operators for querying arrays effectively.
| Operator | Purpose | Example |
|---|---|---|
$all |
Array contains ALL specified values | { tags: { $all: ["red", "blue"] } } |
$elemMatch |
At least one array element matches ALL conditions | { grades: { $elemMatch: { grade: "A", score: { $lt: 5 } } } } |
$size |
Array has exactly this many elements | { tags: { $size: 3 } } |
π― $elemMatch (Match Array Element)
Goal: Find restaurants with at least one inspection that has grade "A" AND score < 5.
// At least one grade element with BOTH conditions db.restaurants.find({ grades: { $elemMatch: { grade: "A", score: { $lt: 5 } } } }).limit(10); // Italian restaurants with perfect inspection db.restaurants.find({ cuisine: "Italian", grades: { $elemMatch: { grade: "A", score: { $lte: 3 } } } }).limit(10);
Without $elemMatch, conditions might match DIFFERENT array elements:
{ "grades.grade": "A", "grades.score": {$lt: 5} }- grade A and score <5 could be in different elements{ grades: { $elemMatch: {...} } }- SAME element must match ALL conditions
Goal: Assuming movies have a cast array with objects, find specific actor conditions.
// Example: Find movies with multiple genres including Action db.movies.find({ genres: { $all: ["Action", "Thriller"] } }).limit(10); // If cast were objects: { name: "...", role: "..." } // db.movies.find({ // cast: { // $elemMatch: { // name: "Tom Hanks", // role: "Lead" // } // } // });
π $size (Array Length)
Goal: Find restaurants with exactly 5 inspections.
// Exactly 5 grades/inspections db.restaurants.find({ grades: { $size: 5 } }).limit(10); // Exactly 3 inspections db.restaurants.find({ grades: { $size: 3 } }).limit(10); // Count restaurants with exactly 5 grades db.restaurants.countDocuments({ grades: { $size: 5 } });
$size only accepts exact numbers. You CANNOT use ranges like { $size: { $gt: 3 } }. For range queries on array length, you need to maintain a separate count field or use aggregation.
Goal: Find movies with exactly 3 genres.
// Movies with exactly 3 genres db.movies.find({ genres: { $size: 3 } }).limit(10); // Single genre movies db.movies.find({ genres: { $size: 1 } }).limit(10);
β $all (Contains All Values)
Goal: Find movies that have BOTH "Action" AND "Thriller" genres.
// Must have BOTH Action AND Thriller db.movies.find({ genres: { $all: ["Action", "Thriller"] } }).limit(10); // Must have Action, Thriller, AND Sci-Fi db.movies.find({ genres: { $all: ["Action", "Thriller", "Sci-Fi"] } }).limit(10); // High-rated action thrillers db.movies.find({ genres: { $all: ["Action", "Thriller"] }, "imdb.rating": { $gte: 7.0 } }).sort({ "imdb.rating": -1 }) .limit(10);
$in- Array contains ANY of these values$all- Array contains ALL of these values
Goal: Find books categorized as both Fiction AND Classic.
// Books with both Fiction and Classic genres db.books.find({ genre: { $all: ["Fiction", "Classic"] } }).limit(10); // Available books with multiple genres db.books.find({ genre: { $all: ["Fiction", "Classic"] }, available_copies: { $gt: 0 } }).limit(10);
βοΈ Update Operations Overview
MongoDB provides several methods to modify documents. Understanding when to use each is crucial.
| Method | Purpose | Returns |
|---|---|---|
updateOne() |
Update first document matching filter | { matchedCount, modifiedCount } |
updateMany() |
Update all documents matching filter | { matchedCount, modifiedCount } |
replaceOne() |
Replace entire document (except _id) | { matchedCount, modifiedCount } |
findOneAndUpdate() |
Update and return the document | The updated document |
NEVER use update() without update operators! Always use $set, $inc, etc. Otherwise, you'll replace the entire document!
π― updateOne() - Update Single Document
Goal: Update the name of a specific restaurant.
// Update one restaurant's name db.restaurants.updateOne( { restaurant_id: "30075445" }, { $set: { name: "Morris Park Bakery & Cafe" } } ); // Check the result db.restaurants.findOne({ restaurant_id: "30075445" });
{
"acknowledged": true,
"matchedCount": 1,
"modifiedCount": 1
}
// matchedCount: how many documents matched the filter
// modifiedCount: how many were actually changed
matchedCount: 1- Found 1 document matching the filtermodifiedCount: 1- Changed 1 documentmodifiedCount: 0- Document matched but value was already the same (no change needed)
Goal: Update the IMDb rating for a specific movie.
// Update nested field (imdb.rating) db.movies.updateOne( { title: "The Shawshank Redemption" }, { $set: { "imdb.rating": 9.3, "imdb.votes": 2500000 } } ); // Verify the update db.movies.findOne( { title: "The Shawshank Redemption" }, { title: 1, imdb: 1, _id: 0 } );
Goal: Update available copies when a book is borrowed.
// Decrement available copies (we'll use $inc later, but showing $set) db.books.updateOne( { isbn: "978-0-06-112008-4" }, { $set: { available_copies: 2 } } ); // Better: check current value first var book = db.books.findOne({ isbn: "978-0-06-112008-4" }); db.books.updateOne( { isbn: "978-0-06-112008-4" }, { $set: { available_copies: book.available_copies - 1 } } );
This works, but using $inc is cleaner for incrementing/decrementing. We'll cover that soon!
π―π― updateMany() - Update Multiple Documents
Goal: Add a new field "verified: true" to all Manhattan restaurants.
// Add field to all Manhattan restaurants db.restaurants.updateMany( { borough: "Manhattan" }, { $set: { verified: true } } ); // Verify - count updated documents db.restaurants.countDocuments({ borough: "Manhattan", verified: true });
{
"acknowledged": true,
"matchedCount": 1883,
"modifiedCount": 1883
}
// All 1,883 Manhattan restaurants now have verified: true
Goal: Add "decade" field to all 90s movies.
// Add decade field to 90s movies db.movies.updateMany( { year: { $gte: 1990, $lte: 1999 } }, { $set: { decade: "1990s" } } ); // Add to 2000s movies db.movies.updateMany( { year: { $gte: 2000, $lte: 2009 } }, { $set: { decade: "2000s" } } ); // Verify db.movies.find({ decade: "1990s" }).count();
Goal: Mark all old books (pre-1950) as "classic".
// Mark classics db.books.updateMany( { published_year: { $lt: 1950 } }, { $set: { category: "classic", special_handling: true } } ); // Count classics db.books.countDocuments({ category: "classic" });
π§ $set & $unset - Modify Fields
$set - Creates or updates fields
$unset - Removes fields from documents
Goal: Add multiple new fields at once.
// Add multiple fields db.restaurants.updateOne( { restaurant_id: "30075445" }, { $set: { phone: "718-822-5200", email: "info@morrispark.com", website: "www.morrispark.com", "contact.manager": "John Smith" } } ); // $set creates nested objects if they don't exist db.restaurants.updateOne( { restaurant_id: "30075445" }, { $set: { "hours.monday": "9AM-9PM", "hours.tuesday": "9AM-9PM" } } );
- If field exists β updates it
- If field doesn't exist β creates it
- Can create nested objects/arrays
- Safe to use - won't replace entire document
Goal: Remove the "verified" field we added earlier.
// Remove single field db.restaurants.updateMany( { borough: "Manhattan" }, { $unset: { verified: "" } } ); // Remove multiple fields (value doesn't matter, use "" or 1) db.restaurants.updateOne( { restaurant_id: "30075445" }, { $unset: { phone: "", email: "", website: "" } } ); // Verify field is gone db.restaurants.findOne( { restaurant_id: "30075445" }, { verified: 1, phone: 1 } );
Goal: Update IMDb data and add awards field.
// Update nested imdb object
db.movies.updateOne(
{ title: "The Godfather" },
{
$set: {
"imdb.rating": 9.2,
"imdb.votes": 1800000,
"awards.oscars": 3,
"awards.nominations": 11
}
}
);
Goal: Add and remove fields from book documents.
// Add condition rating db.books.updateOne( { isbn: "978-0-06-112008-4" }, { $set: { condition: "excellent", last_checked: new Date() } } ); // Remove temporary field db.books.updateMany( {}, { $unset: { temp_field: "" } } );
β $inc & $mul - Numeric Operations
$inc - Increment/decrement by value
$mul - Multiply by value
$min - Update if new value is less
$max - Update if new value is greater
Goal: Increment IMDb votes by 100.
// Increment votes by 100 db.movies.updateOne( { title: "Inception" }, { $inc: { "imdb.votes": 100 } } ); // Decrement (use negative value) db.movies.updateOne( { title: "Inception" }, { $inc: { "imdb.votes": -50 } } ); // Multiple increments at once db.movies.updateOne( { title: "Inception" }, { $inc: { "imdb.votes": 100, view_count: 1 } } );
- Atomic operation - no race conditions
- Don't need to read current value first
- Works with positive or negative values
- Creates field if it doesn't exist (starts at 0)
Goal: Decrement available copies when book is borrowed.
// Book borrowed - decrement available db.books.updateOne( { isbn: "978-0-06-112008-4" }, { $inc: { available_copies: -1, total_borrowed: 1 } } ); // Book returned - increment available db.books.updateOne( { isbn: "978-0-06-112008-4" }, { $inc: { available_copies: 1 } } ); // Verify the update db.books.findOne( { isbn: "978-0-06-112008-4" }, { title: 1, available_copies: 1, total_borrowed: 1, _id: 0 } );
Goal: Apply 10% late fee increase using $mul.
// Increase late fee by 10% (multiply by 1.1) db.books.updateMany( { late_fee: { $exists: true } }, { $mul: { late_fee: 1.1 } } ); // Apply 20% discount (multiply by 0.8) db.books.updateMany( { price: { $exists: true } }, { $mul: { price: 0.8 } } ); // Double a value db.books.updateOne( { isbn: "978-0-06-112008-4" }, { $mul: { popularity_score: 2 } } );
π Array Update Operators
$push |
Add element to array |
$pop |
Remove first or last element |
$pull |
Remove elements matching condition |
$pullAll |
Remove all matching values |
$addToSet |
Add only if not already present |
β $push & $pop - Add/Remove Array Elements
Goal: Add a new inspection/grade to the grades array.
// Add new grade to the end of array db.restaurants.updateOne( { restaurant_id: "30075445" }, { $push: { grades: { date: new Date("2024-12-15"), grade: "A", score: 3 } } } ); // Add to beginning (use $position with $each) db.restaurants.updateOne( { restaurant_id: "30075445" }, { $push: { grades: { $each: [{ date: new Date("2024-12-15"), grade: "A", score: 3 }], $position: 0 } } } );
Goal: Add a new genre to a movie.
// Add single genre db.movies.updateOne( { title: "Inception" }, { $push: { genres: "Mystery" } } ); // Add multiple genres at once db.movies.updateOne( { title: "Inception" }, { $push: { genres: { $each: ["Mystery", "Adventure"] } } } ); // Add to cast array db.movies.updateOne( { title: "Inception" }, { $push: { cast: "Ellen Page" } } );
Use $each to add multiple elements at once:
{ $push: { array: { $each: [item1, item2] } } }
Goal: Remove first or last element from grades array.
// Remove last element from grades (-1) db.restaurants.updateOne( { restaurant_id: "30075445" }, { $pop: { grades: 1 } } ); // Remove first element from grades (-1) db.restaurants.updateOne( { restaurant_id: "30075445" }, { $pop: { grades: -1 } } );
{ $pop: { array: 1 } }- Remove LAST element{ $pop: { array: -1 } }- Remove FIRST element- Only accepts 1 or -1 as values
Goal: Add genre only if it doesn't already exist.
// Add genre only if not present db.movies.updateOne( { title: "Inception" }, { $addToSet: { genres: "Sci-Fi" } } ); // Try to add again - won't create duplicate db.movies.updateOne( { title: "Inception" }, { $addToSet: { genres: "Sci-Fi" } } ); // Add multiple unique values db.movies.updateOne( { title: "Inception" }, { $addToSet: { genres: { $each: ["Thriller", "Mystery"] } } } );
$push- Always adds, allows duplicates$addToSet- Only adds if not already present (unique)
β $pull & $pullAll - Remove Specific Elements
Goal: Remove specific genre from genres array.
// Remove specific value db.movies.updateOne( { title: "Inception" }, { $pull: { genres: "Mystery" } } ); // Remove with condition db.movies.updateOne( { title: "Inception" }, { $pull: { cast: "Unknown Actor" } } );
Goal: Remove all inspections with score > 40.
// Remove grades with score > 40 db.restaurants.updateMany( {}, { $pull: { grades: { score: { $gt: 40 } } } } ); // Remove grades with grade "Z" (pending) db.restaurants.updateMany( {}, { $pull: { grades: { grade: "Z" } } } );
Goal: Remove multiple genres at once.
// Remove multiple specific values db.movies.updateOne( { title: "Inception" }, { $pullAll: { genres: ["Mystery", "Adventure"] } } ); // pullAll removes exact matches only db.movies.updateMany( {}, { $pullAll: { tags: ["outdated", "deprecated", "old"] } } );
π Upsert - Update or Insert
Goal: Update book if exists, create if doesn't.
// Upsert: update if found, insert if not db.books.updateOne( { isbn: "978-1-234-56789-0" }, { $set: { title: "New Book Title", author: "Jane Doe", available_copies: 5 } }, { upsert: true } ); // Check the result db.books.findOne({ isbn: "978-1-234-56789-0" });
// If document existed: { "acknowledged": true, "matchedCount": 1, "modifiedCount": 1, "upsertedId": null } // If document was created: { "acknowledged": true, "matchedCount": 0, "modifiedCount": 0, "upsertedId": ObjectId("...") }
- Synchronizing data from external sources
- Maintaining counters/statistics
- Session management
- Cache initialization
Goal: Set fields only on insert, not on update.
// Update rating always, set created_at only on insert
db.movies.updateOne(
{ title: "New Movie 2024" },
{
$set: { "imdb.rating": 8.0 },
$setOnInsert: {
created_at: new Date(),
year: 2024,
genres: ["Action"]
}
},
{ upsert: true }
);
Fields in $setOnInsert are only set when a NEW document is created (inserted). They're ignored on updates.
ποΈ Delete Operations
Goal: Delete a specific restaurant.
// Delete by unique identifier db.restaurants.deleteOne({ restaurant_id: "99999999" }); // Delete first matching document db.restaurants.deleteOne({ borough: "Manhattan", cuisine: "Temporary" });
{
"acknowledged": true,
"deletedCount": 1
}
DELETE operations are PERMANENT! Always double-check your filter before deleting. Consider using a "soft delete" (set deleted: true) for important data.
Goal: Delete all books with 0 copies.
// Delete all matching documents db.books.deleteMany({ total_copies: 0, available_copies: 0 }); // Delete old temp records db.books.deleteMany({ temp: true, created_at: { $lt: new Date("2023-01-01") } });
{
"acknowledged": true,
"deletedCount": 15
}
// deletedCount shows how many were removed
Goal: Mark as deleted instead of removing (recommended for important data).
// Soft delete - mark as deleted db.movies.updateOne( { title: "Old Movie" }, { $set: { deleted: true, deleted_at: new Date(), deleted_by: "admin" } } ); // Query active (non-deleted) documents db.movies.find({ deleted: { $ne: true } }); // Permanently delete after 30 days (cleanup job) db.movies.deleteMany({ deleted: true, deleted_at: { $lt: new Date(Date.now() - 30*24*60*60*1000) } });
- Can undo deletions
- Maintain audit trail
- Comply with regulations (data retention)
- Recover from accidental deletes
π MongoDB Indexes Overview
Indexes are special data structures that store a small portion of the collection's data in an easy-to-traverse form. They dramatically improve query performance but come with trade-offs.
| Index Type | Purpose | Example Use Case |
|---|---|---|
| Single Field | Index on one field | Find by email, userId |
| Compound | Index on multiple fields | Find by (state, city) |
| Text | Full-text search | Search movie titles/descriptions |
| Geospatial | Location-based queries | Find nearby restaurants |
| Unique | Enforce uniqueness | Unique email addresses |
| Sparse | Index only docs with field | Optional fields |
| TTL | Auto-delete after time | Session data, logs |
β
Benefits:
|
β Costs:
|
π Single Field Indexes
Goal: Create index on borough field for faster queries.
// Create ascending index on borough db.restaurants.createIndex({ borough: 1 }); // Create descending index db.restaurants.createIndex({ cuisine: -1 }); // Create unique index (prevent duplicates) db.restaurants.createIndex({ restaurant_id: 1 }, { unique: true }); // List all indexes db.restaurants.getIndexes(); // Test performance improvement db.restaurants.find({ borough: "Manhattan" }).explain("executionStats");
{
"numIndexesBefore": 1,
"numIndexesAfter": 2,
"createdCollectionAutomatically": false,
"ok": 1
}
1= Ascending order (AβZ, 0β9)-1= Descending order (ZβA, 9β0)- For single-field indexes, direction rarely matters
- For compound indexes, direction is crucial
Goal: Create index on nested zipcode field.
// Index on nested field using dot notation db.restaurants.createIndex({ "address.zipcode": 1 }); // Index on array element db.restaurants.createIndex({ "grades.grade": 1 }); // Query benefits from index db.restaurants.find({ "address.zipcode": "10462" });
Goal: View, rename, and drop indexes.
// List all indexes with details db.movies.getIndexes(); // Create named index db.movies.createIndex( { year: 1 }, { name: "idx_year" } ); // Drop specific index by name db.movies.dropIndex("idx_year"); // Drop index by specification db.movies.dropIndex({ year: 1 }); // Drop ALL indexes (except _id) db.movies.dropIndexes();
- Cannot drop the
_idindex (always exists) - Dropping index during peak hours impacts performance
- Recreating dropped index can take time on large collections
- Always test in dev before dropping production indexes
π Compound Indexes
Goal: Create index on borough + cuisine for faster combined queries.
// Compound index on two fields db.restaurants.createIndex({ borough: 1, cuisine: 1 }); // This query uses the compound index db.restaurants.find({ borough: "Manhattan", cuisine: "Italian" }); // This also uses the index (prefix match) db.restaurants.find({ borough: "Manhattan" }); // This does NOT use the index efficiently db.restaurants.find({ cuisine: "Italian" });
Equality β Sort β Range
- Equality fields first (=)
- Sort fields next
- Range fields last ($gt, $lt, $in)
Index { borough: 1, cuisine: 1 } supports:
- β { borough }
- β { borough, cuisine }
- β { cuisine } (not efficiently)
Goal: Create optimized index for common query pattern.
// Common query: filter by genre, sort by rating, limit results // BAD index order: db.movies.createIndex({ "imdb.rating": -1, genres: 1 }); // GOOD index order (ESR rule): db.movies.createIndex({ genres: 1, "imdb.rating": -1 }); // Query that benefits: db.movies.find({ genres: "Action" }) .sort({ "imdb.rating": -1 }) .limit(10); // Compound with 3 fields db.movies.createIndex({ genres: 1, year: 1, "imdb.rating": -1 });
Goal: Optimize aggregation with proper index.
// Create index for aggregation db.restaurants.createIndex({ borough: 1, cuisine: 1, "grades.score": 1 }); // Aggregation that uses the index db.restaurants.aggregate([ { $match: { borough: "Manhattan", cuisine: "Italian" } }, { $sort: { "grades.score": 1 } }, { $limit: 10 } ]);
π Text Search Indexes
Goal: Enable full-text search on movie titles and descriptions.
// Create text index on single field db.movies.createIndex({ title: "text" }); // Text index on multiple fields db.movies.createIndex({ title: "text", plot: "text", genres: "text" }); // Text search query db.movies.find({ $text: { $search: "redemption prison" } }); // Search with exact phrase db.movies.find({ $text: { $search: "\"the godfather\"" } }); // Exclude words with minus db.movies.find({ $text: { $search: "action -comedy" } });
{
"title": "The Shawshank Redemption",
"plot": "Two imprisoned men bond over years, finding redemption...",
"score": 2.5
}
- Stemming: "run", "running", "ran" match
- Stop words ignored: "the", "a", "is"
- Case-insensitive by default
- Only ONE text index per collection
- Use quotes for exact phrases
- Use minus (-) to exclude words
Goal: Sort search results by relevance score.
// Search with text score (relevance) db.movies.find( { $text: { $search: "love story romance" } }, { score: { $meta: "textScore" } } ).sort({ score: { $meta: "textScore" } }) .limit(10); // Filter by score threshold db.movies.find( { $text: { $search: "action adventure" }, score: { $meta: "textScore" } }, { score: { $meta: "textScore" } } ).sort({ score: { $meta: "textScore" } });
Goal: Give different weights to different fields.
// Title is 3x more important than description db.books.createIndex( { title: "text", description: "text" }, { weights: { title: 3, description: 1 } } ); // Search benefits from weighting db.books.find( { $text: { $search: "mockingbird" } }, { score: { $meta: "textScore" } } ).sort({ score: { $meta: "textScore" } });
π Geospatial Indexes & Queries
Goal: Enable location-based queries on restaurant coordinates.
// Create 2dsphere index on coordinates db.restaurants.createIndex({ "address.coord": "2dsphere" }); // Check data format (must be [longitude, latitude]) db.restaurants.findOne({}, { "address.coord": 1 }); // Should show: "coord": [-73.856077, 40.848447]
MongoDB uses GeoJSON format for geospatial data:
{
"location": {
"type": "Point",
"coordinates": [longitude, latitude]
}
}
// β οΈ ORDER MATTERS: [longitude, latitude]
// NOT [latitude, longitude]!
Goal: Find restaurants within 1000 meters of a location.
// Find restaurants near Times Square db.restaurants.find({ "address.coord": { $near: { $geometry: { type: "Point", coordinates: [-73.9851, 40.7589] }, $maxDistance: 1000 // in meters } } }).limit(10); // Find with min and max distance db.restaurants.find({ "address.coord": { $near: { $geometry: { type: "Point", coordinates: [-73.9851, 40.7589] }, $minDistance: 100, $maxDistance: 500 } } });
// Results automatically sorted by distance (closest first) { "name": "Planet Hollywood", "address": { "coord": [-73.9845, 40.7580] } } // Distance: ~110 meters
Goal: Find all restaurants within a specific area (polygon).
// Define a polygon area (Manhattan neighborhood) db.restaurants.find({ "address.coord": { $geoWithin: { $geometry: { type: "Polygon", coordinates: [[ [-73.99, 40.75], // Point 1 [-73.98, 40.75], // Point 2 [-73.98, 40.76], // Point 3 [-73.99, 40.76], // Point 4 [-73.99, 40.75] // Back to Point 1 (close polygon) ]] } } } }); // Find within circle (center + radius) db.restaurants.find({ "address.coord": { $geoWithin: { $centerSphere: [ [-73.9851, 40.7589], // center [lng, lat] 1 / 6378.1 // radius in radians (β1km) ] } } });
Goal: Count restaurants by distance ranges.
// Use $geoNear in aggregation (must be first stage)
db.restaurants.aggregate([
{
$geoNear: {
near: {
type: "Point",
coordinates: [-73.9851, 40.7589]
},
distanceField: "distance",
maxDistance: 5000,
spherical: true
}
},
{
$group: {
_id: {
$switch: {
branches: [
{ case: { $lte: ["$distance", 500] }, then: "0-500m" },
{ case: { $lte: ["$distance", 1000] }, then: "500m-1km" },
{ case: { $lte: ["$distance", 2000] }, then: "1-2km" }
],
default: "2-5km"
}
},
count: { $sum: 1 },
avgScore: { $avg: "$grades.0.score" }
}
},
{ $sort: { _id: 1 } }
]);
β‘ Performance Optimization
Goal: Measure query performance with and without index.
// Test WITHOUT index db.restaurants.dropIndex({ borough: 1 }); var start = new Date(); db.restaurants.find({ borough: "Manhattan" }).count(); var end = new Date(); print("Without index: " + (end - start) + "ms"); // Test WITH index db.restaurants.createIndex({ borough: 1 }); start = new Date(); db.restaurants.find({ borough: "Manhattan" }).count(); end = new Date(); print("With index: " + (end - start) + "ms"); // Typical results: // Without index: 45ms (collection scan) // With index: 2ms (index scan) - 22x faster!
Goal: Create query that reads ONLY from index (no document fetch).
// Create compound index db.movies.createIndex({ year: 1, title: 1 }); // Covered query (all fields from index) db.movies.find( { year: 2010 }, { _id: 0, year: 1, title: 1 } ); // Verify it's covered db.movies.find( { year: 2010 }, { _id: 0, year: 1, title: 1 } ).explain("executionStats"); // Look for: totalDocsExamined: 0 (covered!)
- MongoDB doesn't need to fetch full documents
- Reads only from index (much faster)
- Works when query + projection are entirely in index
- Must exclude _id or include it in index
Goal: Understand which fields make good index candidates.
// Check cardinality (unique values) db.restaurants.distinct("borough").length; // Result: 5 (low cardinality - not ideal alone) db.restaurants.distinct("cuisine").length; // Result: 84 (medium cardinality - good) db.restaurants.distinct("restaurant_id").length; // Result: 3772 (high cardinality - excellent) // Good compound index (high selectivity together) db.restaurants.createIndex({ borough: 1, cuisine: 1 }); // 5 boroughs Γ 84 cuisines = 420 combinations
- High Cardinality (many unique values) β Great for indexes
- Low Cardinality (few unique values) β Poor alone, good in compounds
- Boolean fields β Low selectivity
- Timestamps β High selectivity
- IDs, emails β Very high selectivity
π Query Explain & Analysis
Goal: Understand query execution plan.
// Basic explain db.restaurants.find({ borough: "Manhattan" }).explain(); // Execution stats (detailed) db.restaurants.find({ borough: "Manhattan" }) .explain("executionStats"); // All plans (shows rejected plans too) db.restaurants.find({ borough: "Manhattan" }) .explain("allPlansExecution");
{
"executionStats": {
"executionTimeMillis": 2,
"totalKeysExamined": 1883,
"totalDocsExamined": 1883,
"nReturned": 1883,
"executionStages": {
"stage": "IXSCAN", // IXSCAN = index used β
"indexName": "borough_1"
}
}
}
// Bad signs:
// - stage: "COLLSCAN" (collection scan - no index)
// - totalDocsExamined >> nReturned (inefficient)
Goal: Learn to read explain output.
// Compare two approaches // Query 1: Without index db.movies.find({ year: 2010 }).explain("executionStats"); // Query 2: With index db.movies.createIndex({ year: 1 }); db.movies.find({ year: 2010 }).explain("executionStats");
| executionTimeMillis | Total query time |
| totalKeysExamined | Index entries scanned |
| totalDocsExamined | Documents scanned |
| nReturned | Documents returned |
| stage: IXSCAN | Using index β |
| stage: COLLSCAN | Full collection scan β |
Goal: Best practices for query optimization.
- Index your queries - Use explain() to verify
- Use projections - Don't return unneeded fields
- Limit results - Use limit() when possible
- Use compound indexes - Follow ESR rule
- Avoid $where & regex - They can't use indexes well
- Use covered queries - Query + projection from index only
- Filter early - Put $match first in aggregations
- Monitor index usage - Drop unused indexes
- Use appropriate types - Numbers as numbers, not strings
- Batch operations - Use bulk writes for many updates
// Find slow queries in logs db.setProfilingLevel(1, { slowms: 100 }); // Check which indexes are used db.restaurants.aggregate([ { $indexStats: {} } ]); // Find unused indexes (accesses.ops = 0)
π Aggregation Pipeline Overview
The aggregation pipeline processes documents through multiple stages, transforming them step-by-step. Think of it like a factory assembly line where each stage performs a specific operation.
Pipeline Flow Example
Filter documents β Only Manhattan restaurants
Group by cuisine β Count each cuisine type
Sort by count β Highest count first
Take top 10 β Final result
| Stage | Purpose | SQL Equivalent |
|---|---|---|
$match |
Filter documents | WHERE clause |
$group |
Group and aggregate | GROUP BY |
$project |
Reshape documents, select fields | SELECT |
$sort |
Order results | ORDER BY |
$limit |
Restrict result count | LIMIT |
$skip |
Skip documents | OFFSET |
$unwind |
Deconstruct arrays | Unnest / lateral join |
$lookup |
Join collections | LEFT JOIN |
π― $match - Filter Documents
Goal: Filter Manhattan restaurants using aggregation.
// $match works like find() db.restaurants.aggregate([ { $match: { borough: "Manhattan" } } ]); // Multiple conditions db.restaurants.aggregate([ { $match: { borough: "Manhattan", cuisine: "Italian" } } ]); // With comparison operators db.restaurants.aggregate([ { $match: { borough: "Manhattan", "grades.0.score": { $lt: 10 } } } ]);
- Put $match as early as possible to reduce documents processed
- Can use indexes when placed first
- Syntax identical to find() filters
Goal: Filter action movies from the 2010s.
// Filter by decade and genre
db.movies.aggregate([
{
$match: {
year: { $gte: 2010, $lte: 2019 },
genres: "Action",
"imdb.rating": { $gte: 7.0 }
}
}
]);
π $group - Aggregate Data
Goal: Count restaurants in each borough.
// Group by borough and count db.restaurants.aggregate([ { $group: { _id: "$borough", count: { $sum: 1 } } } ]); // Sort by count descending db.restaurants.aggregate([ { $group: { _id: "$borough", count: { $sum: 1 } } }, { $sort: { count: -1 } } ]);
{ "_id": "Manhattan", "count": 1883 }
{ "_id": "Queens", "count": 738 }
{ "_id": "Brooklyn", "count": 684 }
{ "_id": "Bronx", "count": 309 }
{ "_id": "Staten Island", "count": 158 }
$sum |
Count or sum values |
$avg |
Calculate average |
$min |
Find minimum value |
$max |
Find maximum value |
$push |
Collect values into array |
$first |
Get first value |
$last |
Get last value |
Goal: Calculate average inspection score by cuisine type.
// Average score by cuisine
db.restaurants.aggregate([
{
$group: {
_id: "$cuisine",
avgScore: { $avg: "$grades.0.score" },
count: { $sum: 1 }
}
},
{ $sort: { avgScore: 1 } },
{ $limit: 10 }
]);
{ "_id": "French", "avgScore": 8.5, "count": 112 }
{ "_id": "Japanese", "avgScore": 9.2, "count": 98 }
// Lower scores = better health ratings!
Goal: Calculate average IMDb rating by decade.
// Group by decade (calculated field)
db.movies.aggregate([
{
$group: {
_id: {
$subtract: [
"$year",
{ $mod: ["$year", 10] }
]
},
avgRating: { $avg: "$imdb.rating" },
movieCount: { $sum: 1 },
maxRating: { $max: "$imdb.rating" },
minRating: { $min: "$imdb.rating" }
}
},
{ $sort: { _id: -1 } }
]);
{
"_id": 2010,
"avgRating": 7.2,
"movieCount": 45,
"maxRating": 8.8,
"minRating": 5.5
}
Goal: Multiple statistics per group.
// Complex aggregation with multiple accumulators
db.restaurants.aggregate([
{ $match: { borough: "Manhattan" } },
{
$group: {
_id: "$cuisine",
totalRestaurants: { $sum: 1 },
avgScore: { $avg: "$grades.0.score" },
bestScore: { $min: "$grades.0.score" },
worstScore: { $max: "$grades.0.score" },
restaurants: { $push: "$name" }
}
},
{ $sort: { totalRestaurants: -1 } },
{ $limit: 5 }
]);
Goal: Statistics for each genre.
// Genre statistics
db.books.aggregate([
{
$group: {
_id: "$genre",
totalBooks: { $sum: 1 },
totalCopies: { $sum: "$total_copies" },
availableCopies: { $sum: "$available_copies" },
avgPublicationYear: { $avg: "$published_year" }
}
},
{ $sort: { totalBooks: -1 } }
]);
π¬ $project - Reshape Documents
Goal: Select specific fields and rename them.
// Select and rename fields
db.restaurants.aggregate([
{ $match: { borough: "Manhattan" } },
{
$project: {
_id: 0,
restaurantName: "$name",
location: "$borough",
foodType: "$cuisine",
latestGrade: "$grades.0.grade",
latestScore: "$grades.0.score"
}
},
{ $limit: 10 }
]);
{
"restaurantName": "Shake Shack",
"location": "Manhattan",
"foodType": "Hamburgers",
"latestGrade": "A",
"latestScore": 7
}
Goal: Create calculated fields.
// Calculate age of movie
db.movies.aggregate([
{
$project: {
title: 1,
year: 1,
rating: "$imdb.rating",
ageInYears: { $subtract: [2024, "$year"] },
isClassic: { $gte: [{ $subtract: [2024, "$year"] }, 30] },
ratingCategory: {
$switch: {
branches: [
{ case: { $gte: ["$imdb.rating", 8.0] }, then: "Excellent" },
{ case: { $gte: ["$imdb.rating", 7.0] }, then: "Good" },
{ case: { $gte: ["$imdb.rating", 5.0] }, then: "Average" }
],
default: "Poor"
}
}
}
},
{ $limit: 10 }
]);
Goal: Transform text fields.
// String manipulation
db.books.aggregate([
{
$project: {
title: 1,
titleUpperCase: { $toUpper: "$title" },
titleLowerCase: { $toLower: "$title" },
titleLength: { $strLenCP: "$title" },
yearString: { $toString: "$published_year" },
availability: {
$concat: [
{ $toString: "$available_copies" },
" of ",
{ $toString: "$total_copies" },
" available"
]
}
}
},
{ $limit: 5 }
]);
π’ $sort, $limit, $skip in Pipeline
Goal: Find most popular cuisines.
// Complete pipeline: match β group β sort β limit
db.restaurants.aggregate([
{ $match: { borough: "Manhattan" } },
{
$group: {
_id: "$cuisine",
count: { $sum: 1 },
avgScore: { $avg: "$grades.0.score" }
}
},
{ $sort: { count: -1 } },
{ $limit: 10 },
{
$project: {
_id: 0,
cuisine: "$_id",
restaurantCount: "$count",
averageHealthScore: { $round: ["$avgScore", 1] }
}
}
]);
Goal: Implement pagination with skip and limit.
// Page 1 (skip 0, limit 10)
var page = 1;
var pageSize = 10;
db.movies.aggregate([
{ $match: { "imdb.rating": { $gte: 7.0 } } },
{ $sort: { "imdb.rating": -1 } },
{ $skip: (page - 1) * pageSize },
{ $limit: pageSize },
{
$project: {
_id: 0,
title: 1,
year: 1,
rating: "$imdb.rating"
}
}
]);
π $unwind - Deconstruct Arrays
Goal: Create one document per grade.
// Unwind creates separate doc for each array element
db.restaurants.aggregate([
{ $match: { restaurant_id: "30075445" } },
{ $unwind: "$grades" },
{
$project: {
name: 1,
gradeDate: "$grades.date",
grade: "$grades.grade",
score: "$grades.score"
}
}
]);
{
"name": "Morris Park",
"grades": [
{ "date": "2024-01-15", "grade": "A", "score": 5 },
{ "date": "2023-06-10", "grade": "A", "score": 3 }
]
}
{ "name": "Morris Park", "gradeDate": "2024-01-15", "grade": "A", "score": 5 }
{ "name": "Morris Park", "gradeDate": "2023-06-10", "grade": "A", "score": 3 }
Goal: Count movies per genre (genres is array).
// Unwind genres, then group
db.movies.aggregate([
{ $unwind: "$genres" },
{
$group: {
_id: "$genres",
movieCount: { $sum: 1 },
avgRating: { $avg: "$imdb.rating" }
}
},
{ $sort: { movieCount: -1 } },
{ $limit: 10 }
]);
Arrays can't be grouped directly. $unwind creates one document per array element, enabling aggregation on array values.
π $lookup - Join Collections
Goal: Join books collection with authors collection.
// JOIN books with authors (like SQL LEFT JOIN)
db.books.aggregate([
{
$lookup: {
from: "authors",
localField: "author_id",
foreignField: "_id",
as: "authorDetails"
}
},
{
$project: {
title: 1,
isbn: 1,
authorName: { $arrayElemAt: ["$authorDetails.name", 0] },
authorBio: { $arrayElemAt: ["$authorDetails.bio", 0] }
}
},
{ $limit: 10 }
]);
from- Collection to joinlocalField- Field from current collectionforeignField- Field from joined collectionas- Name for array of matched documents
Goal: Get books with their loan history.
// Books with loan history
db.books.aggregate([
{
$lookup: {
from: "loans",
localField: "_id",
foreignField: "book_id",
as: "loanHistory"
}
},
{
$project: {
title: 1,
totalLoans: { $size: "$loanHistory" },
currentlyBorrowed: {
$size: {
$filter: {
input: "$loanHistory",
cond: { $eq: ["$$this.returned", false] }
}
}
}
}
},
{ $sort: { totalLoans: -1 } },
{ $limit: 10 }
]);
Goal: Get borrowers with their loan details and book info.
// Triple join: borrowers β loans β books
db.borrowers.aggregate([
{
$lookup: {
from: "loans",
localField: "_id",
foreignField: "borrower_id",
as: "loans"
}
},
{ $unwind: "$loans" },
{
$lookup: {
from: "books",
localField: "loans.book_id",
foreignField: "_id",
as: "bookDetails"
}
},
{
$project: {
borrowerName: "$name",
bookTitle: { $arrayElemAt: ["$bookDetails.title", 0] },
borrowDate: "$loans.borrow_date",
dueDate: "$loans.due_date",
isOverdue: {
$gt: [new Date(), "$loans.due_date"]
}
}
},
{ $match: { isOverdue: true } }
]);
π Advanced Pipeline Examples
Goal: Complex analysis with 6+ stages.
// Find best cuisines by average score and count
db.restaurants.aggregate([
// Stage 1: Filter Manhattan only
{ $match: { borough: "Manhattan" } },
// Stage 2: Unwind grades array
{ $unwind: "$grades" },
// Stage 3: Group by cuisine
{
$group: {
_id: "$cuisine",
avgScore: { $avg: "$grades.score" },
restaurantCount: { $sum: 1 },
totalInspections: { $sum: 1 }
}
},
// Stage 4: Filter cuisines with 50+ restaurants
{ $match: { restaurantCount: { $gte: 50 } } },
// Stage 5: Sort by average score
{ $sort: { avgScore: 1 } },
// Stage 6: Limit to top 10
{ $limit: 10 },
// Stage 7: Format output
{
$project: {
_id: 0,
cuisine: "$_id",
averageScore: { $round: ["$avgScore", 2] },
restaurants: "$restaurantCount",
inspections: "$totalInspections"
}
}
]);
Goal: Movie trends over decades.
// Decade-by-decade movie analysis
db.movies.aggregate([
{
$group: {
_id: {
$multiply: [
{ $floor: { $divide: ["$year", 10] } },
10
]
},
totalMovies: { $sum: 1 },
avgRating: { $avg: "$imdb.rating" },
topRatedMovie: {
$max: {
rating: "$imdb.rating",
title: "$title"
}
}
}
},
{ $sort: { _id: 1 } },
{
$project: {
decade: { $concat: [{ $toString: "$_id" }, "s"] },
totalMovies: 1,
avgRating: { $round: ["$avgRating", 2] },
_id: 0
}
}
]);
π Real-World Analytics Queries
Goal: Generate comprehensive health inspection report.
// Comprehensive health report by borough and cuisine
db.restaurants.aggregate([
{ $unwind: "$grades" },
{
$group: {
_id: {
borough: "$borough",
cuisine: "$cuisine"
},
avgScore: { $avg: "$grades.score" },
restaurantCount: { $addToSet: "$restaurant_id" },
gradeDistribution: {
$push: "$grades.grade"
}
}
},
{
$project: {
borough: "$_id.borough",
cuisine: "$_id.cuisine",
avgScore: { $round: ["$avgScore", 2] },
totalRestaurants: { $size: "$restaurantCount" },
gradeACoun: {
$size: {
$filter: {
input: "$gradeDistribution",
cond: { $eq: ["$$this", "A"] }
}
}
},
_id: 0
}
},
{ $sort: { borough: 1, avgScore: 1 } }
]);
Goal: Books most borrowed vs availability.
// Most popular books with availability status
db.books.aggregate([
{
$lookup: {
from: "loans",
localField: "_id",
foreignField: "book_id",
as: "loanHistory"
}
},
{
$project: {
title: 1,
genre: 1,
totalLoans: { $size: "$loanHistory" },
available_copies: 1,
total_copies: 1,
utilizationRate: {
$multiply: [
{
$divide: [
{ $subtract: ["$total_copies", "$available_copies"] },
"$total_copies"
]
},
100
]
}
}
},
{ $sort: { totalLoans: -1 } },
{ $limit: 20 }
]);
Goal: Find similar highly-rated movies.
// Recommendation based on genre overlap and rating
var userFavoriteGenres = ["Action", "Sci-Fi"];
db.movies.aggregate([
{ $unwind: "$genres" },
{ $match: { genres: { $in: userFavoriteGenres } } },
{
$group: {
_id: "$_id",
title: { $first: "$title" },
year: { $first: "$year" },
rating: { $first: "$imdb.rating" },
allGenres: { $push: "$genres" },
genreMatchCount: { $sum: 1 }
}
},
{ $match: { rating: { $gte: 7.0 } } },
{ $sort: { genreMatchCount: -1, rating: -1 } },
{ $limit: 10 },
{
$project: {
_id: 0,
title: 1,
year: 1,
rating: 1,
matchedGenres: "$genreMatchCount"
}
}
]);