DBDBMS

Block A · Topics 1–7

Modelling

The schema is the one decision that's still shaping your codebase in five years. Everything here is about making the database structurally incapable of holding contradictory data — and knowing when to give that up on purpose.

01

What a DBMS gives you

vs filesthree-schemadata independence
Why not just use files?

This is a real interview question and the answer is a list of things you'd otherwise have to build yourself — badly. Naming five of them is the answer.

Problem with filesWhat a DBMS provides
The same fact copied into several files, drifting apartControlled redundancy and integrity constraints
Every query is a hand-written scanA declarative query language plus an optimiser that picks the access path
Two writers corrupt each otherConcurrency control — transactions and isolation
A crash mid-write leaves half a recordAtomicity and durability via the write-ahead log
Anyone who can read the file can read everythingPer-table and per-column access control
Changing a record layout breaks every programData independence — the schema is separate from the programs
Finding a row means reading the whole fileIndexes
Backup and restore is your problemPoint-in-time recovery

The three-schema architecture

EXTERNAL (view) level     what each user or app sees
                            e.g. a payroll view exposing no home addresses
        ↕ logical data independence
CONCEPTUAL (logical) level the whole schema: tables, columns, constraints
        ↕ physical data independence
INTERNAL (physical) level  files, pages, indexes, compression

The two independences are the payoff, and are worth naming precisely:
· PHYSICAL data independence — add an index, change the storage engine,
  repartition a table, and no query has to change.
· LOGICAL data independence — split a table in two, and existing views
  can be redefined so applications querying them still work.
Physical independence is nearly complete in practice; logical
independence is only partial, which is why schema migrations are
still real work.
Terms to keep straight

Schema is the structure (the definition); instance is the data in it right now. DBMS is the software; database is the data it manages. RDBMS is a DBMS that implements the relational model. And DDL defines structure, DML manipulates data, DCL grants permissions, TCL controls transactions.

02

The relational model & keys

candidate keyprimary keyforeign keyintegrity
Why keys matter

A key is how you identify a row, which is how you reference it, which is how relationships exist at all. Get keys wrong and you get duplicate rows, orphaned references, and a table you cannot reliably update. Most exam questions in this topic are really testing whether you can distinguish six similar terms.

KeyDefinitionExample
Super keyAny set of attributes that uniquely identifies a row — including ones with extra useless attributes{id}, {id, name}, {id, name, email}
Candidate keyA minimal super key — remove any attribute and it stops being unique{id}, {email}
Primary keyThe candidate key you chose. Implies NOT NULL + UNIQUE, and usually determines physical ordering.{id}
Alternate keyThe candidate keys you didn't choose{email}
Composite keyA key made of more than one attribute{order_id, line_no}
Foreign keyAn attribute referencing another table's primary key. May be NULL (meaning "no relationship").orders.customer_id
Surrogate keyAn artificial identifier with no business meaningAn auto-increment id, a UUID
Natural keyA real-world identifier used as the keyISBN, email, national ID
Surrogate or natural? A good tradeoff answer

Prefer a surrogate primary key, with a unique constraint on the natural key. Reason: natural keys change — people change email addresses, countries change postcodes, a "unique" supplier code turns out to be reused — and a changing primary key means cascading updates through every referencing table. Surrogates are also narrower, which matters because every secondary index stores the primary key. Keep the natural key as a UNIQUE constraint so the database still enforces the real-world rule.

Integrity constraints

  • Entity integrity — the primary key is unique and not null.
  • Referential integrity — a foreign key must either be NULL or match an existing primary key. No orphan rows.
  • Domain integrity — values fall in the column's type and CHECK constraints.
  • Key/unique constraints — enforce alternate keys.
-- Referential actions: what happens to children when a parent goes away
FOREIGN KEY (customer_id) REFERENCES customers(id)
    ON DELETE RESTRICT     -- refuse the delete (the safe default)
    ON DELETE CASCADE      -- delete the children too — powerful and dangerous
    ON DELETE SET NULL     -- orphan them deliberately (column must be nullable)
    ON DELETE SET DEFAULT  -- point them at a placeholder row
    ON DELETE NO ACTION    -- like RESTRICT, but checked at end of statement

-- CASCADE is the one to be careful with: deleting one customer can
-- silently remove millions of rows across many tables, and it happens
-- inside your transaction, holding locks the whole time.
Two NULL facts that get tested

NULL means "unknown", not "empty" — it isn't zero and isn't an empty string. And in a UNIQUE constraint, most databases (including Postgres and MySQL) allow multiple NULLs, because two unknowns aren't provably equal. So UNIQUE(email) does not stop you inserting a thousand rows with a NULL email — a genuinely surprising result the first time it bites.

03

ER modelling

entitiescardinalityweak entitiesmapping to tables
Why it exists

ER diagrams are a thinking tool for the step before SQL: agreeing what things exist and how they relate, in a notation a non-engineer can check. The examinable part is mechanical — mapping the diagram to tables — and the rules are worth knowing exactly.

The vocabulary

ConceptMeansBecomes
EntityA thing with independent existenceA table
AttributeA property of an entityA column
Composite attributeaddress = street + city + zipSeveral columns
Multivalued attributeA person's several phone numbersA separate table — never a comma-separated column
Derived attributeage, from date_of_birthComputed, or a generated column — not stored raw
RelationshipAn association between entitiesA foreign key, or a join table
Weak entityCan't exist or be identified without an ownerA table with a composite PK including the owner's key
ParticipationTotal (mandatory) vs partial (optional)NOT NULL or not, on the FK

Mapping cardinality to tables — the mechanical rules

1 : 1   Put the FK on either side — preferably the side with TOTAL
        participation — and add UNIQUE to it.
        e.g. employee ─ has ─ parking_space
        parking_space(id, employee_id UNIQUE REFERENCES employee)

1 : N   Put the FK on the N side. Always. No join table needed.
        e.g. customer ─ places ─ order   (one customer, many orders)
        orders(id, customer_id REFERENCES customers)

M : N   You must create a junction / associative table whose primary
        key is the pair of foreign keys.
        e.g. student ─ enrols ─ course
        enrolments(student_id, course_id, grade, enrolled_at,
                   PRIMARY KEY (student_id, course_id))
        -- and note the junction table is the natural home for
        -- attributes OF THE RELATIONSHIP, like grade and enrolled_at

Weak entity
        order_lines(order_id, line_no, product_id, qty,
                    PRIMARY KEY (order_id, line_no))
        -- line_no alone means nothing; identity comes from the owner

Ternary relationships map to a table with three FKs — but check whether
        it's genuinely ternary or really two binary relationships.
Modelling mistakes interviewers look for
  • Repeating groups. phone1, phone2, phone3 — that's a multivalued attribute; give it a table. You'll always need a fourth.
  • Comma-separated lists in a column. Unqueryable, unindexable, unenforceable.
  • Storing derived values like age or total without a plan to keep them correct.
  • Missing the M:N. "A student has a course_id" collapses a many-to-many into one-to-many and loses data.
  • No history where the business needs it. If a price can change, storing one price on products means old orders silently re-price themselves — you need the price captured on the order line. This is the mistake most worth catching out loud.
04

Relational algebra

σ π ⋈set operationsdivision
Why bother

Two reasons. It's examined directly. And it's what the query optimiser actually manipulates — when you read that the planner "pushed the predicate down", it means it moved a σ below a ⋈, which is valid precisely because relational algebra has algebraic equivalences. Understanding that makes query plans much less mysterious.

OperationSymbolMeansSQL
Selectionσcond(R)Pick rows matching a conditionWHERE
Projectionπcols(R)Pick columns — and removes duplicatesSELECT DISTINCT
RenameρRename a relation or attributeAS
Cartesian productR × SEvery row paired with every rowCROSS JOIN
Natural joinR ⋈ SProduct, then match on common attributes, then drop duplicates of themNATURAL JOIN
Theta joinR ⋈θ SJoin on an arbitrary conditionJOIN … ON
UnionR ∪ SRows in either (needs union-compatible schemas)UNION
IntersectionR ∩ SRows in bothINTERSECT
DifferenceR − SRows in R but not SEXCEPT
DivisionR ÷ S"Rows in R associated with all of S"No direct SQL — needs a double NOT EXISTS
-- "Names of students enrolled in CS101"
πname( σcourse_id = 'CS101'( students ⋈ enrolments ) )

-- The optimiser's favourite rewrite: PREDICATE PUSHDOWN.
-- These are equivalent, but the second joins far fewer rows:
   σcourse='CS101'( students ⋈ enrolments )        -- join everything, then filter
=  students ⋈ σcourse='CS101'( enrolments )         -- filter first, then join ✓

-- DIVISION is the one that trips people up. "Students who take EVERY
-- course offered" — the SQL idiom is double negation:
SELECT s.name FROM students s
WHERE NOT EXISTS (
  SELECT 1 FROM courses c
  WHERE NOT EXISTS (
    SELECT 1 FROM enrolments e
    WHERE e.student_id = s.id AND e.course_id = c.id))
-- reads as: "no course exists that this student is not enrolled in"
Say this

"Relational algebra is the formal basis for SQL — selection picks rows, projection picks columns, join combines relations. It matters practically because the optimiser rewrites queries using its algebraic equivalences: the classic one is predicate pushdown, applying the filter before the join rather than after, which is provably equivalent but processes far fewer rows. Division is the awkward one — it has no direct SQL operator and is expressed as a double NOT EXISTS."

05

Functional dependencies

FDsclosurecandidate keysArmstrong
Why it exists

Normalisation is defined in terms of functional dependencies, so you cannot do normalisation properly without them. An FD is a rule about the real world that the data must obey — and finding candidate keys from a set of FDs is a reliably examined procedure.

X → Y ("X functionally determines Y") means: any two rows agreeing on X must agree on Y.

student_id → name — one student has one name. ✓
name → student_id — two students can share a name. ✗

Trivial if Y ⊆ X ({a,b} → a is always true).
Partial dependency: a non-key attribute depends on part of a composite key.
Transitive dependency: X → Y and Y → Z, so X → Z indirectly.

Armstrong's axioms

Reflexivity     if Y ⊆ X then X → Y
Augmentation    if X → Y then XZ → YZ
Transitivity    if X → Y and Y → Z then X → Z
-- derived, and the ones you actually use:
Union           if X → Y and X → Z then X → YZ
Decomposition   if X → YZ then X → Y and X → Z
Pseudotransitivity  if X → Y and WY → Z then WX → Z

Attribute closure — and finding candidate keys

R(A, B, C, D, E)
FDs:  A → BC        CD → E        B → D        E → A

Closure of A:  start {A}
  A → BC   → {A,B,C}
  B → D    → {A,B,C,D}
  CD → E   → {A,B,C,D,E}
  A⁺ = {A,B,C,D,E} = all attributes  →  A is a candidate key

Closure of E:  {E}  →  E → A  →  {E,A}  →  then as above
  E⁺ = all  →  E is a candidate key

Closure of B:  {B} → B → D → {B,D}. Nothing else applies.
  B⁺ = {B,D}  ≠ all  →  B is NOT a key

Closure of CD: {C,D} → CD → E → {C,D,E} → E → A → {C,D,E,A}
                → A → BC → {A,B,C,D,E}
  CD⁺ = all  →  CD is a candidate key

Candidate keys: A, E, CD
PRIME attributes (in some candidate key): A, C, D, E
NON-PRIME: B          ← this distinction is what 2NF/3NF are defined on

The exam shortcut: an attribute that appears ONLY on the right-hand side
of every FD can never be part of a candidate key. An attribute that never
appears on any right-hand side must be in EVERY candidate key.
06

Normalisation

1NF2NF3NFBCNFanomalies
Why it exists

Redundancy isn't primarily a storage problem — it's a correctness problem. If a fact is stored in three places, one update can miss one of them and now your database contains a contradiction with no way to tell which copy is right. Normalisation removes the possibility structurally, so the database can't be inconsistent even if the application is buggy.

The three anomalies — always name these first

Update anomaly

A department's name is repeated on 500 employee rows. Renaming it means 500 updates; miss one and the database says two things at once.

Insertion anomaly

You can't record a new department until it has an employee, because department data only exists on employee rows.

Deletion anomaly

Deleting the last employee in a department destroys the department's information entirely.

The forms, worked on one example

Start — unnormalised
student_courses(student_id, student_name, course_id, course_name,
                instructor, instructor_office, grades)
   grades = "A,B,A"        ← multiple values in one cell
   PK = (student_id, course_id)

══ 1NF ══ atomic values, no repeating groups ══════════════════════
Split the multivalued attribute out.
   student_courses(student_id, student_name, course_id, course_name,
                   instructor, instructor_office, grade)
   -- one grade per row now. 1NF achieved.

══ 2NF ══ 1NF + no PARTIAL dependencies ══════════════════════════
   (only relevant when the PK is composite)
FDs present:
   student_id            → student_name          ← depends on PART of PK ✗
   course_id             → course_name, instructor ← PART of PK ✗
   (student_id, course_id) → grade               ← full PK ✓

Decompose:
   students(student_id, student_name)
   courses(course_id, course_name, instructor, instructor_office)
   enrolments(student_id, course_id, grade)

══ 3NF ══ 2NF + no TRANSITIVE dependencies ═══════════════════════
   (no non-prime attribute depends on another non-prime attribute)
In courses:  course_id → instructor → instructor_office
             instructor_office depends on course_id only TRANSITIVELY ✗
             -- symptom: an instructor's office is repeated on every
             -- course they teach, so moving office = many updates
Decompose:
   courses(course_id, course_name, instructor)
   instructors(instructor, instructor_office)

══ BCNF ══ every determinant is a superkey ═══════════════════════
The classic case 3NF misses. Suppose:
   enrolments(student_id, course_id, instructor)
   · each course is taught by many instructors
   · a student takes a course from exactly one instructor
   · each instructor teaches only ONE course
FDs:  (student_id, course_id) → instructor      ✓ superkey
      instructor → course_id                   ✗ NOT a superkey!

This IS in 3NF — course_id is prime, and 3NF permits a non-superkey
determinant when the dependent attribute is prime. But it still has
redundancy. BCNF has no such exception:
   teaches(instructor, course_id)
   enrolments(student_id, instructor)
FormRequiresIn one sentence
1NFAtomic values, no repeating groupsOne value per cell.
2NF1NF + no partial dependency on a composite keyNon-key attributes depend on the whole key.
3NF2NF + no transitive dependency…and on nothing but the key.
BCNFEvery determinant is a superkeyThe strict version of 3NF, with no prime-attribute exception.
4NFBCNF + no multivalued dependencyTwo independent multivalued facts don't belong in one table (or you get a cartesian product of them).
5NF4NF + no join dependencyRarely reached deliberately.
The mnemonic, and the BCNF tradeoff

3NF: "every non-key attribute depends on the key, the whole key, and nothing but the key." 2NF fixes "the whole key", 3NF fixes "nothing but the key".

The subtle point worth knowing: BCNF decomposition is always lossless but may not be dependency-preserving — you can end up unable to enforce an FD with a single-table constraint. 3NF decomposition can always be both lossless and dependency-preserving. So there's a genuine tradeoff, and "3NF is usually enough in practice" is a defensible position, not laziness.

Lossless decomposition

A decomposition of R into R1 and R2 is lossless if joining them back gives exactly R — no spurious extra rows. The condition: R1 ∩ R2 must be a superkey of R1 or of R2. If you split on something that isn't, the rejoin manufactures rows that were never in the original, which is strictly worse than the redundancy you were trying to remove. Always check this when you decompose in an exam.

07

Denormalisation

deliberate redundancyread optimisationkeeping copies honest
Why break the rules

Normalisation optimises for write correctness and pays in joins. At some read volumes — or across shards where joins are impossible — that bill is too high. The skill isn't knowing that denormalisation exists; it's doing it deliberately, in one direction, with a plan to keep the copies correct.

TechniqueWhat it doesCost you accept
Duplicate a columnCopy customer_name onto orders to avoid a joinA rename must update both — or you decide the order should keep the name as it was, which is often correct
Precomputed aggregateStore order_count on customersEvery order insert must update it, and it can drift; needs a periodic reconciliation job
Materialised viewStore a query's result and refresh itStaleness, plus refresh cost — but the database maintains it, not your code
Merge 1:1 tablesFold user_profiles into usersWider rows, so fewer fit per page and scans read more
Repeat a groupStore the three most recent items inlineReintroduces the anomalies 1NF removed — use sparingly and only for a bounded, display-only cache
Column-store copyStream to a warehouse for analyticsTwo systems, replication lag — but it keeps analytics off the transactional database, which is usually the right call
The rule that makes it safe

Normalise the source of truth; denormalise into derived structures you can rebuild. If the copy is a materialised view, a cache, or a read model fed by an event stream, then drift is recoverable — you re-derive it from the truth. If instead you denormalise the primary tables and the copies diverge, you have two candidate truths and no way to decide between them. That distinction is the whole answer.

And sometimes the "duplicate" isn't a duplicate

Copying the product price onto the order line looks like denormalisation but isn't: the order line records the price at the time of sale, which is a genuinely different fact from the product's current price. Same for the delivery address on an order. Recognising these as point-in-time facts rather than redundancy is a sign of real modelling experience — and getting it wrong means old invoices silently change when someone edits a product.

Say this

"I'd normalise to 3NF by default, because it makes inconsistency structurally impossible and the schema enforces the rules rather than the application. Then I'd denormalise where measurement shows a join is too expensive — but into derived structures, like a materialised view or a read model, so drift is recoverable by re-deriving from the source of truth. I'd avoid denormalising the primary tables themselves, because then two copies disagree and neither is authoritative. And I'd distinguish real denormalisation from point-in-time facts: the price on an order line isn't a duplicate of the product price, it's the price at the time of sale."