FirstHack Learn
Log in Sign up free
Lessons in this course 0/6 All courses DBMS and SQL

CSE

Progress0 / 6 lessons
  1. 1. Why databases exist and what a table really is
  2. 2. SELECT, WHERE, ORDER BY and LIMIT
  3. 3. JOINs, with a worked two-table example
  4. 4. GROUP BY, aggregates, and HAVING vs WHERE
  5. 5. Keys and normalisation up to 3NF
  6. 6. Transactions, ACID, and what happens on a crash

Courses › DBMS and SQL

JOINs, with a worked two-table example

Why data is split across tables, and exactly which rows INNER and LEFT JOIN produce.

12 min read · Lesson 3 of 6 · Free

Why the data is in two tables at all

You could store marks inside the students table: columns subject1, marks1, subject2, marks2. Then a student takes a sixth subject and you have to change the table structure. That is the wrong shape.

The right shape: one table per kind of thing.

SQL
CREATE TABLE students (
    roll_no INT PRIMARY KEY,
    name    VARCHAR(60) NOT NULL,
    branch  VARCHAR(10) NOT NULL
);

CREATE TABLE marks (
    id       INT PRIMARY KEY,
    roll_no  INT NOT NULL,
    subject  VARCHAR(20) NOT NULL,
    score    INT NOT NULL,
    FOREIGN KEY (roll_no) REFERENCES students(roll_no)
);

FOREIGN KEY (roll_no) REFERENCES students(roll_no) is the important line. It tells the database: a mark row must point at a student that exists. Insert a mark for roll 999 and the insert is rejected. Broken data is now impossible, not just discouraged.

The data

students:

roll_no name branch
101 Anita Sharma CSE
102 Ravi Kumar CSE
103 Meera Nair ECE
104 Imran Qureshi CSE

marks:

id roll_no subject score
1 101 DBMS 78
2 101 OS 85
3 102 DBMS 61
4 103 DBMS 92

Notice: Anita has two mark rows. Imran has none — he joined late and has not written an exam yet.

INNER JOIN

SQL
SELECT s.roll_no, s.name, m.subject, m.score
FROM students s
INNER JOIN marks m ON s.roll_no = m.roll_no
ORDER BY s.roll_no, m.subject;

Result — exactly four rows:

roll_no name subject score
101 Anita Sharma DBMS 78
101 Anita Sharma OS 85
102 Ravi Kumar DBMS 61
103 Meera Nair DBMS 92

Work through what happened, because this is the whole idea.

The database looks at every possible pairing of a student row with a marks row. Four students times four mark rows is sixteen pairs. Then it keeps only the pairs where s.roll_no = m.roll_no. Twelve pairs fail that test and are dropped. Four survive.

Two consequences fall straight out of that:

Anita appears twice. She has two matching mark rows, so she is in two surviving pairs. Her name is repeated. This is normal and correct — the join result is not a list of students, it is a list of student-mark facts.

Imran vanishes. No mark row has roll_no = 104, so no pair containing Imran survives. An INNER JOIN silently deletes rows that have no match.

s and m are table aliases. They save typing and, more importantly, make s.roll_no versus m.roll_no unambiguous when both tables have a column of the same name.

LEFT JOIN

When "silently deletes rows" is wrong for your question, use LEFT JOIN. It keeps every row from the left table, matched or not, and fills the right-hand columns with NULL when there is no match.

SQL
SELECT s.roll_no, s.name, m.subject, m.score
FROM students s
LEFT JOIN marks m ON s.roll_no = m.roll_no
ORDER BY s.roll_no;
roll_no name subject score
101 Anita Sharma DBMS 78
101 Anita Sharma OS 85
102 Ravi Kumar DBMS 61
103 Meera Nair DBMS 92
104 Imran Qureshi NULL NULL

Five rows now. Imran is back, with NULL where his marks would be.

This gives you the "who has not written any exam" query for free:

SQL
SELECT s.roll_no, s.name
FROM students s
LEFT JOIN marks m ON s.roll_no = m.roll_no
WHERE m.roll_no IS NULL;

Only Imran. LEFT JOIN plus IS NULL on the right side is the standard way to find "rows in A with nothing in B". Learn this pattern — it comes up in interviews constantly.

RIGHT JOIN is the mirror image and is rarely used, because you can always swap the table order and write a LEFT JOIN instead, which is easier to read. FULL OUTER JOIN keeps unmatched rows from both sides; MySQL does not support it directly, PostgreSQL does.

⚠️

Putting the filter in the wrong place breaks a LEFT JOIN. Compare these two:

LEFT JOIN marks m ON s.roll_no = m.roll_no AND m.score > 70

LEFT JOIN marks m ON s.roll_no = m.roll_no WHERE m.score > 70

The first keeps all four students and only attaches high marks. The second runs the join, then filters the finished result — and since Imran's m.score is NULL, NULL > 70 is not true, so Imran is thrown out. The WHERE version quietly turns your LEFT JOIN back into an INNER JOIN. This exact bug appears in real production code every week.

Reading a join in your head

When you see a join, ask three questions in order. Which table is on the left? What is the ON condition? Can a single left row match more than one right row? Answer those three and you can predict the number of output rows before you press run.