DSA, Database System & Operating System β Data Models, Normalization, and SQL, NEC licence examination syllabus (Nepal Engineering Council).
Relational Algebra
Six primitive operators that can express any relational query β the formal language SQL is compiled into.
π Where this lives: relational algebra is not an academic curiosity β it is the intermediate representation inside every SQL engine. Your query text is parsed into an algebra tree, the optimiser rewrites that tree using algebraic equivalences (push selections down, reorder joins), and only then is a physical plan chosen. When you run EXPLAIN you are reading a decorated algebra tree. That is why WHERE a=1 AND b=2 and WHERE b=2 AND a=1 produce identical plans: both compile to the same algebra. Search "query rewrite algebraic equivalence rules" β the transformations in the optimiser are exactly the identities below.
The set-versus-bag distinction is the one thing to carry away from this comparison. In the algebra, Ο is duplicate-eliminating by definition, so Οdept(Student) has exactly two rows. In SQL, SELECT dept FROM student has four. Every textbook algebra answer implicitly assumes DISTINCT, which is why translating algebra to SQL usually means adding it.
Division β the hardest operator
R Γ· S answers "which values in R are associated with ALL of
S?"
FORMAL DEFINITION
Let R have attributes (X, Y) and S have attributes (Y).
Then R Γ· S has attributes (X), and contains
{ x | for EVERY y in S, the tuple (x,y) is in R }
EXAMPLE β "students enrolled in every course"
Enrol(roll, course) Γ· Ο_code(Course)
Enrol: Course:
101 C1 C1
101 C2 C2
101 C3 C3
102 C1
102 C3
103 C2
roll 101 β {C1,C2,C3} β {C1,C2,C3} β INCLUDED
roll 102 β {C1,C3} missing C2 β
roll 103 β {C2} missing C1,C3 β
roll 104 β {} missing all β
Result: {101}
EXPRESSING Γ· WITH THE PRIMITIVES β the classic derivation:
R Γ· S = Ο_X(R) β Ο_X( (Ο_X(R) Γ S) β R )
Read it inside out:
Ο_X(R) all candidate x values
Ο_X(R) Γ S every (x,y) combination that WOULD
be needed
(Ο_X(R) Γ S) β R the combinations that are MISSING
Ο_X( ... ) the x values that are missing
something
Ο_X(R) β ... the x values missing NOTHING β
Applied to our data:
Ο_roll(Enrol) = {101,102,103}
Ο_roll(Enrol) Γ Course = 9 pairs
minus Enrol = {(102,C2),(103,C1),(103,C3)}
Ο_roll of that = {102,103}
{101,102,103} β {102,103} = {101} β
IN SQL β the double-NOT-EXISTS idiom:
SELECT s.roll FROM student s
WHERE NOT EXISTS ( -- there is no course
SELECT c.code FROM course c
WHERE NOT EXISTS ( -- that this student
SELECT 1 FROM enrol e -- has not taken
WHERE e.roll = s.roll AND e.course = c.code));
Read the nesting as: "no course exists such that the student
has not taken it" = "the student has taken every course".
That double negation is why division is considered the
hardest operator to express.
THE COUNTING ALTERNATIVE β easier to read and usually faster:
SELECT roll FROM enrol
GROUP BY roll
HAVING COUNT(DISTINCT course) = (SELECT COUNT(*) FROM course);
β 101 has 3 distinct courses; there are 3 courses. β
Note DISTINCT is required if (roll,course) is not unique.
Algebraic equivalences β what the optimiser uses
-- The same query written two ways. Logically identical;
-- the optimiser produces the same plan for both, which is
-- itself the proof that the rewrite rules are being applied.
-- WRITTEN NAIVELY: product then filter
EXPLAIN (COSTS OFF)
SELECT s.name, c.title
FROM student s, enrol e, course c
WHERE s.roll = e.roll
AND e.course = c.code
AND s.dept = 'ACtE07';
-- WRITTEN OPTIMALLY: filter pushed down by hand
EXPLAIN (COSTS OFF)
SELECT s.name, c.title
FROM (SELECT roll, name FROM student WHERE dept='ACtE07') s
JOIN enrol e ON e.roll = s.roll
JOIN course c ON c.code = e.course;
-- ACTUAL PLAN from PostgreSQL 18 (both forms give this):
--
-- Nested Loop
-- -> Hash Join
-- Hash Cond: (e.roll = s.roll)
-- -> Seq Scan on enrol e
-- -> Hash
-- -> Seq Scan on student s
-- Filter: (dept = 'ACtE07'::bpchar)
-- ^^^^^^^ pushed DOWN β
-- -> Index Scan using course_pkey on course c
-- Index Cond: (code = e.course)
--
-- Read it bottom-up. The filter on dept sits at the SCAN of
-- student, not above the joins β rule 8 applied
-- automatically. Note also that the optimiser chose two
-- DIFFERENT join algorithms: a hash join for student/enrol
-- (no useful index on enrol.roll) and a nested loop with an
-- index scan for course (a primary-key lookup per row).
-- Algebra decides WHAT; the planner decides HOW.
--
-- You do not need to hand-optimise: the optimiser applies
-- rule 8 for you. But knowing the rule tells you WHY the
-- plan looks like that, and lets you spot the cases where
-- it CANNOT push a predicate down β for example through an
-- OUTER join, or when the predicate calls a
-- non-deterministic function.
Extended operators β aggregation and outer joins
The six primitives cannot express COUNT, SUM or NULL-padding.
Practical algebra adds:
AGGREGATION π (also written Ξ³)
grouping-attrs π aggregate-list (R)
dept π COUNT(roll), AVG(marks) (Student)
β SELECT dept, COUNT(roll), AVG(marks)
FROM Student GROUP BY dept;
With no grouping attributes it aggregates the whole
relation:
π COUNT(*)(Student) β SELECT COUNT(*) FROM Student
OUTER JOINS β need NULL, which the pure algebra lacks
R β S LEFT outer join (keep unmatched R rows)
R β S RIGHT outer join
R β S FULL outer join
GENERALISED PROJECTION β allows computed columns
Ο_{roll, marks * 1.1 AS scaled}(Student)
The pure Ο may only choose existing attributes.
WHY THE DISTINCTION MATTERS FOR EXAMS: if a question says
"express in relational algebra" and the answer needs a count
or an average, you must use π and say so. Writing COUNT
inside a Ο is wrong β Ο takes a per-row predicate and cannot
see other rows.
β Ο_{COUNT(*) > 2}(Enrol)
β Ο_{cnt > 2}( roll π COUNT(*) AS cnt (Enrol) )
That is the algebraic form of the WHERE-versus-HAVING
distinction: Ο before π is WHERE, Ο after π is HAVING.
π Go further: relational algebra is procedural β you specify an order of operations. Its declarative twin is relational calculus (tuple and domain calculus), where you state a logical condition and specify no order at all. Codd's theorem proves the two have equal expressive power, which is the formal justification for SQL being declarative: you write the what, the optimiser derives a how. Datalog and modern query languages descend from the calculus side. Search "Codd's theorem relational completeness" and "tuple relational calculus vs algebra".