Worked examples — Relational model — tables, rows, columns, NULL
This page is a drill through every case the relational model can throw at you. We do not learn a new idea here — we take the ideas from the parent note (tables, rows, columns, and the tricky NULL) and grind them against every situation, one cell of a matrix at a time.
The scenario matrix
Every question about a relational table falls into one of these case classes. Read the whole grid once; each worked example below is tagged with the cell it covers.
| # | Case class | The situation | Example that covers it |
|---|---|---|---|
| C1 | Counting shape | degree vs cardinality, incl. an empty table | Ex 1 |
| C2 | Comparison with a known value | col = v, col <> v where col is present |
Ex 2 |
| C3 | Comparison touching NULL | NULL = v, NULL <> v → UNKNOWN |
Ex 3 |
| C4 | Boolean combine: AND | TRUE/FALSE/UNKNOWN AND UNKNOWN |
Ex 4 |
| C5 | Boolean combine: OR | TRUE/FALSE/UNKNOWN OR UNKNOWN |
Ex 4 |
| C6 | Arithmetic with NULL | NULL + 5, NULL * 0 — degenerate zero case |
Ex 5 |
| C7 | Aggregates over NULL | COUNT, COUNT(col), SUM, AVG skipping NULLs |
Ex 6 |
| C8 | Set identity: duplicates & order | is a table a set or a bag? no ORDER BY |
Ex 7 |
| C9 | Word problem (real world) | a survey with missing answers | Ex 8 |
| C10 | Exam twist | the = OR <> "returns everyone?" trap + the fix |
Ex 9 |
We will reuse this small table throughout (call it Student):
| id | name | age | major |
|---|---|---|---|
| 1 | Asha | 20 | CS |
| 2 | Ravi | NULL | Physics |
| 3 | Mira | 22 | NULL |
Here "NULL" means, as the parent note said, value absent / unknown — not zero, not empty text.
The figure below draws this exact grid. Look at it now: the cyan header boxes are the columns (kinds of fact), each white row is a record, and the two amber boxes mark the NULL cells — the ones we will keep colliding with throughout this page. Notice degree (4) counts across the top, cardinality (3) counts down the side.

Ex 1 — Counting shape, including the empty table (C1)
Steps
- Degree = number of columns.
Studenthas columnsid, name, age, major→ degree . Why this step? Degree describes the shape (the schema), which is fixed regardless of how many rows exist. - Cardinality = number of rows.
Studentshows 3 records → cardinality . Why this step? Cardinality describes the content — how many facts are stored right now. - Empty table
Log: it has 3 declared columns, so degree , even with zero rows. Cardinality . Why this step? This is the degenerate cell: shape and content are independent. A table can have shape but no content.
Verify: Student degreecardinality ; Log . Counts are non-negative integers ✓.
Ex 2 — Comparison with a known value (C2)
Steps
age = 20: Asha has → TRUE. Mira has → is FALSE. Why this step? Both ages are present and known, so ordinary two-valued logic applies — every comparison is cleanly TRUE or FALSE.- Result of
age = 20:{Asha}— WHERE keeps only the TRUE row. Why this step? A WHERE clause returns exactly the rows whose test is TRUE, so only Asha survives while Mira's FALSE row is dropped. age <> 20(the<>means "not equal"): Asha FALSE; Mira TRUE. Why this step? We flip the test to "not equal" to check the complementary set; since both ages are known, each again resolves cleanly to TRUE or FALSE.- Result of
age <> 20:{Mira}— the one TRUE row. Why this step? Again WHERE keeps only TRUE rows, so Mira survives and Asha's FALSE row is dropped — the exact opposite of step 2, as expected for a not-equal test.
Verify: Asha and Mira are split into disjoint sets {Asha} and {Mira}; together they cover both known-age rows, exactly what "equal or not-equal" should do when no NULL is involved ✓.
Ex 3 — Comparison touching NULL (C3)
Steps
age = 20for Ravi: age is unknown, so "is the unknown equal to 20?" → we cannot honestly say. Result UNKNOWN. Why this step? Any comparison against an unknown value yields UNKNOWN, because you cannot test something you do not know.age <> 20for Ravi: same reasoning → UNKNOWN.age = NULL: now both sides are unknown. . This is not TRUE. Why this step? Two unknowns cannot be proven equal — you don't know either of them.
The figure shows Ravi's age as an amber ? box, with all three tests arrowing out to the same amber verdict UNKNOWN. Look at the arrows: every one lands on UNKNOWN — visual proof that no ordinary comparison can escape the unknown box.

Verify: all three land on UNKNOWN, matching the parent formula ✓.
Ex 4 — Boolean combine: AND & OR with UNKNOWN (C4, C5)
We write U for UNKNOWN. Think of U as "TRUE or FALSE, we can't tell."
Steps
T AND U: AND is TRUE only if both are TRUE. One side is TRUE, the other could be TRUE or FALSE → the whole could still go either way → U. Why this step? The known TRUE does not "dominate" AND — it still needs the other side to also be TRUE, and that side is unknown, so the result stays unknown.F AND U: AND is FALSE if either side is FALSE. One side is already FALSE → result is FALSE regardless of the unknown → FALSE. Why this step? The known FALSE "dominates" AND, so the unknown never matters.T OR U: OR is TRUE if either side is TRUE. One side is already TRUE → result TRUE regardless → TRUE. Why this step? The known TRUE "dominates" OR.F OR U: OR is TRUE only via a TRUE somewhere; here one side is FALSE and the other unknown → could go either way → U. Why this step? The known FALSE does not "dominate" OR — OR still needs some TRUE to fire, and the only candidate is unknown, so the result stays unknown.
The figure shows two 3×3 grids, AND on the left and OR on the right, with each cell coloured by outcome (cyan T, white F, amber U). Look at the amber cells: they cluster exactly where no dominating value is present — the geometry of the mnemonic below.

Verify: F AND U = FALSE, T OR U = TRUE, T AND U = U, F OR U = U — the two collapse cases match the parent's truth tables ✓.
Ex 5 — Arithmetic with NULL, and the zero degenerate (C6)
Steps
NULL + 5: unknown plus 5 is still unknown (you don't know the starting number) → NULL. Why this step? Adding a known amount to an unknown quantity leaves the total unknown — shifting an unknown by 5 does not reveal what it was, so the result must stay NULL.NULL - NULL: two separate unknowns subtracted — you don't know either, so the difference is unknown → NULL. (It is not 0!) Why this step? NULLs are not "the same value"; they are independent absences.NULL * 0: in ordinary maths anything times 0 is 0. But SQL treats any arithmetic touching NULL as NULL → NULL. Why this step? This is the degenerate zero case. SQL propagates NULL structurally; it does not simplifyunknown * 0to 0.
Verify (concept): all three results are NULL, consistent with from the parent ✓.
Ex 6 — Aggregates skip NULL (C7)
Steps
COUNT(*): counts rows, regardless of NULLs → . Why this step? The*form is about existence of rows, so all three rows count.COUNT(age): counts non-NULL values inage. Ages present: Asha , Mira ; Ravi is NULL and is skipped → . Why this step?COUNT(col)ignores NULLs — this is the whole point of the C7 cell.SUM(age): adds present ages only: . Why this step? SUM, like every aggregate exceptCOUNT(*), silently drops NULL inputs before adding — you cannot add an unknown, so Ravi's row contributes nothing to the total.AVG(age): SUM over the count of non-NULL values , not . Why this step? AVG divides by how many values it actually summed (2), so a missing age does not drag the average toward 0.
Verify: , non-NULL count , . Compare with the wrong method — the difference proves NULL was correctly excluded ✓.
Ex 7 — Set vs bag: duplicates and order (C8)
Steps
- Pure relation is a set: duplicates collapse.
{CS, CS, Physics}as a set → 2 distinct rows. Why this step? A set has no repeated elements — that is the mathematical definition the parent gave. - Real SQL is a multiset (bag): an actual SQL table may keep both
CSrows unless aPRIMARY KEY/UNIQUEforbids it. So theory says 2, practice may store 3. Why this step? The steel-man from the parent — real tables relax the "no duplicates" rule. - Order: without
ORDER BY, row order is not guaranteed. You must not assumeCS, Physics, NULL. Why this step? A set has no intrinsic order, so the engine may return rows in any order.
Verify: distinct count of {CS, CS, Physics} ✓.
Ex 8 — Word problem: a survey with missing answers (C9)
Steps
- Separate absent from zero. Two NULLs = "customer left the question blank" (unknown). The
0= a known answer: "I tipped nothing." These are different! Why this step? This is the parent's core distinction: NULL 0. The0is real data; the NULLs are missing data. - Average tip =
AVG(tip): sum of known values ; count of non-NULL values → . Why this step? AVG skips the 2 NULLs but includes the known0, because 0 is a value. - "Tipped above 0": count rows where
tip > 0. Known values : those are and → people. The two NULL rows giveNULL > 0→ UNKNOWN → not counted. Why this step? A WHERE/filter keeps a row only when the test is TRUE; UNKNOWN rows are dropped (parent's rule).
Verify: , non-NULL count , ; people with tip>0 ✓.
Ex 9 — Exam twist: the "returns everyone?" trap and its fix (C10)
Steps
- Asha (age 20):
20 = 20TRUEOR20 <> 20FALSE →TRUE OR FALSE= TRUE → kept. Why this step? Asha's age is known, so both branches resolve to definite truth values and the OR fires TRUE — she must survive the filter, establishing the "expected" behaviour before the trap appears. - Mira (age 22):
22 = 20FALSEOR22 <> 20TRUE →FALSE OR TRUE= TRUE → kept. Why this step? Again a known age gives two definite branches; the OR fires TRUE via the not-equal branch, confirming every non-NULL row does behave like the tautology suggests. - Ravi (age NULL):
NULL = 20→ UORNULL <> 20→ U →U OR U= U. WHERE keeps only TRUE rows → Ravi is silently dropped. Why this step? The tautology breaks precisely because the "either/or" never becomes TRUE when both branches are UNKNOWN. - Result:
{Asha, Mira}— not everyone. Ravi vanished. Why this step? Collecting the per-row verdicts (TRUE, TRUE, UNKNOWN) and applying the "only TRUE survives" rule shows the query returns 2 of 3 rows — making the silent loss of the NULL row concrete and countable. - Fix — handle NULL explicitly with
IS NULL, which returns real TRUE/FALSE:SELECT name FROM Student WHERE age = 20 OR age <> 20 OR age IS NULL;Now Ravi's branchage IS NULL→ TRUE → kept. Result{Asha, Ravi, Mira}. Why this step?IS NULLis the only correct way to test for absence, escaping three-valued logic back into two-valued TRUE/FALSE.
The figure lays out all three rows with an arrow to each verdict: Asha and Mira arrow to cyan TRUE / kept, while Ravi arrows to amber UNKNOWN / DROPPED. Look at the amber row: it is the visual of a row silently falling out of the result, and the caption line shows the IS NULL fix that rescues it.

Verify: buggy query returns 2 names {Asha, Mira}; fixed query returns 3 names {Asha, Ravi, Mira}. The difference (1 row) is exactly the NULL row ✓.
Active recall
Recall Which cell was which?
- Empty table degree? ::: Equal to its declared column count (rows don't matter) — here 3.
NULL - NULL? ::: NULL (two independent unknowns), never 0.COUNT(age)on 3 rows with 1 NULL? ::: 2.AVGof{20, NULL, 22}? ::: 21 = 42/2, dividing by non-NULL count.F AND U? ::: FALSE.T OR U? ::: TRUE.- Does
age = 20 OR age <> 20return NULL-age rows? ::: No — UNKNOWN OR UNKNOWN drops them. - Correct fix to include them? ::: Add
OR age IS NULL.
Connections
- SQL SELECT and WHERE — where three-valued logic actually filters rows
- COALESCE and NULL handling functions — substitute defaults for NULL before aggregating
- Primary Keys and Uniqueness — forbids the duplicate rows of Ex 7
- Data Types and Domains — why
0,'', and NULL live in different worlds - Foreign Keys and Referential Integrity · Normalization
Degree of an empty 3-column table?
NULL - NULL result?
NULL * 0 result?
COUNT(age) when one of three ages is NULL?
AVG of {20, NULL, 22}?
FALSE AND UNKNOWN?
TRUE OR UNKNOWN?
Why does age = 20 OR age <> 20 drop NULL-age rows?
Fix to keep NULL-age rows in that query?
OR age IS NULL.