1️⃣ What is Many-to-Many?#
- A Many-to-Many relationship means:
One record can be related to many records on the other side
AND
The other side can also relate to many records back
Real-world examples
Example |
Meaning |
|---|---|
Students ↔ Courses |
A student takes many courses, a course has many students |
Products ↔ Tags |
A product has many tags, a tag belongs to many products |
Users ↔ Groups |
A user can be in many groups, a group has many users |
2️⃣ Mental Model (IMPORTANT)#
Databases do NOT store many-to-many directly
- Instead:
A third table (called a junction / join table) is created
Student ←→ Enrollment ←→ Course
3️⃣ Django Models Example#
Student & Course
# models.py
class Course(models.Model):
title = models.CharField(max_length=100)
def __str__(self):
return self.title
class Student(models.Model):
name = models.CharField(max_length=100)
courses = models.ManyToManyField(Course)
def __str__(self):
return self.name
You wrote only 2 models
Django creates 3 tables
4️⃣ Tables Django Creates (PostgreSQL)#
Table 1: student
CREATE TABLE student (
id SERIAL PRIMARY KEY,
name VARCHAR(100)
);
Table 2: course
CREATE TABLE course (
id SERIAL PRIMARY KEY,
title VARCHAR(100)
);
Table 3: AUTO-GENERATED JOIN TABLE
Django auto-creates something like:
CREATE TABLE student_courses (
id SERIAL PRIMARY KEY,
student_id INTEGER NOT NULL,
course_id INTEGER NOT NULL,
UNIQUE (student_id, course_id),
FOREIGN KEY (student_id) REFERENCES student(id) ON DELETE CASCADE,
FOREIGN KEY (course_id) REFERENCES course(id) ON DELETE CASCADE
);
This is the hidden magic table
This is the real many-to-many implementation
5️⃣ What Data Looks Like#
student
id |
name |
|---|---|
1 |
Ali |
2 |
Sara |
course
id |
title |
|---|---|
1 |
Math |
2 |
Physics |
student_courses (JOIN TABLE)
id |
student_id |
|---|---|
course_id |
1 |
1 |
1 |
2 |
1 |
2 |
3 |
2 |
1 |
- Meaning:
Ali → Math, Physics
Sara → Math
6️⃣ PostgreSQL Queries (REAL SQL)#
All courses for student “Ali”
SELECT c.title
FROM course c
JOIN student_courses sc ON c.id = sc.course_id
JOIN student s ON s.id = sc.student_id
WHERE s.name = 'Ali';
All students in “Math” course
SELECT s.name
FROM student s
JOIN student_courses sc ON s.id = sc.student_id
JOIN course c ON c.id = sc.course_id
WHERE c.title = 'Math';
7️⃣ Same thing in Django ORM (for comparison)#
Courses of Ali
student = Student.objects.get(name="Ali")
student.courses.all()
Students in Math
course = Course.objects.get(title="Math")
course.student_set.all()
Django ORM hides the join table
PostgreSQL still executes JOINs internally
8️⃣ Custom Many-to-Many (EXTRA IMPORTANT)#
Sometimes you need extra fields in the relationship
(example: enrollment date, grade)
Use through
class Enrollment(models.Model):
student = models.ForeignKey("Student", on_delete=models.CASCADE)
course = models.ForeignKey("Course", on_delete=models.CASCADE)
enrolled_at = models.DateTimeField(auto_now_add=True)
grade = models.CharField(max_length=2)
class Student(models.Model):
name = models.CharField(max_length=100)
courses = models.ManyToManyField("Course", through="Enrollment")
Now the join table is explicit and controllable.
9️⃣ PostgreSQL for custom join table#
CREATE TABLE enrollment (
id SERIAL PRIMARY KEY,
student_id INTEGER REFERENCES student(id),
course_id INTEGER REFERENCES course(id),
enrolled_at TIMESTAMP,
grade VARCHAR(2)
);
Key Takeaways (MEMORIZE THIS)
✔ Many-to-Many = 3 tables
✔ Django auto-creates the join table
✔ PostgreSQL uses JOINs, not magic
✔ ManyToManyField is ORM sugar
✔ Use through when relationship has data