The Relational Model
From Algebra to SQL
JOIN enrollments e ON s.id = e.student_id
JrCodex·6 min read
Jr Codex DBMS Notes
Level: Beginner–Intermediate Prerequisites: Chapter 3: Relational Algebra Time to complete: ~15 minutes
Table of Contents
- The Translation Table
- Reading a Query as Algebra
- Where SQL Departs from the Model
- The Logical Order of Evaluation
- Summary & Next Steps
1. The Translation Table
| Algebra | SQL | Notes |
|---|---|---|
| σ selection | WHERE | Filters rows |
| π projection | SELECT columns | Does not deduplicate |
| π with dedup | SELECT DISTINCT | Matches the algebra |
| × product | FROM a, b or CROSS JOIN | Rarely wanted alone |
| ⋈ theta join | JOIN ... ON | The everyday join |
| ⋈ natural join | NATURAL JOIN | Avoid — see Chapter 3 |
| ⟕ left outer | LEFT JOIN | Keeps unmatched left rows |
| ∪ union | UNION (dedups) / UNION ALL | UNION ALL is faster |
| ∩ intersection | INTERSECT | |
| − difference | EXCEPT (MINUS in Oracle) | |
| ρ rename | AS | Required for self-joins |
| γ grouping | GROUP BY + aggregates | |
| ÷ division | double NOT EXISTS | No direct syntax |
2. Reading a Query as Algebra
SELECT DISTINCT s.name, c.title
FROM students s
JOIN enrollments e ON s.id = e.student_id
JOIN courses c ON e.course_id = c.id
WHERE s.city = 'Pune' AND s.marks > 80;The Same Query, as Algebra
─────────────────────────────────────────
π_name,title(
σ_city='Pune' ∧ marks>80 (
students ⋈_s.id=e.student_id enrollments
⋈_e.course_id=c.id courses
)
)
SELECT DISTINCT ──► π
FROM / JOIN ──► ⋈
WHERE ──► σ
AS (s, e, c) ──► ρ
─────────────────────────────────────────
The Useful Habit
─────────────────────────────────────────
When a query returns the wrong rows, translate it
back to algebra in your head:
"which σ is filtering?"
"is this a ⋈ or an accidental ×?"
"am I projecting before or after the filter?"
Most wrong-result bugs are a selection applied at
the wrong point, or a join condition that is
missing — which turns ⋈ into ×.
─────────────────────────────────────────
3. Where SQL Departs from the Model
SQL is not a faithful implementation of the algebra. Four differences matter in practice.
1. TABLES ARE BAGS, NOT SETS
─────────────────────────────────────────
SQL permits duplicate rows unless a key forbids
them, and SELECT keeps duplicates.
WHY: deduplication requires sorting or hashing
the entire result — expensive, and usually
unnecessary. SQL made you ask for it.
CONSEQUENCE: use UNION ALL rather than UNION when
you know there are no duplicates. It skips the
dedup pass entirely and is substantially faster.
2. COLUMN ORDER IS SIGNIFICANT
─────────────────────────────────────────
SELECT * returns columns in schema order, and
INSERT without a column list depends on it.
The algebra says attribute order is meaningless.
SQL disagrees, which is why both of those
constructs break when a column is added.
3. THREE-VALUED LOGIC
─────────────────────────────────────────
NULL makes comparisons UNKNOWN (Chapter 1), so
SQL's WHERE is not simple boolean logic.
A consequence worth knowing:
NOT IN (subquery containing a NULL) returns NO
ROWS, ever. Use NOT EXISTS instead.
4. ORDER BY AND LIMIT
─────────────────────────────────────────
Relations are unordered, so ORDER BY is outside
the algebra entirely — it converts a relation
into a LIST on the way to the client.
This is why ORDER BY is only valid at the very
end of a query, not inside a subquery where it
would be meaningless.
─────────────────────────────────────────
-- Difference 1, in practice:
SELECT city FROM students; -- duplicates kept: Pune, Delhi, Pune
SELECT DISTINCT city FROM students; -- the algebra's π: Pune, Delhi
-- Difference 3, the NOT IN trap:
SELECT * FROM students
WHERE id NOT IN (SELECT student_id FROM enrollments);
-- If ANY student_id is NULL, this returns ZERO rows.
SELECT * FROM students s
WHERE NOT EXISTS (SELECT 1 FROM enrollments e WHERE e.student_id = s.id);
-- Correct regardless of NULLs. Prefer this form.4. The Logical Order of Evaluation
SQL is written in one order and evaluated in another. Knowing the real order resolves most beginner confusion.
Written Order vs Logical Order
─────────────────────────────────────────
WRITTEN LOGICAL (what happens)
─────────────────────────────────────────
SELECT 1. FROM / JOIN
FROM 2. WHERE
WHERE 3. GROUP BY
GROUP BY 4. HAVING
HAVING 5. SELECT
ORDER BY 6. DISTINCT
LIMIT 7. ORDER BY
8. LIMIT
─────────────────────────────────────────
Three Confusions This Explains
─────────────────────────────────────────
"Why can't I use a SELECT alias in WHERE?"
WHERE runs at step 2; the alias is created at
step 5. It does not exist yet.
"What is the difference between WHERE and HAVING?"
WHERE filters ROWS before grouping (step 2).
HAVING filters GROUPS after (step 4). So
WHERE cannot see aggregates, and HAVING can.
"Why CAN I use a SELECT alias in ORDER BY?"
ORDER BY runs at step 7, after SELECT has
created it.
─────────────────────────────────────────
-- Fails: `total` does not exist at WHERE time.
SELECT city, COUNT(*) AS total FROM students
WHERE total > 1 GROUP BY city;
-- Correct: filter GROUPS with HAVING.
SELECT city, COUNT(*) AS total FROM students
GROUP BY city HAVING COUNT(*) > 1
ORDER BY total DESC; -- but the alias IS available hereThe Performance Corollary
─────────────────────────────────────────
WHERE runs before grouping; HAVING runs after.
So a condition that could be written either way
belongs in WHERE — it discards rows before the
expensive grouping step, rather than after.
─────────────────────────────────────────
5. Summary & Next Steps
Key Takeaways
- Every SQL clause maps onto an algebra operation, and translating a misbehaving query back into algebra usually locates the bug — most often a missing join condition turning ⋈ into ×.
- SQL uses bags rather than sets, so
SELECTkeeps duplicates andUNION ALLis faster thanUNIONwhen you know there are none. NOT INagainst a subquery containing NULL returns no rows at all;NOT EXISTSis the correct form.- SQL is evaluated in a different order than it is written, which explains why aliases work in
ORDER BYbut notWHERE, and why filters belong inWHERErather thanHAVINGwhen either would work.
Module 2 Complete — What's Next
You have the formal model: relations, keys, constraints, and the algebra SQL compiles to. Module 3 turns that into fluent, practical SQL — the language you will actually spend your time writing.
Concept Check
- Why does
SELECT city FROM studentsreturn three rows whereπ_city(students)returns two? - A query filters on
WHERE total > 1wheretotalis aSELECTalias, and errors. Explain using the logical evaluation order. - When both would produce the same result, why should a condition go in
WHERErather thanHAVING?
Next Module
Jr Codex — 1-on-1 Personalized Coaching | Back to Module Index | Back to DBMS Index