Chapter 33: PostgreSQL LEFT JOIN
PostgreSQL LEFT JOIN βπ‘οΈ
Last time we covered INNER JOIN β which only shows matching rows from both tables. Today we go to LEFT JOIN β which says:
βI want every single row from the left table, no matter what. If there is a matching row in the right table β great, show it. If there is no match β still show the left row, but put NULL in the right tableβs columns.β
This is extremely common because in real life you almost always want to see all records from your main entity (customers, students, products, ordersβ¦) even if some of them donβt have related data yet.
1. What LEFT JOIN really does (teacher drawing on imaginary board)
Imagine two circles:
- Left circle = students (the main table you care about)
- Right circle = enrollments (optional related data)
INNER JOIN β only the overlap (students who enrolled) LEFT JOIN β entire left circle + overlap β Students with no enrollments still appear β with NULLs in enrollment columns
Official PostgreSQL wording (from docs):
LEFT OUTER JOIN returns all rows from the left table and the matched rows from the right table. The result is NULL from the right side if there is no match.
(Note: people usually just say LEFT JOIN β the word OUTER is optional and almost never written)
2. Basic syntax (the shape you will type 1,000 times)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
SELECT left_table.column1, right_table.column2, ... FROM left_table LEFT JOIN right_table ON left_table.foreign_key = right_table.primary_key [WHERE β¦] [ORDER BY β¦]; |
- The table written first (after FROM) is the left table
- Everything from left table is kept
- Matching rows from right table are added
- No match β right columns = NULL
3. Real, detailed example β using our college schema
Tables (same as last time):
students (left table β we want all students)
| id | first_name | last_name | gpa | |
|---|---|---|---|---|
| 1 | Aarav | Patel | aarav@hyd.edu | 8.95 |
| 2 | Priya | Reddy | priya@gmail.com | 9.60 |
| 3 | Rahul | Sharma | rahul@yahoo.com | 7.85 |
| 4 | Sneha | Kumar | sneha@gmail.com | 9.20 |
| 5 | Vikram | Naik | vikram@hyd.edu | 8.40 |
enrollments (right table β optional)
| id | student_id | course_id | grade | enrolled_at |
|---|---|---|---|---|
| 1 | 1 | 1 | A | 2025-08-01 |
| 2 | 1 | 2 | A+ | 2025-08-02 |
| 3 | 2 | 1 | B+ | 2025-08-01 |
| 4 | 4 | 3 | A | 2025-08-05 |
| 5 | 5 | 2 | B | 2025-08-10 |
(Rahul id=3 has no enrollment)
Example 1: Basic LEFT JOIN β all students + their enrollments
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
SELECT s.first_name || ' ' || s.last_name AS student_name, e.course_id, e.grade, e.enrolled_at FROM students s LEFT JOIN enrollments e ON s.id = e.student_id ORDER BY student_name; |
Result:
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 |
student_name | course_id | grade | enrolled_at ---------------|-----------|-------|--------------------- Aarav Patel | 1 | A | 2025-08-01 Aarav Patel | 2 | A+ | 2025-08-02 Priya Reddy | 1 | B+ | 2025-08-01 Rahul Sharma | NULL | NULL | NULL Sneha Kumar | 3 | A | 2025-08-05 Vikram Naik | 2 | B | 2025-08-10 |
β Rahul appears! β but with NULLs in enrollment columns β This is the classic use-case of LEFT JOIN
Example 2: LEFT JOIN + multiple tables + COALESCE (very real pattern)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
SELECT s.first_name || ' ' || s.last_name AS student, c.name AS course_name, COALESCE(e.grade, 'Not enrolled') AS grade_status, COALESCE(e.enrolled_at::text, '-') AS enrollment_date FROM students s LEFT JOIN enrollments e ON s.id = e.student_id LEFT JOIN courses c ON c.id = e.course_id ORDER BY student, course_name; |
Result (partial):
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 |
student | course_name | grade_status | enrollment_date --------------|---------------------------------|----------------|----------------- Aarav Patel | Database Systems | A+ | 2025-08-02 Aarav Patel | Introduction to Programming | A | 2025-08-01 Priya Reddy | Introduction to Programming | B+ | 2025-08-01 Rahul Sharma | NULL | Not enrolled | - Sneha Kumar | Machine Learning Basics | A | 2025-08-05 Vikram Naik | Database Systems | B | 2025-08-10 |
β COALESCE turns ugly NULLs into nice readable text
Example 3: Find students with no enrollments (very common report)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 |
SELECT s.first_name || ' ' || s.last_name AS student_name, s.email FROM students s LEFT JOIN enrollments e ON s.id = e.student_id WHERE e.student_id IS NULL -- magic condition! ORDER BY student_name; |
Result:
|
0 1 2 3 4 5 6 7 8 |
student_name | email ---------------|------------------- Rahul Sharma | rahul@yahoo.com |
β This is the classic way to find βorphanedβ / βnon-relatedβ records β Very important interview question pattern
4. LEFT JOIN vs INNER JOIN β quick comparison (same data)
| Query type | Rows returned | Rahul included? | Use case feeling |
|---|---|---|---|
| INNER JOIN | 5 rows | No | βOnly enrolled studentsβ |
| LEFT JOIN | 6 rows | Yes (with NULLs) | βAll students β show enrollment if existsβ |
5. Best practices & common mistakes (2026 production tips)
| Mistake / Question | Problem / Surprise | Fix / Recommendation |
|---|---|---|
| Forget WHERE β¦ IS NULL | No rows for non-matches | Use WHERE right.id IS NULL for non-matching rows |
| Use LEFT when you really want INNER | Too many rows with NULLs | Choose join type based on business question |
| LEFT JOIN on wrong column | All rows appear with NULLs | Double-check ON condition (fk = pk) |
| Performance on large tables | Slow if no index on join columns | Index foreign key columns (almost always) |
| LEFT JOIN + WHERE on right table column | Turns into INNER JOIN! | Move right-table conditions to ON clause or use COALESCE |
Dangerous pattern (very common mistake):
|
0 1 2 3 4 5 6 7 8 |
FROM students s LEFT JOIN enrollments e ON s.id = e.student_id WHERE e.grade = 'A' -- β this kills the LEFT ! |
β Becomes equivalent to INNER JOIN β Fix: move to ON β¦ AND e.grade = ‘A’ or use COALESCE
Your mini homework right now
- Write a LEFT JOIN query showing all students + their grades (use COALESCE for ‘Not enrolled’)
- Modify it to show only students with no enrollments (WHERE β¦ IS NULL)
- Add a third LEFT JOIN to also show course names
- Try the dangerous mistake above β see how Rahul disappears
Next class?
Tell me:
- Want to compare LEFT JOIN vs RIGHT JOIN vs FULL OUTER JOIN with same data?
- LEFT JOIN with multiple tables (3β4 way join)?
- LEFT JOIN + aggregate (COUNT, SUM, AVG) β e.g. students + number of courses?
- Performance differences INNER vs LEFT + index usage?
Your guru is ready β what’s the next topic? π
