Relational Model & Relational Algebra
The mathematical contract behind relational databases: relations as sets of typed tuples, integrity constraints as invariants, and a closed algebra that turns queries into expressions the optimizer can safely transform.
The relational model is not “data in rectangular files.” It is a separation of concerns: users describe what relation they want, an algebra gives that request precise meaning, constraints define which database states are legal, and the engine is free to choose a physical representation and execution plan. That logical/physical boundary is why an index, partitioning scheme, or join algorithm can change without changing the meaning of a correct query.
The theory is deliberately small. A relation is a set of typed tuples; every query operator consumes relations and produces another relation. From that closure property comes composability, equivalence reasoning, views, and most rule-based query optimization.
Four layers that must not be conflated #
flowchart TD W["Real-world facts"] --> S["Schema and constraints"] S --> R["Relation values"] R --> A["Relational expression"] A --> P["Physical execution plan"] P --> O["Result relation"]
| Layer | Question | Examples |
|---|---|---|
| Meaning | What fact does one tuple assert? | “Product 42 is supplied by supplier 7 at price 9.50” |
| Logical schema | Which attributes and invariants represent those facts? | domains, keys, foreign keys, checks |
| Logical query | Which relation follows from the current facts? | selection, projection, join, difference |
| Physical plan | How will the engine compute it? | sequential scan, B-tree lookup, hash join, sort-merge join |
The formal object #
Let each attribute have a domain . A relation schema
names the attributes and their domains. A tuple is a finite mapping from attribute names to domain values, so . A relation value (often called an instance) is a finite set of such tuples:
Using attribute names rather than only positions matters: it makes projection, renaming, and natural join precise without treating the third physical column as part of the fact's identity.
- Heading
- The schema: attribute names and domains.
- Body
- The current set of tuples satisfying that heading and the declared constraints.
- Degree (arity)
- The number of attributes in the heading.
- Cardinality
- The number of tuples in the body.
A mathematical relation has three consequences that SQL users often overlook:
- tuples are unordered;
- attributes are identified by name, not display position;
- duplicate tuples do not exist because the body is a set.
The empty relation is valid. So are a zero-degree relation containing no tuple
(false) and a zero-degree relation containing the single empty tuple (true):
they are the relational counterparts of Boolean values and explain why closed
relational expressions can represent predicates as well as data.
A running database #
We will use three relation schemas:
with the following small instances:
Supplier
| sid | name | city |
|---|---|---|
| 1 | Ada Parts | Kyiv |
| 2 | Bolt Works | Lviv |
| 3 | Circuit House | Kyiv |
Product
| pid | label | category |
|---|---|---|
| 10 | M3 bolt | mechanical |
| 20 | op-amp | electronic |
| 30 | encoder | electronic |
Supplies
| sid | pid | price |
|---|---|---|
| 1 | 10 | 0.12 |
| 1 | 20 | 1.80 |
| 2 | 10 | 0.09 |
| 3 | 20 | 1.65 |
| 3 | 30 | 8.40 |
One Supplies tuple means one supplier offers one product at one price. That
sentence is part of the schema contract. If prices vary by currency, quantity,
or validity interval, those attributes—or separate relations representing those
facts—must be added. A table cannot repair an underspecified predicate.
Integrity constraints define legal states #
Domains say which individual values are admissible. Constraints say which combinations of tuples form a legal database state.
Keys #
For an attribute set , is a superkey when no two distinct tuples in any legal instance agree on every attribute in . A candidate key is a minimal superkey: removing any attribute destroys uniqueness. One candidate key may be designated the primary key; the remaining candidate keys are alternate keys. “Primary” is a schema/implementation choice, not a stronger mathematical kind of key.
In the running example:
Supplier.sidandProduct.pidare candidate keys;(Supplies.sid, Supplies.pid)is a candidate key if each supplier has at most one current offer per product;(sid, pid, price)is a superkey but not a candidate key, becausepriceis redundant for uniqueness.
Referential and general constraints #
A foreign key from Supplies.sid to Supplier.sid requires every non-null
referencing value to identify an existing supplier. Likewise for pid. This is
referential integrity: the database cannot assert an offer for a supplier or
product absent from the referenced relation.
Other invariants include:
- domain constraints:
priceis numeric; - tuple constraints:
price > 0; - relation constraints: no two active offers share
(sid, pid); - cross-relation constraints: every offer references a supplier and product.
SQL exposes these through types, NOT NULL, CHECK, UNIQUE, PRIMARY KEY,
FOREIGN KEY, and sometimes exclusion constraints or triggers. Their exact
null and timing semantics are implementation concerns layered on the logical
invariant.
The algebra: operators that stay inside the model #
If and are relations, relational algebra builds expressions from a small operator set. Closure means every well-typed result is itself a relation, so it can feed the next operator.
Selection chooses tuples #
Selection preserves the heading and keeps tuples satisfying predicate . Kyiv suppliers are:
It is a horizontal restriction: fewer rows, same attributes.
Projection chooses attributes #
Projection keeps attributes and removes duplicates created by discarding distinguishing attributes. Supplier cities are , not three rows.
Projection is existential quantification in disguise. From a ternary predicate
Supplies(sid,pid,price), projecting (sid,pid) asks whether some price
makes the original predicate true.
Rename makes schemas composable #
The rename operator changes a relation or attribute name without changing tuples. It is essential for self-joins and for making headings compatible:
Renaming is not cosmetic when names carry operator semantics. A natural join matches equally named attributes; careless names can therefore change a query.
Set operators combine compatible relations #
For union-compatible relations—same attribute names with corresponding domains—set union, intersection, and difference have their ordinary meanings:
Difference is the expressive step that supports “has no,” “all,” and anti-join-style questions. It is also non-monotone: adding input tuples can remove result tuples.
Cartesian product combines every pair #
After resolving duplicate attribute names,
Its cardinality is . A product is rarely the intended final answer, but it is the substrate of joins: first consider pairs, then retain pairs that satisfy a relationship.
Join combines related facts #
A theta join is selection over a product:
An equijoin uses equality conditions. A natural join equates every same-named attribute and emits one copy of each matched attribute. The supplier names attached to offers can be expressed as:
Join does not mean “look up by foreign key.” A foreign key is an integrity constraint; a join is a query operator. Joins can use non-key conditions, and a foreign key can exist without a query joining the tables.
Semijoin and antijoin answer existence questions #
A left semijoin keeps only the left tuples that have at least one match:
An antijoin keeps those with no match. These are useful mental models for SQL
EXISTS and NOT EXISTS, and they prevent accidental row multiplication when
the question asks only whether a related tuple exists.
Division expresses “for every” #
Suppose records pairs and lists required values. Division returns each related to every in :
An expression using primitive set operators is:
Read it causally:
- form every candidate paired with every required ;
- subtract the pairs that actually exist—what remains are missing requirements;
- project the failing values;
- subtract failures from all candidates.
For “suppliers that supply every electronic product,” let
and then compute
Only supplier 3 qualifies in the running data.
A complete query derivation #
Question: Which Kyiv suppliers offer an electronic product for less than 2?
Work from meaning to operators:
- restrict suppliers by city;
- restrict products by category;
- restrict offers by price;
- join the three relations on their identifiers;
- project the requested supplier attributes.
The result is {(1, Ada Parts), (3, Circuit House)}.
One SQL rendering is:
SELECT DISTINCT s.sid, s.name
FROM supplier AS s
JOIN supplies AS x ON x.sid = s.sid
JOIN product AS p ON p.pid = x.pid
WHERE s.city = 'Kyiv'
AND x.price < 2
AND p.category = 'electronic';DISTINCT preserves the set-valued algebraic result when one supplier has
multiple matching offers. Without it, SQL returns one output row per matching
join combination.
Why equivalent expressions matter #
Because operators have algebraic laws, many syntactically different expressions denote the same relation. A query optimizer exploits exactly that freedom.
For predicates and :
If mentions only attributes of :
The second form can discard tuples before the join. If Supplier has one
million rows but only 2,000 Kyiv rows, early selection can shrink both memory
and comparison work without changing meaning.
Projection can similarly be pushed down when every attribute needed by later predicates and joins is retained. The caveat is the lesson: transformations are justified by schemas and laws, not by visual resemblance.
| Rewrite | Usually valid when | Failure boundary |
|---|---|---|
| Split selections | Predicates use ordinary two-valued relational logic | SQL nulls and volatile expressions require SQL-specific rules |
| Push selection below join | Predicate mentions only one input | Moving a filter across an outer join can remove null-extended rows |
| Push projection | Join keys and later attributes remain | Dropping a needed attribute makes the later expression ill-typed |
| Reorder inner joins | Predicates and schemas are preserved | Outer, lateral, or order-sensitive operations add constraints |
SQL is relationally inspired, not identical #
The distinction is not pedantry; it predicts real bugs.
| Relational model | Typical SQL behavior |
|---|---|
| Relation body is a set | Query results use bag/multiset semantics unless DISTINCT or a set operator removes duplicates |
| Tuples have no order | Output order is unspecified unless the outermost query has ORDER BY |
| Predicates are true or false | NULL introduces UNKNOWN; WHERE keeps only rows for which the condition is true |
| Every tuple component is a value from its domain | SQL permits null markers and implementation-specific types |
| Projection removes duplicates | SELECT column preserves duplicates; SELECT DISTINCT column removes them |
| Union is set union | UNION removes duplicates; UNION ALL preserves multiplicity |
| Join combines matching tuples | Outer joins additionally create null-extended rows for unmatched input |
Null changes logical laws #
In SQL, x = NULL is not true or false; it is unknown. Use IS NULL to test
the marker. A CHECK constraint in PostgreSQL is satisfied when its expression
is true or null, so CHECK (price > 0) does not imply price IS NOT NULL.
Declare both when both are invariants.
Unique constraints also need explicit null reasoning. PostgreSQL normally
treats nulls as distinct for uniqueness, while UNIQUE NULLS NOT DISTINCT
makes them collide. Neither behavior should be smuggled into the mathematical
definition of a key; decide the domain semantics first, then choose matching
SQL constraints.
Bags explain duplicate explosions #
If a supplier has three matching offers and the query projects only its name, plain SQL can return that name three times. That is not the engine “duplicating data”; it is the correct multiplicity of the joined bag. Ask which semantics the result needs:
- preserve occurrences because each match is meaningful;
- use
EXISTSbecause only existence matters; - aggregate because the question asks for a count or total;
- use
DISTINCTbecause the intended mathematical result is a set.
Using DISTINCT blindly at the top often hides an incorrect join predicate.
Logical design before physical design #
A reliable modelling pass proceeds in this order:
- Write the fact sentence. What does one tuple assert?
- Name attributes and domains. Include units, time scope, currency, and identity semantics.
- State candidate keys. Explain why each is guaranteed across all legal states.
- State dependencies and constraints. Which values determine others? Which references must exist?
- Normalize where dependencies create anomalies. This is covered in Functional Dependencies & Normalization.
- Write representative relational queries. Ensure the schema can answer required questions without inventing meaning.
- Only then choose indexes and storage. Those optimize the workload; they do not define the facts.
Diagnostic reasoning #
When a query or schema behaves unexpectedly, ask in this order:
- What fact does one input tuple mean?
- Which keys and foreign keys are actually enforced?
- Is the intended result a set, a bag, or an aggregate?
- Can one left tuple match several right tuples? Should it?
- Does a predicate evaluate to unknown because of null?
- Is a filter applied before or after an outer join creates null-extended rows?
- Is ordering explicitly requested?
- Which algebraic expression describes the intended result before SQL syntax?
- Only after semantics are correct: which physical plan did the optimizer choose and why?
This ordering separates two very different failures: a wrong result relation and an inefficient plan for a correct relation.
Exercises #
- Express “products supplied by no Kyiv supplier” with difference or antijoin.
- Explain why projecting
cityfromSupplierchanges cardinality from 3 to 2 in relational algebra but not necessarily in SQL. - Show the difference between “suppliers that provide at least one electronic product” and “suppliers that provide every electronic product.”
- Decide whether
(sid,pid,price)is a candidate key when(sid,pid)is already guaranteed unique.
Solutions
- Let . Products supplied by a Kyiv supplier are . Subtract that from .
- The mathematical projection forms a set, so the two Kyiv occurrences
collapse. SQL
SELECT cityis a bag projection;SELECT DISTINCT citymatches the set result. - “At least one” is a semijoin/existential query. “Every” is division or a
double
NOT EXISTS: there must be no required product for which the supplier lacks an offer. - No. It is a superkey, but
pricecan be removed while uniqueness remains, so it is not minimal.