Concepts / Introduction to Many-to-Many Relationships

Introduction to Many-to-Many Relationships

SQL does not support arrays as a native data type, making array-based many-to-many workarounds impossible.

  • Programming

The Tempting Shortcut

Suppose a course can have many students, and each student can enroll in many courses. When you first meet this many-to-many relationship, it is natural to extend the one-to-many idea: add a column to the Course table and place all related student IDs there. That approach appears simple because it keeps the relationship inside one course record. The difficulty is that relational tables are not designed to store a collection of values inside one cell.

course relationshipsstudent relationshipsCoursesmany course recordsJoin tableindexed relationship rowsStudentsmany student records
How can one course connect to many students while one student also connects to many courses?

The relationship needs to represent two directions at once: one course may be associated with many students, and one student may be associated with many courses. A join table represents those connections across multiple rows, rather than compressing all connections into one course row.

Why Arrays Do Not Fit a SQL Cell

Programmers who use Python, JavaScript, or Java are accustomed to storing collections in arrays, lists, or other single variables. It is therefore reasonable to imagine a course record containing an array of student IDs. SQL, however, does not support arrays as a native data type for this purpose. In the relational model described here, each column holds one single, indivisible value. A cell cannot contain a collection.

containsattempts to containOne column valueone indivisible valueStudent ID14Student IDcollection1, 3, 4, 5, 6, 9, 14ARRAY OF INTEGERnot a native SQL type
What would a row look like if it tried to place multiple related foreign keys in one cell, and why does that conflict with the relational table design?

The Comma-Separated Workaround

Because an array cannot be stored as a native SQL value in this design, developers sometimes place the IDs into a TEXT column as one comma-separated string. A TEXT column can hold the characters, so the value can be inserted, retrieved, and displayed. This solves only the storage-format problem. It does not make the embedded IDs behave like separate indexed foreign-key values.

A Course with Embedded Student IDs

Represent the students enrolled in course si311 using the comma-separated workaround, then identify what the database must search when looking for student 14.

Store the relationship: The Course table has one row for si311, and its student_ids column contains the text string 1,3,4,5,6,9,14.

Ask a relationship question: The query asks which courses have student 14 enrolled.

Search the stored value: The database must inspect the student_ids text and check whether the substring 14 appears inside it.

The relationship can be stored and displayed, but finding the relationship requires scanning course rows and performing string matching instead of using a simple indexed lookup.

relationship representationseparate rowseparate rowsi311student_ids: 1,3,4,5,6,9,14si311relationship rowsStudent 1course si311Student 14course si311
What is contained inside a comma-separated value, and how does that differ from storing relationships across separate join-table rows?

Following a Relationship Query

Consider the question, Which courses have student 14 enrolled? With comma-separated data, the database cannot perform a simple indexed lookup for the embedded ID. It must scan every row in the Course table, extract the student_ids string, and check whether the substring 14 appears inside it. The work is tied to the number of course rows, not just to the small number of relationships that match student 14.

scan each rowinspect textretain matchesCourse tableall course rowsstudent_ids string1,3,4,5,6,9,14Substring search14Matching coursesquery result
How does a database find one matching ID inside a comma-separated string instead of looking up an indexed foreign key?

This process combines a full-table scan with string matching. The source identifies these as computationally expensive operations and explains that string-based queries cannot use indexes. By contrast, a proper join table stores the relationship across rows with indexed foreign keys, allowing the database to locate related records through those indexes.

process each stored valuecheck embedded IDreturn matchesRead textrelationship stringScan rowsfull tableCompare texttarget IDQuery resultmatching records
What work must the database perform when searching for one ID inside relationship strings?

Growth Changes the Trade-Off

The comma-separated design may appear acceptable when the table is small. Its central problem is that every query looking for an ID inside the stored text must scan the table and perform substring matching. Because these string-based queries cannot use indexes, the work grows linearly with the number of rows.

more rowsmore rows and embedded IDsmore scan and matching workSmall tablefewer rows to scanGrowing tablemore strings to inspectLarge tablefull scan growsSlower querylinear degradation
How does query work change as the number of course records and embedded IDs increases?

Course Registration at Scale

Compare the work required to find every course for one student in a system with 500,000 courses and 2 million students.

Comma-separated design: The query must scan all 500,000 course rows and perform a substring search on each row.

Indexed join-table design: The relationship is stored in a proper join table with indexed foreign keys, allowing a fast indexed query instead of scanning every course row.

Practical effect: The source gives an example estimate of 5 to 10 seconds for the string approach and 10 to 50 milliseconds for the indexed join-table approach.

The difference becomes a usability issue as well as a performance issue: the string approach degrades with data volume, while the indexed join-table approach scales more effectively.

Mistakes in Relationship Storage

  • Treating a SQL column like a programming-language list

    SQL does not support arrays as a native data type in this design, and a relational cell cannot contain a collection.

    Fix: Represent the relationships across multiple rows in a join table with indexed foreign keys.

  • Assuming that a TEXT column creates separate foreign keys

    The database sees one text string, so finding an ID requires scanning rows and performing substring matching.

    Fix: Use a proper join table so the relationship is stored across rows rather than compressed into one string.

  • Judging the workaround only on whether it can store and display the data

    The expensive work appears when a query must find a particular relationship, especially as the table grows.

    Fix: Evaluate how relationship queries use indexes and how their work changes with data volume.

Check Your Design Reasoning

MEDIUM

A Course table stores all student IDs in one comma-separated TEXT value. You need to find every course containing student 14. Explain the sequence of work the database must perform and why the work becomes less efficient as the number of course rows grows.

Hints
  • Ask whether the database can use a simple indexed lookup for an ID embedded in text.
  • Identify what must happen to every Course row.
  • Connect the number of rows scanned to the linear performance degradation described in the source.

What do you think happens?

Which design is expected to scale better for finding all courses associated with one student: a comma-separated string in Course, or a join table with indexed foreign keys?

  • The comma-separated string
  • The indexed join table
  • Both designs scale in the same way
Reveal answer

Answer: The indexed join table

String-based queries require full-table scans and substring searches that cannot use indexes. A proper join table with indexed foreign keys enables fast queries that scale more effectively with data volume.

The Design Decision

  1. SQL does not support arrays as a native data type for placing a collection of related IDs in one relational cell.
  2. Comma-separated strings can store multiple IDs as text, but they turn relationship queries into full-table scans and substring searches.
  3. String-based queries cannot use indexes, so their work grows linearly as the number of rows increases.
  4. A normalized join table with indexed foreign keys spreads relationships across rows and supports fast, scalable lookups.
  5. The right design should be judged by the cost of querying relationships as data volume grows, not only by how easy the data is to store or display.

Key Takeaways

  • Arrays are not a native SQL solution for storing many related foreign keys in one cell.
  • Comma-separated strings compress relationships into text but require full-table scans and substring searches.
  • Because those searches cannot use indexes, their cost grows linearly with the number of rows.
  • Indexed join tables store relationships across rows and support much faster queries as data volume increases.