Section 11: Complete MongoDB Practice Lab

πŸŽ“ MongoDB Mastery: From Basics to Advanced

Master Every MongoDB Concept Through 150+ Real-World Examples Across 3 Production Datasets

🎯 Complete MongoDB Query Guide

πŸ’‘ What You'll Learn

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 Practice Datasets

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.

🍽️
Restaurants Dataset
3,772 NYC Restaurants
Download JSON
🎬
Movies Dataset
500+ Popular Films
Download JSON
πŸ“š
Books Dataset
Sample Library Data
Download JSON
How to Import:
mongoimport --db practice --collection restaurants --file primer-dataset.json --jsonArray

πŸ“‘ Quick Topic Navigation

πŸ“₯
Retrieve All
🎯
Equality
βš–οΈ
Comparison
πŸ”€
Logical Ops
πŸ”’
Limit & Sort
🎬
Projection

πŸ“₯ 1. Retrieve All Documents

πŸ’‘ Concept

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.

EXAMPLE 1.1
Retrieve All Restaurants
🍽️ Restaurants

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();
πŸ“€ Expected Output
// 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
⚠️ Best Practice

Always use limit() when exploring data to avoid overwhelming output. In production, retrieving ALL documents can cause performance issues with large collections.

EXAMPLE 1.2
Retrieve All Movies
🎬 Movies

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);
πŸ“€ Sample Output
{
  "_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 }
}
EXAMPLE 1.3
Retrieve All Books
πŸ“š Library

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({});
πŸ“€ Sample Output
{
  "_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
}
EXAMPLE 1.4
Iterate Through Cursor
🍽️ Restaurants

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);
});
πŸ’‘ Cursor Behavior
  • 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!)
EXAMPLE 1.5
Count All Documents
🍽️ Restaurants

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
πŸ“€ Output
3772
⚑ Performance Tip

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

πŸ’‘ Concept

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.

EXAMPLE 2.1
Find by Name (Implicit Equality)
🍽️ Restaurants

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
πŸ“€ Sample Output
{
  "_id": ObjectId("..."),
  "name": "Wendy'S",
  "borough": "Brooklyn",
  "cuisine": "Hamburgers",
  "restaurant_id": "30112340"
}
⚠️ Case Sensitivity

MongoDB queries are case-sensitive by default. "Wendy'S" β‰  "wendy's". For case-insensitive search, use regex (covered later).

EXAMPLE 2.2
Find by Borough
🍽️ Restaurants

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"]
EXAMPLE 2.3
Find Movies by Year
🎬 Movies

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" });
πŸ“€ Sample Output
{
  "_id": ObjectId("..."),
  "title": "The Shawshank Redemption",
  "year": 1994,
  "director": "Frank Darabont"
}
{
  "_id": ObjectId("..."),
  "title": "Pulp Fiction",
  "year": 1994,
  "director": "Quentin Tarantino"
}
EXAMPLE 2.4
Find Books by ISBN
πŸ“š Library

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("...") });
EXAMPLE 2.5
Find by Nested Field (Zipcode)
🍽️ Restaurants

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" });
πŸ’‘ Dot Notation

Use dot notation to access nested fields: "address.zipcode". Always wrap in quotes to prevent JavaScript errors.

πŸ“¦ 3. Querying Embedded Documents

πŸ’‘ What Are 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.

Key Concepts
  • 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
EXAMPLE 3.1
Query Nested Address Fields
🍽️ Restaurants

Goal: Find restaurants using nested address fields (zipcode, street, building).

πŸ“‹ Restaurant Address Structure:
{
  "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"
});
πŸ“€ Sample Output
{
  "_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"
  }
}
⚠️ Quote the Dotted Path!

WRONG: address.zipcode (JavaScript interprets as object property)
RIGHT: "address.zipcode" (MongoDB interprets as field path)

EXAMPLE 3.2
Query Array of Embedded Documents
🍽️ Restaurants

Goal: Find restaurants with specific grades (array of embedded documents).

πŸ“‹ Restaurant Grades Structure:
{
  "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 }
});
πŸ’‘ Array Query Behavior

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).

EXAMPLE 3.3
Query Movie Ratings & Reviews
🎬 Movies

Goal: Query nested rating information in movie documents.

πŸ“‹ Movie Ratings Structure:
{
  "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 }
});
πŸ“€ Sample Output
{
  "_id": ObjectId("..."),
  "title": "Inception",
  "year": 2010,
  "imdb": {
    "rating": 8.8,
    "votes": 2000000,
    "id": "tt1375666"
  },
  "tomatoes": {
    "viewer": { "rating": 4.2 },
    "critic": { "rating": 87 }
  }
}
EXAMPLE 3.4
Query Book Publisher Details
πŸ“š Library

Goal: Query nested publisher information in book records.

πŸ“‹ Book Publisher Structure:
{
  "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" });
EXAMPLE 3.5
Match Entire Embedded Document
🍽️ Restaurants

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"
});
⚠️ Exact Match Gotchas
  • Field order must be exactly the same
  • All fields must be included (no partial matching)
  • Generally avoid exact matching - use dot notation instead
Key Takeaways
  • 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

πŸ’‘ What Are 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)

EXAMPLE 3.1
Find High-Scoring Restaurants
🍽️ Restaurants

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);
πŸ“€ Expected Output
{
  "name": "Brunos On The Boulevard",
  "grades": [{ "score": 38, ... }]
}
// High scores mean more violations (bad!)
⚠️ Understanding Grades

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.

EXAMPLE 3.2
Find Recent Movies
🎬 Movies

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"
});
πŸ“€ Sample Output
{
  "_id": ObjectId("..."),
  "title": "Inception",
  "year": 2010,
  "genres": ["Action", "Sci-Fi", "Thriller"]
}
EXAMPLE 3.3
Find High-Rated Movies ($gt with Nested Field)
🎬 Movies

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 }
);
πŸ“€ Sample Output
{
  "title": "The Shawshank Redemption",
  "imdb": { "rating": 9.3 },
  "year": 1994
}
{
  "title": "The Godfather",
  "imdb": { "rating": 9.2 },
  "year": 1972
}
EXAMPLE 3.4
Find Old Books ($lt)
πŸ“š Library

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 }
});
πŸ“€ Sample Output
{
  "_id": ObjectId("..."),
  "title": "The Great Gatsby",
  "published_year": 1925,
  "available_copies": 2
}
EXAMPLE 3.5
Low-Scoring Restaurants (Good Scores!)
🍽️ Restaurants

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)

EXAMPLE 4.1
Find Movies from a Decade
🎬 Movies

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 });
πŸ“€ Sample Output
{
  "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 }
}
πŸ’‘ Range Queries

Combine $gte and $lte to create range queries: { field: { $gte: min, $lte: max } }. Works with numbers, dates, and strings.

EXAMPLE 4.2
Find Acceptable Restaurant Scores
🍽️ Restaurants

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"
});
EXAMPLE 4.3
Find Books by Publication Range
πŸ“š Library

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)

EXAMPLE 5.1
Exclude Specific Borough
🍽️ Restaurants

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"
});
πŸ“€ Expected Output
// Returns restaurants from:
// Bronx, Brooklyn, Queens, Staten Island
// Total: ~1,889 restaurants (3,772 - 1,883)
EXAMPLE 5.2
Exclude PG-13 Movies
🎬 Movies

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)

EXAMPLE 6.1
Find Restaurants in Multiple Boroughs
🍽️ Restaurants

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 vs $or

$in is shorthand for multiple $or conditions on the SAME field. Use $in when checking one field against multiple values.

EXAMPLE 6.2
Find Multiple Cuisines
🍽️ Restaurants

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"
});
EXAMPLE 6.3
Exclude Multiple Boroughs ($nin)
🍽️ Restaurants

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"] }
});
EXAMPLE 6.4
Find Movies by Multiple Genres
🎬 Movies

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

πŸ’‘ What Are 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)

EXAMPLE 7.1
Implicit AND (Most Common)
🍽️ Restaurants

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" }
  ]
});
πŸ’‘ Implicit vs Explicit AND

By default, multiple conditions in a query are combined with AND. Use explicit $and only when you need the same field with different operators.

EXAMPLE 7.2
Explicit $and (When Needed)
🍽️ Restaurants

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)

EXAMPLE 8.1
Find Multiple Cuisines ($or)
🍽️ Restaurants

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"] }
});
EXAMPLE 8.2
$or with Different Fields
🍽️ Restaurants

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" }
  ]
});
EXAMPLE 8.3
Movies: Recent OR Highly Rated
🎬 Movies

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

EXAMPLE 9.1
Complex Query: AND + OR
🍽️ Restaurants

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"
});
EXAMPLE 9.2
Complex Movie Query
🎬 Movies

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 });
EXAMPLE 9.3
Library: Available Books
πŸ“š Library

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

πŸ’‘ What is limit()?

The limit() method restricts the number of documents returned by a query. Essential for pagination and preventing overwhelming results.

EXAMPLE 10.1
Basic Limit Usage
🍽️ Restaurants

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);
πŸ“€ Result
// Returns exactly 10, 5, and 3 documents respectively
// Regardless of how many match the filter
EXAMPLE 10.2
Top Rated Movies
🎬 Movies

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

πŸ’‘ What is sort()?

The sort() method orders documents by one or more fields. Use 1 for ascending, -1 for descending.

EXAMPLE 11.1
Sort Alphabetically by Name
🍽️ Restaurants

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);
πŸ“€ Expected Output
// 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", ... }
EXAMPLE 11.2
Sort by Score (Numeric)
🍽️ Restaurants

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);
EXAMPLE 11.3
Sort Movies by Rating
🎬 Movies

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);
πŸ“€ Sample Output
{
  "title": "The Shawshank Redemption",
  "imdb": { "rating": 9.3 }
}
{
  "title": "The Godfather",
  "imdb": { "rating": 9.2 }
}
EXAMPLE 11.4
Multi-Level Sort
🍽️ Restaurants

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);
πŸ’‘ Multi-Level Sort

When sorting by multiple fields, MongoDB sorts by the first field, then uses subsequent fields as tie-breakers. Order matters!

EXAMPLE 11.5
Sort Movies by Year and Rating
🎬 Movies

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);
EXAMPLE 11.6
Sort Library Books
πŸ“š Library

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

πŸ’‘ What is skip()?

The skip() method bypasses a specified number of documents. Combine with limit() for pagination.

EXAMPLE 12.1
Basic Skip Usage
🍽️ Restaurants

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);
EXAMPLE 12.2
Pagination Pattern
🎬 Movies

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);
πŸ’‘ Pagination Formula

skip((page - 1) * pageSize).limit(pageSize)

  • Page 1: skip(0), limit(10)
  • Page 2: skip(10), limit(10)
  • Page 3: skip(20), limit(10)
EXAMPLE 12.3
Complete Pagination Example
🍽️ Restaurants

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);
⚠️ Performance Warning

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)

πŸ’‘ What is Projection?

Projection allows you to include or exclude specific fields from query results. Use 1 to include, 0 to exclude.

EXAMPLE 13.1
Include Specific Fields
🍽️ Restaurants

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);
πŸ“€ Expected Output
{
  "name": "Morris Park Bake Shop",
  "borough": "Bronx",
  "cuisine": "Bakery"
}
// No _id, address, grades, or restaurant_id
πŸ’‘ Projection Rules
  • 1 = include field
  • 0 = exclude field
  • Cannot mix inclusion and exclusion (except _id)
  • _id is included by default - use _id: 0 to exclude
EXAMPLE 13.2
Exclude Fields
🍽️ Restaurants

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);
EXAMPLE 13.3
Project Nested Fields
🍽️ Restaurants

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);
πŸ“€ Sample Output
{
  "name": "Morris Park Bake Shop",
  "address": {
    "zipcode": "10462"
  }
}
EXAMPLE 13.4
Movie Projection
🎬 Movies

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);
EXAMPLE 13.5
Array Element Projection ($slice)
🍽️ Restaurants

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 Syntax
  • { $slice: 3 } - first 3 elements
  • { $slice: -3 } - last 3 elements
  • { $slice: [2, 3] } - skip 2, return 3
EXAMPLE 13.6
Library Book Projection
πŸ“š Library

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

πŸ’‘ Array Query Operators

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)

EXAMPLE 14.1
Find Specific Grade Combination
🍽️ Restaurants

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);
πŸ’‘ Why $elemMatch?

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
EXAMPLE 14.2
$elemMatch with Movies
🎬 Movies

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)

EXAMPLE 15.1
Find by Array Length
🍽️ Restaurants

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 Limitation

$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.

EXAMPLE 15.2
Movies with Multiple Genres
🎬 Movies

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)

EXAMPLE 16.1
Movies with Multiple Genres
🎬 Movies

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);
πŸ’‘ $all vs $in
  • $in - Array contains ANY of these values
  • $all - Array contains ALL of these values
EXAMPLE 16.2
Books with Multiple Genres
πŸ“š Library

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 Update Methods

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
⚠️ Critical Warning

NEVER use update() without update operators! Always use $set, $inc, etc. Otherwise, you'll replace the entire document!

🎯 updateOne() - Update Single Document

EXAMPLE 17.1
Update Restaurant Name
🍽️ Restaurants

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" });
πŸ“€ Expected Output
{
  "acknowledged": true,
  "matchedCount": 1,
  "modifiedCount": 1
}

// matchedCount: how many documents matched the filter
// modifiedCount: how many were actually changed
πŸ’‘ Understanding the Response
  • matchedCount: 1 - Found 1 document matching the filter
  • modifiedCount: 1 - Changed 1 document
  • modifiedCount: 0 - Document matched but value was already the same (no change needed)
EXAMPLE 17.2
Update Movie Rating
🎬 Movies

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 }
);
EXAMPLE 17.3
Update Book Availability
πŸ“š Library

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 } }
);
πŸ’‘ Better Way Coming

This works, but using $inc is cleaner for incrementing/decrementing. We'll cover that soon!

🎯🎯 updateMany() - Update Multiple Documents

EXAMPLE 18.1
Update All Restaurants in a Borough
🍽️ Restaurants

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 
});
πŸ“€ Expected Output
{
  "acknowledged": true,
  "matchedCount": 1883,
  "modifiedCount": 1883
}

// All 1,883 Manhattan restaurants now have verified: true
EXAMPLE 18.2
Update Movies by Genre
🎬 Movies

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();
EXAMPLE 18.3
Bulk Update Library Books
πŸ“š Library

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

πŸ’‘ Field Operators

$set - Creates or updates fields
$unset - Removes fields from documents

EXAMPLE 19.1
$set - Add/Update Multiple Fields
🍽️ Restaurants

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"
    } 
  }
);
πŸ’‘ $set Behavior
  • 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
EXAMPLE 19.2
$unset - Remove Fields
🍽️ Restaurants

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 }
);
EXAMPLE 19.3
$set with Movies - Update Nested Fields
🎬 Movies

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
    } 
  }
);
EXAMPLE 19.4
$set vs $unset - Library Example
πŸ“š Library

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

πŸ’‘ Numeric Operators

$inc - Increment/decrement by value
$mul - Multiply by value
$min - Update if new value is less
$max - Update if new value is greater

EXAMPLE 20.1
$inc - Increment Vote Count
🎬 Movies

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 } }
);
πŸ’‘ $inc Advantages
  • 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)
EXAMPLE 20.2
$inc - Library Book Borrowing
πŸ“š Library

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 }
);
EXAMPLE 20.3
$mul - Apply Percentage Discount
πŸ“š Library

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

πŸ’‘ Array 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

EXAMPLE 21.1
$push - Add New Inspection
🍽️ Restaurants

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
      }
    } 
  }
);
EXAMPLE 21.2
$push - Add Movie Genre
🎬 Movies

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" } }
);
πŸ’‘ $push with $each

Use $each to add multiple elements at once:
{ $push: { array: { $each: [item1, item2] } } }

EXAMPLE 21.3
$pop - Remove Array Elements
🍽️ Restaurants

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 Values
  • { $pop: { array: 1 } } - Remove LAST element
  • { $pop: { array: -1 } } - Remove FIRST element
  • Only accepts 1 or -1 as values
EXAMPLE 21.4
$addToSet - Add Without Duplicates
🎬 Movies

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 vs $addToSet
  • $push - Always adds, allows duplicates
  • $addToSet - Only adds if not already present (unique)

❌ $pull & $pullAll - Remove Specific Elements

EXAMPLE 22.1
$pull - Remove Genre
🎬 Movies

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" } }
);
EXAMPLE 22.2
$pull - Remove by Condition
🍽️ Restaurants

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" } } }
);
EXAMPLE 22.3
$pullAll - Remove Multiple Values
🎬 Movies

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

EXAMPLE 23.1
Upsert - Create If Not Exists
πŸ“š Library

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" });
πŸ“€ Upsert Response
// If document existed:
{
  "acknowledged": true,
  "matchedCount": 1,
  "modifiedCount": 1,
  "upsertedId": null
}

// If document was created:
{
  "acknowledged": true,
  "matchedCount": 0,
  "modifiedCount": 0,
  "upsertedId": ObjectId("...")
}
πŸ’‘ When to Use Upsert
  • Synchronizing data from external sources
  • Maintaining counters/statistics
  • Session management
  • Cache initialization
EXAMPLE 23.2
Upsert with $setOnInsert
🎬 Movies

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 }
);
πŸ’‘ $setOnInsert

Fields in $setOnInsert are only set when a NEW document is created (inserted). They're ignored on updates.

πŸ—‘οΈ Delete Operations

EXAMPLE 24.1
deleteOne() - Remove Single Document
🍽️ Restaurants

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" 
});
πŸ“€ Delete Response
{
  "acknowledged": true,
  "deletedCount": 1
}
⚠️ DANGER: No Undo!

DELETE operations are PERMANENT! Always double-check your filter before deleting. Consider using a "soft delete" (set deleted: true) for important data.

EXAMPLE 24.2
deleteMany() - Remove Multiple Documents
πŸ“š Library

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") }
});
πŸ“€ Delete Response
{
  "acknowledged": true,
  "deletedCount": 15
}

// deletedCount shows how many were removed
EXAMPLE 24.3
Soft Delete Pattern
🎬 Movies

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) }
});
πŸ’‘ Soft Delete Benefits
  • Can undo deletions
  • Maintain audit trail
  • Comply with regulations (data retention)
  • Recover from accidental deletes

πŸš€ MongoDB Indexes Overview

πŸ’‘ What Are Indexes?

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
⚑ Index Benefits vs Costs
βœ… Benefits:
  • Faster queries (10x - 1000x)
  • Better sort performance
  • Efficient range queries
  • Support for uniqueness
❌ Costs:
  • Slower writes (insert/update/delete)
  • Uses disk space
  • Uses memory (RAM)
  • Maintenance overhead

πŸ“Œ Single Field Indexes

EXAMPLE 33.1
Create Single Field Index
🍽️ Restaurants

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");
πŸ“€ createIndex Response
{
  "numIndexesBefore": 1,
  "numIndexesAfter": 2,
  "createdCollectionAutomatically": false,
  "ok": 1
}
πŸ’‘ Index Direction (1 vs -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
EXAMPLE 33.2
Index on Nested Field
🍽️ Restaurants

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" });
EXAMPLE 33.3
Manage Indexes
🎬 Movies

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();
⚠️ Index Warnings
  • Cannot drop the _id index (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

EXAMPLE 34.1
Create Compound Index
🍽️ Restaurants

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" });
πŸ’‘ Compound Index Rules (ESR Rule)

Equality β†’ Sort β†’ Range

  1. Equality fields first (=)
  2. Sort fields next
  3. Range fields last ($gt, $lt, $in)

Index { borough: 1, cuisine: 1 } supports:

  • βœ“ { borough }
  • βœ“ { borough, cuisine }
  • βœ— { cuisine } (not efficiently)
EXAMPLE 34.2
Optimal Compound Index Design
🎬 Movies

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 
});
EXAMPLE 34.3
Index for Aggregation Pipeline
🍽️ Restaurants

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 }
]);

🌍 Geospatial Indexes & Queries

EXAMPLE 36.1
Create 2dsphere Index
🍽️ Restaurants

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]
πŸ’‘ GeoJSON Format

MongoDB uses GeoJSON format for geospatial data:

{
  "location": {
    "type": "Point",
    "coordinates": [longitude, latitude]
  }
}

// ⚠️ ORDER MATTERS: [longitude, latitude]
// NOT [latitude, longitude]!
EXAMPLE 36.2
Find Nearby Restaurants ($near)
🍽️ Restaurants

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 (Sorted by Distance)
// Results automatically sorted by distance (closest first)
{
  "name": "Planet Hollywood",
  "address": {
    "coord": [-73.9845, 40.7580]
  }
}
// Distance: ~110 meters
EXAMPLE 36.3
Find Within Polygon ($geoWithin)
🍽️ Restaurants

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)
      ]
    }
  }
});
EXAMPLE 36.4
Geospatial Aggregation
🍽️ Restaurants

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

EXAMPLE 37.1
Index Performance Comparison
🍽️ Restaurants

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!
EXAMPLE 37.2
Covered Queries
🎬 Movies

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!)
⚑ Covered Query Benefits
  • 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
EXAMPLE 37.3
Index Selectivity
🍽️ Restaurants

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
πŸ’‘ Index Selectivity Guidelines
  • 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

EXAMPLE 38.1
Basic Explain
🍽️ Restaurants

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");
πŸ“€ Key Metrics to Check
{
  "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)
EXAMPLE 38.2
Interpret Explain Results
🎬 Movies

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");
⚑ Explain Metrics Explained
executionTimeMillis Total query time
totalKeysExamined Index entries scanned
totalDocsExamined Documents scanned
nReturned Documents returned
stage: IXSCAN Using index βœ“
stage: COLLSCAN Full collection scan βœ—
EXAMPLE 38.3
Optimization Checklist
🍽️ Restaurants

Goal: Best practices for query optimization.

⚑ MongoDB Optimization Checklist
  1. Index your queries - Use explain() to verify
  2. Use projections - Don't return unneeded fields
  3. Limit results - Use limit() when possible
  4. Use compound indexes - Follow ESR rule
  5. Avoid $where & regex - They can't use indexes well
  6. Use covered queries - Query + projection from index only
  7. Filter early - Put $match first in aggregations
  8. Monitor index usage - Drop unused indexes
  9. Use appropriate types - Numbers as numbers, not strings
  10. 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

πŸ’‘ What is Aggregation?

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

Stage 1: $match

Filter documents β†’ Only Manhattan restaurants

⬇️
Stage 2: $group

Group by cuisine β†’ Count each cuisine type

⬇️
Stage 3: $sort

Sort by count β†’ Highest count first

⬇️
Stage 4: $limit

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

EXAMPLE 25.1
Basic $match
🍽️ Restaurants

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 }
    } 
  }
]);
πŸ’‘ $match Best Practices
  • Put $match as early as possible to reduce documents processed
  • Can use indexes when placed first
  • Syntax identical to find() filters
EXAMPLE 25.2
$match with Movies
🎬 Movies

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

EXAMPLE 26.1
Count by Borough
🍽️ Restaurants

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 } }
]);
πŸ“€ Expected Output
{ "_id": "Manhattan", "count": 1883 }
{ "_id": "Queens", "count": 738 }
{ "_id": "Brooklyn", "count": 684 }
{ "_id": "Bronx", "count": 309 }
{ "_id": "Staten Island", "count": 158 }
πŸ’‘ $group Accumulators
$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
EXAMPLE 26.2
Average Score by Cuisine
🍽️ Restaurants

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 }
]);
πŸ“€ Sample Output
{ "_id": "French", "avgScore": 8.5, "count": 112 }
{ "_id": "Japanese", "avgScore": 9.2, "count": 98 }
// Lower scores = better health ratings!
EXAMPLE 26.3
Movies by Decade - Average Rating
🎬 Movies

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 } }
]);
πŸ“€ Sample Output
{ 
  "_id": 2010, 
  "avgRating": 7.2, 
  "movieCount": 45,
  "maxRating": 8.8,
  "minRating": 5.5
}
EXAMPLE 26.4
Multiple Aggregations
🍽️ Restaurants

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 }
]);
EXAMPLE 26.5
Library - Books by Genre Stats
πŸ“š Library

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

EXAMPLE 27.1
Select and Rename Fields
🍽️ Restaurants

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 }
]);
πŸ“€ Sample Output
{
  "restaurantName": "Shake Shack",
  "location": "Manhattan",
  "foodType": "Hamburgers",
  "latestGrade": "A",
  "latestScore": 7
}
EXAMPLE 27.2
Computed Fields
🎬 Movies

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 }
]);
EXAMPLE 27.3
String Operations
πŸ“š Library

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

EXAMPLE 28.1
Top 10 Cuisines in Manhattan
🍽️ Restaurants

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] }
    }
  }
]);
EXAMPLE 28.2
Pagination in Aggregation
🎬 Movies

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

EXAMPLE 29.1
Unwind Grades Array
🍽️ Restaurants

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"
    }
  }
]);
πŸ“€ Before $unwind (1 doc)
{
  "name": "Morris Park",
  "grades": [
    { "date": "2024-01-15", "grade": "A", "score": 5 },
    { "date": "2023-06-10", "grade": "A", "score": 3 }
  ]
}
πŸ“€ After $unwind (2 docs)
{ "name": "Morris Park", "gradeDate": "2024-01-15", "grade": "A", "score": 5 }
{ "name": "Morris Park", "gradeDate": "2023-06-10", "grade": "A", "score": 3 }
EXAMPLE 29.2
Unwind Movie Genres
🎬 Movies

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 }
]);
πŸ’‘ Why $unwind?

Arrays can't be grouped directly. $unwind creates one document per array element, enabling aggregation on array values.

πŸ”— $lookup - Join Collections

EXAMPLE 30.1
Join Books with Authors
πŸ“š Library

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 }
]);
πŸ’‘ $lookup Syntax
  • from - Collection to join
  • localField - Field from current collection
  • foreignField - Field from joined collection
  • as - Name for array of matched documents
EXAMPLE 30.2
Join Books with Loans
πŸ“š Library

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 }
]);
EXAMPLE 30.3
Complex Multi-Collection Join
πŸ“š Library

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

EXAMPLE 31.1
Multi-Stage Analytics
🍽️ Restaurants

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"
    }
  }
]);
EXAMPLE 31.2
Time-Based Analysis
🎬 Movies

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

EXAMPLE 32.1
Restaurant Health Report
🍽️ Restaurants

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 } }
]);
EXAMPLE 32.2
Library Circulation Report
πŸ“š Library

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 }
]);
EXAMPLE 32.3
Movie Recommendation Engine
🎬 Movies

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"
    }
  }
]);