Introduction to Many-to-Many Relationships
SQL does not support arrays as a native data type, making array-based many-to-many workarounds impossible.
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.
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.
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.
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.
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.
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.
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
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?
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
- SQL does not support arrays as a native data type for placing a collection of related IDs in one relational cell.
- Comma-separated strings can store multiple IDs as text, but they turn relationship queries into full-table scans and substring searches.
- String-based queries cannot use indexes, so their work grows linearly as the number of rows increases.
- A normalized join table with indexed foreign keys spreads relationships across rows and supports fast, scalable lookups.
- 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.