Many-to-Many Relationship Example

  • In a many-to-many relationship in MongoDB, a document in one collection can have many related documents in another collection, and vice versa. This is typically modeled using referencing in both directions, where each document holds references (IDs) to related documents from the other collection.
Scenario: Modeling Students and Courses
  • Each student can enroll in many courses, and each course can have many students. This creates a many-to-many relationship between students and courses.
Step-by-Step Example Using MongoDB Shell
  • To achieve this, we’ll use referencing by storing student IDs in the courses collection and course IDs in the students collection.
Step 1: Creating the Database
  • First, switch to the desired database (or create one if it doesn't exist):


    use schoolDB

Step 2: Insert Sample Data (Referencing)

  • Insert students into the students collection:


    db.students.insertMany([
      {
        _id: 1,
        name: "John Doe",
        age: 20,
        course_ids: [101, 102]  // List of course references
      },
      {
        _id: 2,
        name: "Jane Smith",
        age: 22,
        course_ids: [101, 103]  // List of course references
      }
    ])

  • Insert courses into the courses collection:


    db.courses.insertMany([
      {
        _id: 101,
        title: "Database Systems",
        instructor: "Prof. A",
        student_ids: [1, 2]  // List of student references
      },
      {
        _id: 102,
        title: "Operating Systems",
        instructor: "Prof. B",
        student_ids: [1]  // List of student references
      },
      {
        _id: 103,
        title: "Algorithms",
        instructor: "Prof. C",
        student_ids: [2]  // List of student references
      }
    ])

Explanation of Data:

  • Each student document holds an array course_ids, which references the courses they are enrolled in.
  • Each course document holds an array student_ids, which references the students enrolled in that course.
Step 3: Querying the Many-to-Many Relationship
  • Query 1: Find a Student and the Courses They Are Enrolled In
  • Find the student:


    var student = db.students.findOne({ _id: 1 })

  • Find the courses using the course_ids:


  db.courses.find({ _id: { $in: student.course_ids } })

  // Output:
  [
    {
      "_id": 101,
      "title": "Database Systems",
      "instructor": "Prof. A",
      "student_ids": [1, 2]
    },
    {
      "_id": 102,
      "title": "Operating Systems",
      "instructor": "Prof. B",
      "student_ids": [1]
    }
  ]

  • Query 2: Find a Course and the Students Enrolled in It
  • Find the course:


    var course = db.courses.findOne({ _id: 101 })

  • Find the students using the student_ids:


    db.students.find({ _id: { $in: course.student_ids } })

    // Output:
    [
      {
        "_id": 1,
        "name": "John Doe",
        "age": 20,
        "course_ids": [101, 102]
      },
      {
        "_id": 2,
        "name": "Jane Smith",
        "age": 22,
        "course_ids": [101, 103]
      }
    ]

Step 4: Using $lookup to Perform Joins in Aggregation

  • MongoDB’s $lookup aggregation can be used to perform a join between the students and courses collections in a single query.
  • Example 1: Find a Student and Join Their Courses


    db.students.aggregate([
      {
        $lookup: {
          from: "courses",       // Collection to join with
          localField: "course_ids", // Field in students collection
          foreignField: "_id",   // Field in courses collection
          as: "courses"          // Output array field name
        }
      },
      { $match: { _id: 1 } }    // Find a specific student (John Doe)
    ])

    // Output:
    [
      {
        "_id": 1,
        "name": "John Doe",
        "age": 20,
        "course_ids": [101, 102],
        "courses": [
          {
            "_id": 101,
            "title": "Database Systems",
            "instructor": "Prof. A",
            "student_ids": [1, 2]
          },
          {
            "_id": 102,
            "title": "Operating Systems",
            "instructor": "Prof. B",
            "student_ids": [1]
          }
        ]
      }
    ]

  • Example 2: Find a Course and Join the Students Enrolled in It


    db.courses.aggregate([
      {
        $lookup: {
          from: "students",       // Collection to join with
          localField: "student_ids", // Field in courses collection
          foreignField: "_id",    // Field in students collection
          as: "students"          // Output array field name
        }
      },
      { $match: { _id: 101 } }   // Find a specific course (Database Systems)
    ])

    // Output:
    [
      {
        "_id": 101,
        "title": "Database Systems",
        "instructor": "Prof. A",
        "student_ids": [1, 2],
        "students": [
          {
            "_id": 1,
            "name": "John Doe",
            "age": 20,
            "course_ids": [101, 102]
          },
          {
            "_id": 2,
            "name": "Jane Smith",
            "age": 22,
            "course_ids": [101, 103]
          }
        ]
      }
    ]

Explanation of the Referencing Approach:

  • Referencing is the preferred approach in a many-to-many relationship. Instead of embedding arrays that may grow indefinitely, you store references to related documents (IDs).
  • This approach allows documents in both collections to grow independently and stay manageable even if the relationships become complex.
Advantages of Referencing in Many-to-Many:
  • Scalability: Both students and courses can have an unlimited number of relationships without causing the documents to grow too large.
  • Avoids Duplication: Since courses and students are stored in separate collections, you avoid duplicating data.
  • Flexible Relationships: The same course can be linked to many students, and the same student can be linked to many courses.
Disadvantages of Referencing:
  • Multiple Queries: Retrieving related data requires multiple queries or the use of $lookup aggregation.
  • Data Consistency: If not managed carefully, it’s possible for an entity (student or course) to reference another entity that doesn’t exist.
Summary of Steps for Many-to-Many Relationship in MongoDB
  • Create Two Collections: In this example, we created students and courses collections.
  • Use Referencing: Store the references to related documents (IDs) in arrays (e.g., course_ids in students and student_ids in courses).
  • Perform Queries: Use the _id arrays to query related collections and retrieve the associated data.
  • Use $lookup for Joins: To combine data from two collections in a single query, use MongoDB's $lookup aggregation.
  • This method effectively models a many-to-many relationship in MongoDB, allowing for flexible, scalable, and efficient data retrieval.

One-to-Many Relationship Example

In MongoDB, a one-to-many relationship can be modeled in two main ways:
  • Embedding: Embed many related documents in an array within the parent document.
  • Referencing: Store related documents in a separate collection and reference them via an identifier.
  • I'll provide an example using both approaches in the MongoDB shell.
  • Scenario: Modeling Authors and Books
  • We will model an author who writes multiple books, which is a classic one-to-many relationship.
1. Embedding Approach (One-to-Many)
  • In this approach, the books are embedded directly inside the author document as an array.
  • Step 1: Inserting Data (Embedding)

  use libraryDB  # Switch to or create the database

  db.authors.insertOne({
    _id: 1,
    name: "George Orwell",
    age: 46,
    books: [
      {
        title: "1984",
        genre: "Dystopian",
        published_year: 1949
      },
      {
        title: "Animal Farm",
        genre: "Political Satire",
        published_year: 1945
      }
    ]
  })

  • In this case, the books field is an array, and each book is stored as an embedded document within the author document.
  • Step 2: Querying Data (Embedding)
  • To retrieve an author along with their books, you can simply query the authors collection:

  db.authors.findOne({ _id: 1 })

  // Output:
  {
    "_id": 1,
    "name": "George Orwell",
    "age": 46,
    "books": [
      {
        "title": "1984",
        "genre": "Dystopian",
        "published_year": 1949
      },
      {
        "title": "Animal Farm",
        "genre": "Political Satire",
        "published_year": 1945
      }
    ]
  }


Explanation of Embedding:

Advantages:
  • Simpler data retrieval: Since the books are stored directly within the author document, you don’t need to perform additional queries.
  • Single query for updates: You can update the entire author and their books in one go.
Disadvantages:
  • Document size limit: MongoDB has a 16 MB document size limit. If an author writes too many books, the document can grow large.
  • Data duplication: If books are referenced by other entities (e.g., publishers), duplication can occur.
2. Referencing Approach (One-to-Many)
  • In this approach, the books are stored in a separate collection, and the author document references the book_ids in an array. This approach avoids embedding large amounts of data inside a single document.
  • Step 1: Inserting Data (Referencing)
  • Insert data into the books collection:

    db.books.insertMany([
      {
        _id: 101,
        title: "1984",
        genre: "Dystopian",
        published_year: 1949
      },
      {
        _id: 102,
        title: "Animal Farm",
        genre: "Political Satire",
        published_year: 1945
      }
    ])

  • Insert data into the authors collection with references to book_ids:

    db.authors.insertOne({
      _id: 1,
      name: "George Orwell",
      age: 46,
      book_ids: [101, 102]  # Array of references to the books
    })

  • Step 2: Querying Data (Referencing)
  • To get an author and their books, you need to:
    • 1. Query the authors collection to get the book_ids.
    • 2. Use the book_ids to query the books collection.
  • Step 2.1: Find the author:

    var author = db.authors.findOne({ _id: 1 })

  • Step 2.2: Find the books using the book_ids:

    db.books.find({ _id: { $in: author.book_ids } })

    // Output (from the books collection):
    [
      {
        "_id": 101,
        "title": "1984",
        "genre": "Dystopian",
        "published_year": 1949
      },
      {
        "_id": 102,
        "title": "Animal Farm",
        "genre": "Political Satire",
        "published_year": 1945
      }
    ]

  • Step 3: Using $lookup to Join Collections
  • Alternatively, you can use the $lookup operator to join the authors and books collections in a single query:

    db.authors.aggregate([
      {
        $lookup: {
          from: "books",         // Collection to join with
          localField: "book_ids", // Field in the authors collection
          foreignField: "_id",    // Field in the books collection
          as: "books"             // Output array field name
        }
      }
    ])

    // Output:
    [
      {
        "_id": 1,
        "name": "George Orwell",
        "age": 46,
        "book_ids": [101, 102],
        "books": [
          {
            "_id": 101,
            "title": "1984",
            "genre": "Dystopian",
            "published_year": 1949
          },
          {
            "_id": 102,
            "title": "Animal Farm",
            "genre": "Political Satire",
            "published_year": 1945
          }
        ]
      }
    ]


Explanation of Referencing:

Advantages:
  • Flexible and scalable: The books can grow in number without causing the author document to become too large.
  • No duplication: Since the books are stored in a separate collection, other entities (like publishers or libraries) can reference them without duplicating data.
Disadvantages:
  • More complex queries: You need to perform multiple queries or use $lookup to retrieve related data.
  • Data consistency: It’s possible for an author to reference a book that doesn’t exist, which introduces potential data integrity issues unless you enforce checks at the application level.
Conclusion: When to Use Embedding vs. Referencing in One-to-Many Relationships
  • Use Embedding when:
    • The related data (e.g., books) is always accessed with the parent document (e.g., author).
    • The size of the embedded data is small and won’t grow indefinitely.
    • You want simplicity in your data model with fewer collections to manage.
  • Use Referencing when:
    • The related data (e.g., books) might be accessed independently of the parent document (e.g., author).
    • The size of the related data is large or could grow over time.
    • You want to share related data between different entities (e.g., a book is written by an author but also published by a publisher).
    • You need to avoid hitting the document size limit (16 MB in MongoDB).
  • By using either approach, you can effectively model one-to-many relationships based on the specific requirements of your application.

Debouncing and Throttling in JavaScript

Debouncing and Throttling - Made Simple! Think of these as traffic controllers for your functions: Debouncing = Wait until user stops, ...