LLGA · Course notes

DataBase Management System

CSC_51053_EP · Year 1 · Period 1

Contents

Introduction and overview

Module DataBase Management Systems (official catalog title: “Système de Gestion de Base de Données”)
Code CSC_51053_EP
Team and workload Coordinator: Madhulika Mohanty (Inria Saclay, CEDAR team · École Polytechnique) · 36 h in person · 4.5 ECTS · from the relational model and SQL to transactions, B+ trees, and query optimization — up to knowledge graphs, vector databases, and data fact-checking, the research themes of the CEDAR team.
Period Fall 2026 · period 1 (from 09/17/2026 to 12/18/2026) · course taught in English
Assessment 50% final written exam (paper documents allowed) · 50% three 30-minute tests in small classes starting mid-October
Status in the program Notes kept for reference Not in the LLGA 2026–2027 list: the MScT-LLGA-GD1A track page (read on 22 October 2026) does not include this course unit in LLGA 1 – S1; it is still taught at the École in other tracks, and these notes are kept for reference.

Audience: an engineer who already connects an LLM or a graph to data and wants that connection to be reliable — correct queries, fast access, no lost updates. Empirical first: each topic starts from a failure you can observe — a query that works in the lab and times out in production for lack of an index, a RAG system that hallucinates because a join was wrong — and only then names the theory behind it: relational model, ACID transactions, optimizer, vector stores.

This module is the “data” side of the program: how to store, query and secure data before learning from it.

What you will be able to do

Official objectives and syllabus (2026-2027 catalog)

In the official catalog, the course unit Système de Gestion de Base de Données (X3A VE3) sets itself the objective of a “broad and in-depth presentation of the fundamental concepts in very large-scale data management”, drawing on PostgreSQL to introduce declarative queries, query optimization, data modeling, concurrency control and crash recovery, with labs where students load and query a real database, including through a web application.

This is exactly the arc followed by these materials, from the relational model to optimization — and our closing chapters (NoSQL, web data) extend this foundation toward the vector and graph stores that RAG and GraphRAG systems query. The sheet lists no prerequisites (not disclosed); the assessment combines a final written exam and three graded tests in small classes.

Official program theme Course section
Data modeling: entity-association model, relational model Chapter 1 (Def. 1.1 for the relational model, Def. 1.8 for the entity-association → relational mapping), extended in chapter 3
Relational algebra, relational calculus Chapter 1 (Def. 1.3 and Th. 1.7 — algebra/calculus equivalence)
The query language of relational databases: SQL Chapter 2
Quality of relational schemas, normal forms Chapter 3
Subsystem of relational DBMS: disks, files, buffers Chapter 4 (Def. 4.1 for pages and files, Def. 4.5 for the buffer pool)
Indexing: tree structures, array structures Chapter 5 (B+ trees and hashing)
Evaluation of relational operators Chapter 8 (Def. 8.2 for joins, Def. 8.5 for selection access paths)
Optimization of SQL queries Chapter 8
Transaction management Chapter 6
Concurrency control Chapter 6

Every official theme of the syllabus is covered; crash recovery, mentioned in the official description, is the subject of chapter 7. Our additions, beyond the official syllabus: the review chapter 0 (sets, relations, complexity), chapter 9 (NoSQL, graph and vector databases), chapter 10 (web data and heterogeneous integration) and chapter 11 (the work of the CEDAR team). Note: this course unit does not appear in the LLGA-1-S1 track list of the 2026-2027 catalog, although its sheet remains published there and scheduled in period 1.

Where this course fits in the program

Notes kept for reference: this course unit is not in the LLGA 2026–2027 lists (it is still scheduled in Period 1 for other tracks of the École, 36 h), so no LLGA module requires it. Official prerequisites: not stated on the sheet; these materials lay down the Refresher in Computer Science module as their foundation, whose sets, relations and complexity analysis they constantly mobilize — external sorting and indexes are reasoned about in algorithmic cost.

For an LLGA student, the course is an optional complement to three modules; none of them assumes it.

The research of the CEDAR team (Inria) on knowledge graphs and fact-checking also announces concrete internship topics.

Course outline

Session plan

36 h in class = 12 sessions of 3 h. The volume is the one of the SynapseS 2026–2027 sheet (36 h in person); the sheet publishes no session-by-session programme, so the split below is our proposal for covering these notes in that time. Level 1 = foundations (reviews, definitions, first worked examples), 2 = core: the internals of a relational DBMS, 3 = advanced material beyond the relational core (distributed and vector databases, heterogeneous data); the level never goes down from one session to the next.

Session Hours Level Content Practice
1 3 h 1 ch. 0 Review: sets, relations, complexity; ch. 1 Relational model and algebra, equivalence laws (Def. 1.1–Example 1.5) Ex. 1 (a)
2 3 h 1 ch. 1 (end) Division, Codd’s theorem, entity-association model Ex. 1 (b)–(c)
3 3 h 1 ch. 2 SQL: the basic block, quantifiers, aggregates, window functions (Def. 2.1–Example 2.4) lab: the running example in SQLite
4 3 h 2 ch. 2 (end) NULL and three-valued logic, recursive queries, SQL from Python Ex. 8
5 3 h 2 ch. 3 Design: functional dependencies, Armstrong’s axioms, normal forms, lossless decomposition Ex. 2
6 3 h 2 ch. 4 Storage: pages, files, external merge sort, buffer pool Ex. 5
7 3 h 2 ch. 5 Indexes: B+ trees and hashing Ex. 3
8 3 h 2 ch. 6 Transactions: serializability, two-phase locking, isolation levels Ex. 4
9 3 h 2 ch. 7 The log and crash recovery: WAL, ARIES lab: replay Example 7.3 by hand, then crash a SQLite write
10 3 h 2 ch. 8 Query optimization: cost model, join algorithms, rewrites, access paths Ex. 7
11 3 h 3 ch. 9 Beyond relational: CAP, NoSQL families, graph databases, vector search Ex. 6
12 3 h 3 ch. 10 Web data and heterogeneous integration Ex. 9

Self-study, outside the 36 h (about 1.5 h in total): ch. 11 Course research notes — CEDAR (Mohanty): RDF summaries, ConnectionLens, computational fact-checking, read as preparation for Ex. 9 and the project topics (≈ 1.5 h).

How to study this module

Independent note Crossing LLMs and graphs — the identity of the LLGA master’s program — requires knowing where and how data lives: relational databases, graph databases, vector databases. This module provides this command of the data foundation, shared by the engineers who deploy RAG systems and the researchers who make knowledge graphs speak.

0. Review: sets, relations, complexity

A database is, mathematically, a collection of finite relations, and its engine is judged by the cost of its algorithms on data larger than memory. This chapter recalls the set theory, logic and complexity that the rest of the module uses, and proves the first size bounds that an optimizer relies on.

L3 review material used

  • Sets and Cartesian products: A_1 \times \cdots \times A_k, cardinality of the product |A_1 \times A_2| = |A_1|\cdot|A_2|, subsets, differences; a relation in the database sense is a subset of a Cartesian product;
  • Binary relations: reflexivity, transitivity, transitive closure — the hidden tool behind the theory of functional dependencies (ch. 3) and recursive queries (ch. 2);
  • First-order logic: quantifiers \forall, \exists, negation of quantifiers (\neg\forall x\, P \equiv \exists x\, \neg P) — SQL is nothing but an evaluable fragment of this logic;
  • Complexity: notations O, \Theta, \Omega, logarithms (\log_b x = \ln x / \ln b), average complexity of hash tables (L3 algorithms review), sorting and merging;
  • A bit of probability: uniform draws, expectation, the “n balls into m bins” paradigm behind the expected bucket load of hashing (ch. 5) and the selectivity estimates of ch. 8.

Proposition 0.1 — Sizes of products and projections Let R \subseteq A \times B be a finite binary relation. Then |\pi_A(R)| \leq |R| \leq |\pi_A(R)| \cdot |\pi_B(R)|, where \pi_A denotes the projection (the set of values of A actually present).

The left bound is an equality exactly when each value of A appears in a single pair (A determines B). The right one is attained for the complete product R = \pi_A(R) \times \pi_B(R).

These bounds, trivial in appearance, are the foundation of all the cardinality estimates of chapter 8: an optimizer never knows the exact content of the tables, only statistics (number of distinct values, histograms), and must bracket the sizes of intermediate results in order to choose an execution plan.

Note — Why this module in an LLGA master’s program? Every model lives on data: extracting it, cleaning it, integrating it, indexing it, then serving models in production (vector search). This module covers the layer beneath ML: where the Machine Learning module (ch. 1) assumes clean tables, we explain where they come from. Links given along the way: Graph ML (ch. 9), LLMs (ch. 9-10), NLP (ch. 10).

1. The relational model and algebra

Concrete scene: a business dashboard shows wrong totals because a join duplicated rows. To understand why, one needs a precise model of what a table is and of what each operation does to it; this chapter gives that model — relations and relational algebra — on which SQL, design and optimization are all built.

What breaks Three classic mistakes, each treated later in the module: ignoring keys and functional dependencies (duplicated rows, ch. 3), writing SELECT * in production code (fragile queries, wasted I/O, ch. 4 and 8), and taking “the data looks consistent” for isolation (ch. 6).

The relational model, proposed by E. F. Codd in 1970, rests on a radically simple idea: representing data as (mathematical) relations, that is, sets of tuples, and querying them with operations that are closed on these sets. The physical structure is invisible: this separation between the declarative and the physical is what made the model successful, and it is what will make the automatic optimization of chapter 8 possible.

Definition 1.1 — Schema, relation, instance A relation schema is a name and a list of attributes R(A_1\!: D_1, \dots, A_n\!: D_n), where D_i is a domain of values.

An instance of R is a finite set r \subseteq D_1 \times \cdots \times D_n (no duplicates: a relation is a set). A database schema is a set of relation schemas.

A key constraint K \subseteq \{A_1,\dots,A_n\} requires that two distinct tuples do not coincide on K.

Example 1.2 — Running example database Three relations suffice for the whole chapter:

Student (id, name, major) Course (code, title, credits)
(1043, Anna, DS) · (1088, Bruno, AI) · (2101, Chloé, DS) (DB, Databases, 4) · (DL, Deep Learning, 5) · (OT, Optimal Transport, 3)

and the association relation Enrolled(id, code, year) with the tuples (1043, DB, 2025), (1043, DL, 2026), (1088, DB, 2025), (1088, OT, 2025), (2101, DL, 2025). The pair (id, code) is the key of Enrolled.

Definition 1.3 — Relational algebra Relational algebra is the smallest family of operations, all closed (an operation on relations returns a relation):

  • selection \sigma_p(R): tuples of R satisfying the predicate p (comparisons and logical connectives);
  • projection \pi_L(R): restriction to the attributes of L, with duplicate elimination;
  • Cartesian product R \times S, union R \cup S (compatible schemas), difference R - S, renaming \rho_{A \to B}(R);
  • join R \Join_p S := \sigma_p(R \times S) and natural join R \Join S (equality of identically named attributes, then projection of duplicated columns);
  • aggregation {}_G\gamma_{f(A)}(R): partition of R according to the attributes G, computation of the group function f (count, sum, avg, min, max) — an extension of Codd’s initial algebra, indispensable in practice.

Proposition 1.4 — Fundamental equivalence laws The following equivalences hold for all instances (verified by expanding the set-theoretic definitions):

  • cascade and commutativity of selections: \sigma_{p_1 \wedge p_2}(R) = \sigma_{p_1}(\sigma_{p_2}(R)), \qquad \sigma_{p_1}(\sigma_{p_2}(R)) = \sigma_{p_2}(\sigma_{p_1}(R));
  • if p only involves attributes of R: \sigma_p(R \Join S) = \sigma_p(R) \Join S This is the selection push-down law, the heart of the heuristics of ch. 8;
  • \pi_{L_1}(\pi_{L_2}(R)) = \pi_{L_1}(R) whenever L_1 \subseteq L_2;
  • R \Join_p S = S \Join_{p'} R with the symmetrized predicate p' (commutativity) and (R \Join S) \Join T = R \Join (S \Join T) (associativity) — hence the freedom of choice of the join order, exponential but fruitful.

Proof. Proof of the push-down law (the others are in the same style).

  • First inclusion. Let t be a tuple of R \Join S that satisfies p. By definition t = (u, v) with u \in R, v \in S, and the join predicate satisfied.

    Since p mentions only attributes of R, p(t) depends only on u, hence u \in \sigma_p(R) \quad\text{and}\quad t \in \sigma_p(R) \Join S .

  • Converse inclusion. Conversely, a tuple (u, v) of \sigma_p(R) \Join S belongs to R \Join S (since \sigma_p(R) \subseteq R) and satisfies p because u does. ∎

Example 1.5 — Step-by-step evaluation “Majors of the students enrolled in 2025 in a course of at least 4 credits”: \pi_{\texttt{major}}\Big(\sigma_{\texttt{year}=2025}\big(\texttt{Enrolled}\big) \Join \pi_{\texttt{code}}\big(\sigma_{\texttt{credits}\geq 4}(\texttt{Course})\big) \Join \texttt{Student}\Big). On Example 1.2:

  1. the courses kept are {DB, DL};
  2. \sigma_{2025} keeps (1043, DB), (1088, DB), (1088, OT), (2101, DL);
  3. the join eliminates (1088, OT);
  4. after the join with Student and projection: {DS, AI, DS} → {DS, AI} by eliminating duplicates.

The result is {DS, AI}.

The order of the operations above (the most restrictive first) is not the only possible one: starting with \texttt{Enrolled} \Join \texttt{Student} would be correct but more costly — the whole subject of ch. 8.

Definition 1.6 — Relational division The division R \div S (with the schema of S included in that of R) returns the tuples u over the remaining attributes such that \{u\} \times S \subseteq R . It expresses the universal quantifier: “the x associated with all the y of S”.

It is programmed without a dedicated operator by the scheme \pi_X(R) - \pi_X\big((\pi_X(R) \times S) - R\big), where X are the attributes of R absent from S.

Theorem 1.7 — Codd: algebra and relational calculus Relational algebra (with renaming) is equivalent to the safe tuple relational calculus: every query expressible in one is expressible in the other.

This result (1972) defines the yardstick of relational completeness: a query language is relationally complete if it expresses every query of the algebra — which SQL does. Completeness is not universality: neither the basic algebra nor non-recursive SQL can express the transitive closure of a graph (queries of the “ancestors” type), except by using the recursion of ch. 2 or specific extensions.

Proof. Sketch.

  • Calculus → algebra. One translates a safe formula \{x \mid \varphi(x)\} by structural induction: the positive use of \exists becomes a projection of a product (with renaming for the free variables), conjunction a join, negation a difference. Safeness (domains remain finite) guarantees that these constructions terminate.
  • Algebra → calculus. An immediate induction over the operators.

The inexpressiveness of transitive closure is proved by bounding arguments (any non-recursive query “sees” a number of joins fixed in advance, hence a bounded diameter in the graph). ∎

Definition 1.8 — Entity-association (E-A) model and the move to relational Design starts at the conceptual level, before any relation schema: an entity-association model describes entities (students, courses), their attributes, and associations between them, each association carrying a cardinality (1-1, 1-N, N-M) — for example Student—Enrolled(year)—Course is an N-M association carrying the attribute year.

The translation to the relational model is mechanical:

  • each entity becomes a relation (its identifier becomes the key);
  • a 1-N association is absorbed by a foreign key on the N side;
  • an N-M association becomes a relation in its own right whose key is the union of the keys of the linked entities (exactly the Enrolled relation of Example 1.2).

This E-A → relational passage is the first step of the normalized design of ch. 3.

Note — Set semantics vs. multiset semantics The algebra described here is set-based (no duplicates). Real engines manipulate multisets: without DISTINCT, duplicates survive, and the cardinalities of ch. 8 account for them. Duplicate elimination costs a sort or a hash — never free; that is why SQL does not do it by default.

2. SQL: queries, aggregates, windowing

SQL is a declarative language: one describes the result, not the algorithm. Its logical semantics is understood in three stages, which one must know in order to predict what a query returns — and which every optimizer rewrites in its own way (ch. 8):

  1. FROM produces the Cartesian product/joins;
  2. WHERE selects row by row;
  3. then GROUP BY + SELECT group, aggregate, and project.

Definition 2.1 — Semantics of the basic block The query SELECT [DISTINCT] L FROM R1, …, Rk WHERE p returns (multiset if DISTINCT is absent, set otherwise)

Key result — semantics of SELECT … FROM … WHERE \pi_L\big(\sigma_p(R_1 \times \cdots \times R_k)\big)

A subquery is a nested query; IN and EXISTS correspond to semi-joins: WHERE EXISTS (SELECT …) tests that the answer is non-empty, which makes it possible in particular to express differences with NOT EXISTS.

Proposition 2.2 — Translating quantifiers “The x such that \forall y \in S,\; \theta(x,y)” is translated in SQL by anti-join: … WHERE NOT EXISTS (SELECT 1 FROM S WHERE NOT θ). “There exists” is a simple EXISTS. This double negation is the direct legacy of the fact that SQL has no universal quantifier — and of Theorem 1.7: the division of ch. 1 does not exist as an operator, it is programmed.

Definition 2.3 — Aggregates and the HAVING clause A query with GROUP BY g partitions the selected rows according to the values of g, and each group produces one row: the SELECT may mention only g and aggregates. The HAVING clause filters the groups (after aggregation), whereas WHERE filters the rows (before): \pi_{g,f}\Big(\sigma_q\big({}_g\gamma_f( \sigma_p(R))\big)\Big) \;=\; \texttt{SELECT g, f(a) … WHERE p GROUP BY g HAVING q(f)}.

Example 2.4 — Windowing (window functions) Window functions compute an aggregate without grouping: each row keeps its identity and additionally receives the value computed over its “window” (all the rows sharing its partition, possibly ordered). On:

student subject grade
Anna ALG 16
Anna PROG 14
Anna STATS 12
Bruno PROG 18
Chloé ALG 15
Chloé PROG 17

the query SELECT student, grade, ROW_NUMBER() OVER (PARTITION BY student ORDER BY grade DESC) AS personal_rank, AVG(grade) OVER (PARTITION BY student) AS average, RANK() OVER (ORDER BY grade DESC) AS global_rank FROM Grades returns:

student grade personal_rank average global_rank
Anna 16 1 14.0 3
Anna 14 2 14.0 5
Anna 12 3 14.0 6
Bruno 18 1 18.0 1
Chloé 17 1 16.0 2
Chloé 15 2 16.0 4

Note the behavior of RANK() (ties = same rank, next rank shifted: after two 5s there would be a 7) and the partition/global difference. Windows, standardized in 2003, are today the standard tool for top-k per group, running totals (running sums via ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW), and cross-period comparisons.

Definition 2.5 — NULL and three-valued logic SQL distinguishes “unknown value” (NULL) from actual values. Any comparison with NULL returns UNKNOWN, and WHERE keeps only the TRUE rows — the UNKNOWN ones are silently eliminated. Truth tables extend naturally:

AND T U F
T T U F
U U U F
F F F F

Trapping consequence: WHERE a <> 1 (like WHERE NOT (a = 1)) silently drops the rows where a is NULL, since the comparison is UNKNOWN there; to keep them, write a IS DISTINCT FROM 1 (SQL:1999) or add an explicit OR a IS NULL.

Aggregates ignore NULLs (AVG divides by the number of non-NULL values), while COUNT(*) counts all rows — COUNT(a) < COUNT(*) is the classic symptom of incomplete data.

Definition 2.6 — Recursion: WITH RECURSIVE SQL:1999 filled the gap of Theorem 1.7: a recursive CTE WITH RECURSIVE T AS (base UNION SELECT … FROM T …) computes the least fixed point of the equation: one starts from the base query and iterates the recursive part until no new tuples are produced.

The set operator UNION is what makes this true. With UNION ALL, duplicates are kept: on cyclic data the same tuples are derived again at every iteration, so the query never stops and its result is not the set fixed point. Keep UNION ALL for acyclic data, where it terminates but may return duplicates, or for queries with an explicit depth bound.

On a parent-child table this fixed point is exactly the transitive closure (Example 2.7): DBMSs implement it by semi-naive evaluation (a “delta” rewrite that, at each iteration, joins only the tuples produced at the previous one), and ch. 9 will find this fixed-point semantics again for querying knowledge graphs.

Example 2.7 — Ancestors in a tree in SQLite

-- table parent(parent, child): one row per parent-child edge
WITH RECURSIVE ancestor(a, d) AS (          -- a ancestor of d
  SELECT parent, child FROM parent
  UNION                                    -- UNION (without ALL) = deduplicated
  SELECT p.parent, a.d
  FROM ancestor a JOIN parent p ON p.child = a.a
)
SELECT d, COUNT(DISTINCT a) AS num_ancestors FROM ancestor GROUP BY d;
-- semantics: fixed point; at each iteration, only the tuples
-- produced at the previous iteration are expanded (semi-naive evaluation).

Example 2.8 — SQLite and SQLAlchemy in Python

import sqlite3
from sqlalchemy import create_engine, MetaData, Table, Column, Integer, String, ForeignKey, select

# --- 1. SQLite: everything lives in one file, ACID transactions included
con = sqlite3.connect("school.db")
con.execute("PRAGMA foreign_keys = ON")        # optional but recommended
con.executescript("""
CREATE TABLE IF NOT EXISTS student(
  id INTEGER PRIMARY KEY, name TEXT NOT NULL, major TEXT);
CREATE TABLE IF NOT EXISTS course(
  code TEXT PRIMARY KEY, title TEXT, credits INTEGER CHECK (credits > 0));
CREATE TABLE IF NOT EXISTS enrolled(
  id INTEGER REFERENCES student(id),
  code TEXT REFERENCES course(code),
  year INTEGER, PRIMARY KEY (id, code));
""")
con.executemany("INSERT OR IGNORE INTO student VALUES (?,?,?)",
                [(1043, "Anna", "DS"), (1088, "Bruno", "AI"), (2101, "Chloé", "DS")])
con.executemany("INSERT OR IGNORE INTO course VALUES (?,?,?)",
                [("DB", "Databases", 4), ("DL", "Deep Learning", 5), ("OT", "Optimal Transport", 3)])
con.executemany("INSERT OR IGNORE INTO enrolled VALUES (?,?,?)",
                [(1043, "DB", 2025), (1043, "DL", 2026), (1088, "DB", 2025),
                 (1088, "OT", 2025), (2101, "DL", 2025)])
con.commit()                                   # close the implicit transaction

# each student's courses, ranked by year with a window function
q = """
SELECT e.name, c.title, i.year,
       RANK() OVER (PARTITION BY e.id ORDER BY i.year) AS year_rank,
       COUNT(*) OVER (PARTITION BY e.id)                AS num_courses
FROM enrolled i
JOIN student e ON e.id = i.id
JOIN course   c ON c.code = i.code
WHERE c.credits >= 3
"""
for row in con.execute(q):
    print(row)

# explicit transaction: all or nothing
try:
    con.execute("BEGIN")
    con.execute("UPDATE student SET major='AI' WHERE id=2101")
    con.execute("INSERT INTO enrolled VALUES (2101, 'DB', 2026)")
    con.commit()
except sqlite3.IntegrityError as err:        # e.g. foreign key violated
    con.rollback()
    print("cancelled:", err)

# execution plan (useful for ch. 8):
for row in con.execute("EXPLAIN QUERY PLAN " + q):
    print(row)

# --- 2. SQLAlchemy Core: composable queries, DBMS-independent
engine = create_engine("sqlite:///school.db", echo=False)
md = MetaData()
student = Table("student", md, Column("id", Integer, primary_key=True),
                 Column("name", String), Column("major", String))
enrolled = Table("enrolled", md, Column("id", Integer, ForeignKey("student.id"),
                 primary_key=True), Column("code", String, primary_key=True),
                 Column("year", Integer))
with engine.connect() as cn:
    stmt = (select(student.c.name, enrolled.c.code)
            .join(enrolled, student.c.id == enrolled.c.id)
            .where(enrolled.c.year == 2025).order_by(student.c.name))
    print(cn.execute(stmt).fetchall())

Note — Views and constraints A view is a named, stored query, reused like a table (re-evaluated or materialized depending on the engine): an abstraction on a par with a function. Constraints (PRIMARY KEY, FOREIGN KEY, CHECK, UNIQUE, NOT NULL) are checked at every write: postponing validation until after loading (the “data lake” approach of ch. 10) shifts the cost, it does not remove it.

3. Design: dependencies and normal forms

A poorly designed database leads to the three classic anomalies: insertion (impossible to record one fact without another), deletion (losing one fact while deleting another), update (repeating the same fact in several places). Normalization theory, also due to Codd and developed by Bernstein, Rissanen and others, diagnoses and corrects these defects through functional dependencies.

Definition 3.1 — Functional dependency (FD) Let R be a schema and r an instance. A functional dependency X \to Y (where X, Y are sets of attributes) is satisfied by r if \forall\, t_1, t_2 \in r,\quad t_1[X] = t_2[X] \;\Rightarrow\; t_1[Y] = t_2[Y]. X “functionally determines” Y: knowing X suffices (in r) to know Y.

A set F of FDs is a logical object: it is required to hold for all instances allowed by the schema. X \to Y with Y \subseteq X is called trivial.

Theorem 3.2 — Armstrong’s axioms (validity and completeness) The following three rules are valid (every derivable FD is satisfied by every instance satisfying F):

  • reflexivity: if Y \subseteq X then X \to Y;
  • augmentation: if X \to Y then XZ \to YZ;
  • transitivity: if X \to Y and Y \to Z then X \to Z.

They are moreover complete: every FD logically implied by F is derivable by these rules.

Proof. Validity (direct proofs).

  • Reflexivity: if t_1[X]=t_2[X] and Y\subseteq X, the restrictions to Y coincide.
  • Augmentation: assume X\to Y and t_1[XZ]=t_2[XZ]. Then t_1[X]=t_2[X] hence t_1[Y]=t_2[Y], and t_1[Z]=t_2[Z] by hypothesis; therefore t_1[YZ]=t_2[YZ].
  • Transitivity: t_1[X]=t_2[X] gives t_1[Y]=t_2[Y], which gives t_1[Z]=t_2[Z].

Completeness: one shows that if X \to Y is not derivable, then the following two-tuple instance violates it while satisfying F. Take two tuples t_1, t_2 that agree exactly on the attributes of X^{+} (the closure of Def. 3.3) and differ on all the others.

Every FD V \to W \in F is then preserved: either V \subseteq X^{+} and W stays there, or V \not\subseteq X^{+} and the tuples already differ on V. Meanwhile X\to Y is violated. ∎

Definition 3.3 — Closure X^+ and key The closure of X under F is X^{+} = \{A \mid X \to A \text{ derivable from } F \text{ by Armstrong}\}; it is computed in O(|F|) passes (greedy algorithm: add the attributes determined by what we already have, up to a fixed point).

K is a key if K^{+} contains all the attributes and no proper subset of K does; attributes belonging to some key are called prime.

Definition 3.4 — Normal forms Let R be equipped with F, and K a key:

  • 1NF: all values are atomic (no lists or structured values in a cell);
  • 2NF: 1NF and no FD X \to Y with X a strict subset of a key and Y non-prime (no partial dependency on the key);
  • 3NF: 2NF and for every nontrivial FD X \to Y: X is a superkey, or Y is prime;
  • BCNF (Boyce-Codd): for every nontrivial FD X \to Y, X is a superkey — the purest condition, without the “prime attribute” escape hatch.

Theorem 3.5 — Lossless binary decomposition Let R be decomposed into \rho = (R_1, R_2), with R_1 \cup R_2 = R. The decomposition is lossless, that is, for every instance r satisfying F, r = \pi_{R_1}(r) \Join \pi_{R_2}(r), if and only if

Key result — lossless-join test (R_1 \cap R_2) \to R_1 \quad\text{or}\quad (R_1 \cap R_2) \to R_2 \;\in\; F^{+}.

Proof. The “if” direction (the only one proved in full). Assume X = R_1 \cap R_2 and X \to R_1.

  1. The inclusion r \subseteq \pi_{R_1}(r) \Join \pi_{R_2}(r) always holds.
  2. Conversely, take t_1 \in \pi_{R_1}(r) and t_2 \in \pi_{R_2}(r) agreeing on X, and let us show that their join s = t_1 \circ t_2 is in r.
  3. Let u_1, u_2 \in r be witnesses (u_1[R_1] = t_1, u_2[R_2] = t_2).
  4. Since t_1 and t_2 agree on X, we have u_1[X] = u_2[X].
  5. The FD X \to R_1 forces u_1[R_1] = u_2[R_1].
  6. So u_2 equals t_1 on R_1 and t_2 on R_2: u_2 = s \quad\text{after all, and}\quad s \in r.

(The converse is proved by exhibiting, when neither FD holds, a two-tuple instance whose join produces a spurious tuple.) ∎

Example 3.6 — Normalizing an orders table Consider R(\text{order}, \text{item}, \text{qty}, \text{customer}, \text{city}, \text{unit\_price}) with F = \{order·item \to qty,  order \to customer,  customer \to city,  item \to unit_price\}.

Key: \{order, item\} (its closure generates everything).

Visible anomalies: a customer’s city is repeated on each of their lines (update anomaly); an item with no order cannot exist (insertion). Normalization:

  • 2NF: order \to customer depends on a part of the key → extract Order(order, customer, city) — city comes along, since order \to customer \to city — and Line(order, item, qty); likewise item \to unit_price → Item(item, unit_price);
  • 3NF: in Order(order, customer, city), the FD customer \to city has a determinant that is not a superkey and city is not prime → extract Customer(customer, city);
  • result: Line(order, item, qty), Order(order, customer), Customer(customer, city), Item(item, unit_price) — each binary decomposition is validated by Theorem 3.5 (remember to check the intersection at each step).

Note — 3NF vs BCNF, and real life Every relation admits a 3NF decomposition that is lossless and dependency-preserving (Bernstein synthesis: minimal cover, one schema per determinant, plus a key); some relations, on the other hand, have no BCNF decomposition that is both lossless and dependency-preserving — classic case: R(\text{city}, \text{street}, \text{zip\_code}) with {city, street} \to zip_code and zip_code \to city.

3NF is thus the minimal guarantee, BCNF the ideal when it preserves. In practice, warehouses deliberately denormalize (joins paid at write time rather than at read time): theory says what to pay, not whether to pay.

4. Storage: pages, files, external sorting

A DBMS lives on a hierarchy of memories with performance gaps of several orders of magnitude. The whole discipline of storage follows from this brute fact:

Level Order of magnitude Access
Registers / L1-L3 cache 1-100 ns words
RAM ~100 ns, a few tens of GB bytes
NVMe SSD ~20-100 µs per access, TB pages (4-16 KiB)
SATA SSD / HDD ~100 µs / ~5-10 ms, tens of TB blocks
Network / object store ~10-100 ms, unlimited objects

Definition 4.1 — Page, record, organization The page (block) is the unit of input/output: one reads and writes whole pages (typically 4 to 16 KiB), never isolated bytes.

Records are stored in pages with slots (slotted pages: a directory at the head of the page gives the address of each record, allowing insertions, deletions, and variable-length records). A file organization defines how pages are linked:

  • heap (append at the end, sequential read);
  • sorted (order maintained: costly insertion, binary search);
  • or hashed (ch. 5).

Proposition 4.2 — Cost of a sequential scan For a file of N pages and a sequential throughput D pages/s, a full scan costs N/D.

Worked example: a 10 GB table in 8 KiB pages occupies N = 10^{10}/8192 \approx 1.22\times 10^6 \text{ pages}.

  • on HDD at 150 MB/s: \approx 67 s;
  • on NVMe SSD at 3 GB/s: \approx 3.3 s;
  • in random accesses at 10,000 IOPS: 122 s.

Hence the DBMS obsession with sequential access and prefetching.

Theorem 4.3 — External merge sort With M pages of buffer memory available and N pages to sort, external merge sort produces sorted initial runs of M pages, then merges them with arity M-1 (one output page). The number of passes equals

Key result — passes of external merge sort p \;=\; 1 + \Big\lceil \log_{M-1} \lceil N/M \rceil \Big\rceil,

and the total I/O cost is 2Np (each pass rereads and rewrites everything).

Proof. Pass 0 reads N pages and writes \lceil N/M\rceil sorted runs: cost 2N.

At each merge pass, at most M-1 runs are consumed into one run (fan-in M-1 because one buffer page must be kept for output): the number of runs is divided by at least M-1, hence \lceil\log_{M-1}\lceil N/M\rceil\rceil merges. Each merge rereads/rewrites the pages involved, hence 2N per pass. ∎

Example 4.4 — Numbers

  • N = 1000 pages, M = 50: initial runs: 20; \log_{49}(20) \to 1 merge pass (fan-in 49 ≥ 20); p = 2, cost 2\times1000\times2 = 4000 \text{ pages read+written}.
  • With N = 10^6, M = 10^3: 1000 runs, fan-in 999, \lceil\log_{999}(1000)\rceil = 2, p=3, cost 6\times10^6 At 10,000 pages/s, that is about ten minutes.

Sorting remains nearly linear in practice thanks to the base (not the factor) of the logarithm.

Definition 4.5 — Buffer pool Between the disk and the processor, the DBMS manages a buffer pool: a RAM cache of page bytes, organized into frames the size of a page, with a page-to-frame mapping table. Every read goes through it: hit (the page is already there, RAM access) or miss (disk read then installation in a free frame).

When no frame is free, a replacement policy chooses the victim — LRU as a first approximation, but real DBMSs prefer variants (LRU-K, clock with a reference bit, least-frequently-used): a massive sequential scan must not evict hot pages (hence the usage counter of scans).

Writes: a modified page becomes dirty and is rewritten to disk only upon eviction — or earlier, by checkpointing (ch. 7).

The parameter M of the sort and join algorithms (ch. 8) is precisely the share of the buffer pool allocated to the operator: the whole physics of queries plays out in this cache.

Note — Storing columns, not just rows Modern warehouses (Parquet, BigQuery, Snowflake, DuckDB) store tables by columns: reading 3 columns out of 100 reads only ~3% of the data, with adapted encodings (dictionary, RLE) and min/max statistics per block to skip chunks. The logical model remains relational: a perfect illustration of logical/physical independence — and reading columns fast is producing learning matrices fast (see the Machine Learning module, ch. 4).

5. Indexes: B+ trees and hashing

An index is a secondary structure that speeds up locating records without scanning them all. Two families dominate: B+ trees for range queries and sorting, hashing for exact equality. The DBMS maintains the index at every write: each additional index slows down insertions — there is no free index.

Definition 5.1 — B+ tree A B+ tree of order d is a balanced tree in which every node contains between d and 2d keys (only the root may contain fewer, at least 1), every internal node has (number of keys + 1) children, all the data resides in the leaves, and the leaves are chained together (range scan without going back up).

The keys of an internal node are separators: the subtree between two keys contains the records of the corresponding open interval. When a leaf splits, the median key is copied to the parent (it remains present in the right leaf); when an internal node splits, it is moved up to the parent.

Theorem 5.2 — Height of a B+ tree A B+ tree of order d containing n entries has height

Key result — height of a B+ tree h \;\leq\; 2 + \log_{d+1}(n/2),

so that search, insertion, and deletion cost \Theta(\log_d n) logical I/Os — in practice 3 to 5 pages.

Proof. Count the minimal number of leaves of a tree of height h (root at level 1, leaves at level h, h \geq 2). The root has at least 2 children; every internal non-root node has at least d+1 children (because at least d keys); every leaf carries at least d entries.

The number of leaves is therefore at least 2\,(d+1)^{h-2}, and each contains at least d entries: n \geq 2d\,(d+1)^{h-2} \geq 2\,(d+1)^{h-2} \quad (\text{since } d \geq 1), hence (d+1)^{h-2} \leq n/2 and the announced bound.

Search: one page per level; insertion: descent plus possible split cascades upward, one page per level; both are \Theta(h). ∎

Example 5.3 — A billion entries in 4 hops With 8 KiB pages and 16 B per key+pointer, a node holds up to about 512 keys: take d = 256 (every non-root node then has between 257 and 513 children). For n = 10^9 entries, the worst case (all nodes half full) gives h \leq 2 + \log_{257}(5\times10^8) = 2 + \frac{\ln(5\times10^8)}{\ln 257} = 2 + \frac{20.03}{5.55} \approx 5.6, hence h \leq 5.

With a typical fill of about 70% (fan-out ≈ 350), the 10^9 entries need about 2.9\times10^6 leaves, and 350^2 \approx 1.2\times10^5 < 2.9\times10^6 < 350^3 : three internal levels above the leaves, height 4.

The upper levels fit in RAM: an equality search costs 1-2 page reads instead of the 1.2\times10^6 of the full scan (Prop. 4.2).

Example 5.4 — Cascading insertions (d = 1) Insert 10, 20, 5, 15, 30, 25, 35, 40, 45 into a B+ tree of order 1 (1 to 2 keys per node).

  1. After 10, 20: leaf [10, 20].
  2. The 5 overflows: split into [5, 10] and [20], copy of 20 to the new root.
  3. The 15 refills the first leaf: [5,10,15] → [5,10] and [15], root [15, 20], three leaves.
  4. The 30 fills the right leaf [20, 30].
  5. The 25 makes it overflow: [20,25] / [30], copy of 30 — the root [15,20,30] overflows in turn: internal split, key 20 is moved to the new root [20].
  6. The 35, the 40 (split of [30,35,40] → [30,35] / [40], copy of 40) and the 45 lead to the final tree (height 3):
                    [20]
             ┌────────┴────────┐
           [15]              [30, 40]
         ┌──┴──┐        ┌──────┼──────┐
      [5,10]  [15]   [20,25] [30,35] [40,45]
      ──────── leaves chained ────────▶

Each leaf has between 1 and 2 keys, the root at least 1: the structure stays at least half full after each deletion rebalanced by borrowing or merging — this is what guarantees Theorem 5.2 over time.

Definition 5.5 — Static and extendible hashing An on-disk hash table applies h(k) = k \bmod B to store the record with key k in bucket h(k): exact equality in 1 + (overflows) I/Os.

If the n keys hash uniformly, each bucket receives n/B keys on average — the “balls into bins” count of ch. 0. Choosing B so that this average stays well below the bucket capacity keeps overflow chains short; skewed keys or a poor hash function break the assumption.

Extendible hashing grows the table dynamically. A directory of 2^g pointers (global depth g), indexed by the g low-order bits of the hash, points to buckets. A bucket of local depth p_i \leq g holds the keys whose p_i low-order bits coincide, and several directory entries may point to it.

When a bucket overflows, it is split according to bit p_i + 1 and its local depth increases by one; if p_i was equal to g, the directory is first doubled (g \leftarrow g + 1).

Example: buckets of 2 slots, g = 1; bucket “0” (local depth 1) holds 4 (binary 100) and 12 (1100). Inserting 2 (10) overflows it:

  • since p = g = 1, the directory doubles to g = 2;
  • the bucket splits on the second bit — 4 and 12, ending in 00, stay; 2, ending in 10, moves to the new bucket “10”;
  • the bucket “1” of odd keys is untouched, now pointed to by both entries 01 and 11.

The load factor stays under control without global reorganization; Litwin’s linear hashing achieves the same effect without a directory.

Proposition 5.6 — What each index can do

Criterion B+ tree Hashing
Equality (=) \log pages 1 page (ideal)
Range, prefix, order excellent (leaves chained) impossible
Insertion \log, balance guaranteed amortized O(1), overflows
Typical uses primary keys, filtered/sorted columns, foreign keys exact join tables, hash joins (ch. 8), in-memory dedup

Composite index rule: a B+ index on (a, b, c) serves predicates on a, then (a, b), then (a, b, c) — but not b alone: the keys are sorted like a dictionary (a first).

An index can also cover a query (all requested columns are in the index: the table is never read — a covering index often divides times by 10).

6. Transactions and concurrency

Several programs access the same data at the same time. The transaction is the unit of consistency: a sequence of read (r) and write (w) operations that must execute as if it were alone. The ACID acronym summarizes the contract:

Definition 6.1 — Schedules, conflicts, serializability A schedule H is a sequence of operations from several transactions, preserving the internal order of each. Two operations are in conflict if they belong to different transactions, bear on the same object, and at least one is a write.

H is conflict-serializable (CSR) if it is equivalent by conflicts to a serial schedule; the serialization graph SG(H) has one vertex per transaction and an edge T_i \to T_j as soon as a conflicting pair of operations places an operation of T_i before one of T_j.

Theorem 6.2 — The serialization graph criterion H is conflict-serializable if and only if SG(H) is acyclic; in that case, every topological order of SG(H) gives an equivalent serial execution.

Proof. If H is equivalent to the serial schedule T_{\sigma(1)} \dots T_{\sigma(k)}, every edge T_i \to T_j of SG(H) must be respected by \sigma (conflict equivalence preserves the order of all conflicting pairs). The graph is embedded in a total order, hence acyclic.

Conversely, if SG(H) is acyclic with topological order \sigma, one checks that the associated serial schedule has the same conflicting pairs in the same order: for a given pair, the directed edge in SG(H) (which exists by definition) imposes the same relative order as \sigma. ∎

Example 6.3 — Lost update Let H = r_1(X)\, r_2(X)\, w_1(X)\, w_2(X)\, c_1\, c_2 with X a stock counter worth 100, T_1 adding 50 and T_2 subtracting 30.

  • Serialized (T_1;T_2): 100 \to 150 \to 120.
  • In H: both reads see 100, w_1 writes 150, then w_2 overwrites with 70: the update of T_1 is lost.
  • Conflicts on X: r_1(X) < w_2(X) gives T_1 \to T_2, and r_2(X) < w_1(X) gives T_2 \to T_1.

A cycle: H is not serializable — the graph had announced it even before reading the values.

Definition 6.4 — Two-phase locking (2PL) A transaction follows 2PL if it acquires all its locks before releasing a single one: growing phase (acquisitions), lock point, shrinking phase (releases). Strict 2PL keeps its write locks until commit (rigorous 2PL keeps all its locks until then) — it avoids cascading aborts. Locks can be shared (read) or exclusive (write), with S-S the only compatibility.

Theorem 6.5 — Every legal 2PL execution is serializable If a schedule H respects 2PL (for each transaction, legality of the locks being ensured: no lock acquired while the conflicting lock has not been released), then SG(H) is acyclic, hence H is CSR.

Proof. Reason by contradiction and assume a cycle T_1 \to T_2 \to \cdots \to T_k \to T_1.

  1. For each edge T_i \to T_j there exists an object q and a conflicting pair o_i[q] < o_j[q]. Since at least one of the two operations is a write, the locks of T_i and T_j on q are incompatible, hence do not overlap: legality imposes \text{unlock}_i(q) < \text{lock}_j(q).
  2. Let u_i be the instant when T_i releases its first lock (its lock point); then u_i \leq \text{unlock}_i(q) < \text{lock}_j(q) \leq u_j , the last inequality coming from 2PL: T_j cannot acquire a lock after u_j, so \text{lock}_j(q) \leq u_j.
  3. Chaining along the cycle: u_1 < u_2 < \cdots < u_k < u_1 which is a contradiction. ∎

Proposition 6.6 — Deadlock and isolation levels 2PL creates waiting cycles: T_1 holds X and wants Y, T_2 holds Y and wants X. DBMSs detect (wait-for graph, cycle = deadlock) and abort a victim (rollback then retry). The standard levels offered by SQL:

Level Dirty read Non-repeatable read Phantoms Mechanism
READ UNCOMMITTED possible possible possible no read locks
READ COMMITTED no possible possible short read locks
REPEATABLE READ no no possible long row locks
SERIALIZABLE no no no full 2PL / predicates
SNAPSHOT (MVCC) no no no* reads on a dated version, no read locks

(*) Snapshot isolation avoids these three anomalies but is still not serializable: it allows write skew (two transactions read overlapping data and write different rows, each on the basis of a snapshot that the other invalidates). Write-write collisions are detected at commit, and PostgreSQL’s SERIALIZABLE level also detects dangerous read-write patterns (error 40001, to be replayed).

MVCC (multi-version concurrency control) keeps several dated versions of each row: each transaction reads a consistent snapshot of the past — readers never block writers. This is the default strategy of PostgreSQL and of MySQL’s InnoDB engine.

7. The log and crash recovery

Durability requires writing to persistent storage before answering “commit”, and atomicity requires being able to undo — even after a crash right in the middle. Both requirements are solved by one and the same structure: the log (write-ahead log, WAL), an ordered sequence of records describing every modification, written sequentially (hence fast, see ch. 4).

Definition 7.1 — WAL, LSN, STEAL/FORCE policy Each log record receives a sequence number (LSN). For a page write by a transaction: <LSN, T, page, row, before-image, after-image, previous-LSN-of-T>. The WAL rule: the log fragment of a modification must reach the disk before the modified page.

Possible policies:

  • STEAL (one may evict an uncommitted modified page from the buffer → one must know how to undo) vs NO-STEAL;
  • FORCE (all pages are written at commit → no need to redo) vs NO-FORCE.

Every serious DBMS chooses STEAL/NO-FORCE — the most performant, the one that requires both undo and redo.

Proposition 7.2 — Idempotence and ARIES recovery The recovery passes must be idempotent (replayed twice, same effect): this holds if redo/undo are driven by the LSNs stored in the pages. The ARIES algorithm (Mohan et al., 1992), deployed in all major DBMSs, recovers in three phases:

  • Analysis: reread the log since the last checkpoint to rebuild the table of in-progress transactions and the dirty page table;
  • Redo: replay, in LSN order, the after-image of every modification whose LSN exceeds the one recorded in the page (repeating history, including uncommitted transactions);
  • Undo: cancel, in reverse LSN order, the modifications of uncommitted transactions, writing compensation records (CLR) — if the machine crashes again during recovery, one resumes where one had stopped.

Example 7.3 — Rolling out a recovery by hand Log (LSN: 1-7) then crash:

LSN Record Effect
1 checkpoint T1 active
2 T1: update X (before 10 → after 15) page P1 dirty
3 T2: update Y (before 5 → after 8) page P2 dirty
4 T1: commit T1 completed
5 T2: update X (before 15 → after 20) P1 dirty
6 T3: update Z (before 3 → after 7) P3 dirty
7 T2: … (no commit) crash here

ARIES recovery:

  1. Analysis → T2 and T3 in progress, T1 committed, pages P1-P3 potentially dirty.
  2. Redo: LSN 2, 3, 5, 6 replayed (X=20, Y=8, Z=7).
  3. Undo (reverse order): LSN 6 canceled (Z=3, CLR written), then LSN 5 (X=15) and LSN 3 (Y=5).

Result: T2 and T3 are rolled back, T1 survives.

Note that redo reapplies LSN 5 (a write of T2, uncommitted) before undoing it: this is the mark of ARIES — “repeat history” — and the reason for the CLR, which makes undo itself restartable.

Note — Checkpoints and the cost of durability A checkpoint writes a record to the log — in ARIES a fuzzy checkpoint, which saves the transaction table and the dirty page table without forcing the pages to disk — and so bounds the part of the log that analysis and redo must reread; without it, recovery would reread the whole log.

In exchange, every commit waits for at least one sequential log write (group commit: concurrent commits share a single flush): typically ~1 ms — the measurable price of ACID.

8. Query optimization

SQL is declarative: the execution plan — the order of operations, the join algorithms, the indexes used — is chosen by the optimizer. The principle:

  1. rewrite the query as an algebraic tree;
  2. generate the equivalent plans via the laws of Proposition 1.4;
  3. estimate their cost with a model;
  4. keep the cheapest. It is a closed loop between theory (equivalences) and physics (the cost model of ch. 4).

Definition 8.1 — Cost model and selectivity For each table, the catalog stores n (number of tuples), b (pages), and for each column ND(A) (number of distinct values) or even a histogram. The selectivity of a predicate is the expected fraction of rows:

  • sel(A = c) \approx 1/ND(A);
  • sel(A < c) via histogram;
  • sel(p_1 \wedge p_2) \approx sel(p_1)\cdot sel(p_2) (independence — a false but convenient assumption, corrected by modern engines via dynamic sampling).

Estimated cardinality of a node = selectivity × cardinality of the child.

Definition 8.2 — Join algorithms Let R (B_R pages) and S (B_S pages), with a buffer of M pages:

  • nested loops (NLJ): for each page of R, scan S — cost B_R + B_R\!\cdot\!B_S; by blocks of M-1 pages: B_R + \lceil B_R/(M-1)\rceil \cdot B_S ;
  • sort-merge join: sort R and S (Thm 4.3) then merge — cost 2B_R(1+\lceil\log_{M-1}\lceil B_R/M\rceil\rceil) + 2B_S(\dots) + B_R + B_S , zero if already sorted (B+ index);
  • hash join: partition both relations on the join key with the same hash (one pass if everything fits in M), then build a hash table per partition and probe — cost \approx 3(B_R+B_S), in memory if B_R or B_S fits in RAM.

Example 8.3 — Numbers: the winning plan cannot be guessed B_R = 1000 pages, B_S = 500, M = 50.

  • Simple nested loops: 1000 + 1000\times500 = 501\,000 pages.
  • By blocks: 1000 + \lceil 1000/49\rceil \times 500 = 1000 + 21\times500 = 11\,500.
  • Sort-merge: sorting R in 2 passes → 4000, sorting S in 2 passes → 2000, merge 1500: total 7500.
  • Hash: 3\times(1000+500) = 4500 — and if S fits in memory, 1000 + 500 = 1500.
Algorithm Cost (I/O pages) Success conditions
naive NLJ 501,000 none
block NLJ 11,500 M \geq 3
sort-merge 7,500 all; optimal if inputs sorted
hash 4,500 partitions ≤ M pages

The optimizer makes this table for every join and every nesting order: a query joining 10 tables already admits 10! \approx 3.6\times10^6 left-deep orders, and many more bushy trees.

System R’s dynamic programming (over subsets of relations, left-deep trees, Cartesian products avoided) reduces the search to about n\,2^{n-1} subproblems — still exponential, but tractable up to a dozen tables; the general problem is NP-hard, and beyond that engines switch to greedy or randomized searches.

Proposition 8.4 — Safe heuristic rewrites Before any cost-based search, one applies the “always winning” rewrites: push down selections (Prop. 1.4), push down projections (reduce tuple width), reject Cartesian products outside useful joins, eliminate redundant subqueries, transform IN into semi-joins. Each selection pushed down divides the cardinality of the subtree involved: with sel = 1/100, filtering 10^6 rows before rather than after a join gains a factor ~100 on that stream.

Engines add costly but profitable rewrites: elimination of unnecessary joins (a join by foreign key without reading the columns of S and without multiplicity is superfluous), materialized CTEs, plan reuse (plan cache).

Definition 8.5 — Evaluating selection: access paths Even before the joins, each relational operator comes in several implementations that the optimizer compares:

  • selection: sequential scan (B_R pages, Prop. 4.2), or B+ tree descent when an index covers the predicate (one page per level — the height h of Theorem 5.2 — plus the pages holding the matching records), or an index-only scan if the index contains all requested columns (zero table access);
  • projection and aggregation: tuple stream, with sorting or hashing to eliminate duplicates and group (sort-agg vs hash-agg, depending on available memory);
  • union, difference: merge of sorted streams or hash tables, in the same spirit as the joins of Def. 8.2.

The cost of each variant combines the selectivity model (Def. 8.1), the buffer pool (Def. 4.5), and the available indexes (ch. 5): reading an EXPLAIN is seeing this choice materialized node by node.

Note — Reading an EXPLAIN EXPLAIN QUERY PLAN (SQLite) or EXPLAIN (ANALYZE, BUFFERS) (PostgreSQL) shows the chosen tree and the real cardinalities. Typical diagnosis: an estimate off by a factor of 1000 (stale statistics, unmodeled correlation) → nested loops where hashing would be needed. Reflex: compare estimated vs actual rows node by node, and trace back to the first divergence.

9. Beyond relational: NoSQL, graphs, vectors

The relational model dominates transactional workloads, but three developments of the 2000-2020 period seeded new models: horizontal scale (NoSQL), data as graphs (knowledge graphs), and search by similarity over vectors — the fuel of learning systems and of RAG (see the Large Language Models module, chapter 7).

Theorem 9.1 — CAP (Brewer, 2000; Gilbert-Lynch, 2002) In a distributed system subject to network failures (partitions), one cannot simultaneously guarantee strong consistency (C) and availability (A): one must choose, at least during the partition.

Proof. Sketch. Partition the network into two components \Gamma_1, \Gamma_2. One client writes into \Gamma_1, another reads from \Gamma_2. If both operations succeed (A), the read cannot see the write: inconsistency (not C). To stay consistent, \Gamma_2 must refuse or block (not A). ∎

NoSQL systems therefore claim AP with eventual consistency (BASE); so-called CP systems sacrifice availability during partitions. The trade-off is managed per query (readable / writable consistency levels, quorum r + w > n).

Definition 9.2 — NoSQL families

  • key-value (Redis, DynamoDB): a distributed hash table (ch. 5 at cluster scale, consistent hashing);

  • documents (MongoDB): identified JSON trees, queried by fields — 1NF is deliberately abandoned in order to group what is read together;

    { "_id": "ord1043", "customer": { "name": "Anna", "city": "Palaiseau" },
      "lines": [ { "item": "DB", "qty": 2 }, { "item": "DL", "qty": 1 } ],
      "status": "shipped" }
  • columns (Cassandra, Bigtable): sparse rows physically grouped by column family and sorted by clustering key — SSTables and the log-structured merge tree (memtable → levels of sorted files, reads bounded by Bloom filters) replace the B+ tree;

  • graphs: see below — the only model where the recursive join is a first-class citizen.

Definition 9.3 — Property graphs and RDF A property graph (Neo4j; GQL standard) is a multi-relational graph whose nodes and edges carry key-value properties. The Cypher language queries by patterns:

MATCH (a:Person {name: "Anna"})-[:ENROLLED_IN*1..3]->(c:Course)
WHERE c.credits >= 3
RETURN DISTINCT c.code;

The RDF model (W3C) represents everything as triples (subject, predicate, object) forming a global labeled directed graph, with IRIs (universal identifiers) and ontologies (RDFS, OWL) for reasoning. SPARQL takes up SQL (SELECT/WHERE) on graph patterns:

SELECT DISTINCT ?code WHERE {
  :Anna :enrolledIn+ ?c .            # path of length ≥ 1 (SPARQL 1.1)
  ?c :credits ?cr . FILTER(?cr >= 3)
  ?c :code ?code . }
Criterion Property graph RDF
Identifiers local to the database global IRIs → natural federation
Edge properties yes, first-class by reification (statement-node)
Schema flexible, local constraints RDFS/OWL ontologies, inference
Queries Cypher/GQL, patterns + paths SPARQL, federated, reasoning
Ecosystem apps, recommendation, integration web of data, science, libraries

For learning on graphs (node embeddings, GNNs, message passing), both models project onto sparse matrices — see the Graph ML module, chapters 1 (why graphs?) and 5 (GNNs).

Definition 9.4 — Vector databases and ANN search A vector store indexes embeddings x_i \in \mathbb{R}^d (produced by encoders — see the NLP module, ch. 4, and Multimodal GenAI, ch. 5) with a distance (cosine, L^2) and answers “the k nearest neighbors of q”.

Exact search costs O(nd): for n = 10^7, d = 768, that is 7.7\times10^{10} multiplications per query — prohibitive in service.

Approximate (ANN) search trades a bit of recall for orders of magnitude of speed:

  • IVF (inverted file): partition the space into n_{list} cells by k-means (see the Machine Learning module, ch. 7); explore only the n_{probe} closest cells.

    Example: n_{list} = 4096, n_{probe} = 16 over 10^7 vectors: ~ 39\,000 distances instead of 10^7 — 250× less work, with a typical recall above 95% on real (clustered) embeddings;

  • HNSW (Malkov-Yashunin): hierarchical “navigable small-world” graph. Layer 0 contains all the vectors, the upper layers increasingly sparse vertices (a node appears at layer \ell with probability p^{\ell}, for a fixed p < 1).

    The search starts from the top layer’s vertex and performs greedy routing (move to the neighbor that gets closest to q) while going down layer by layer; insertion links the new node to its M nearest neighbors found this way.

    The long links of the upper layers (chosen by a diversity heuristic) provide the nearly logarithmic shortcut; practical cost ~O(\log n), tunable recall, at the price of costly construction and an in-memory graph;

  • quantization (PQ): split x \in \mathbb{R}^d into m sub-vectors each quantized over 2^8 centroids; the distance becomes a sum of terms read from tables — memory falls from 4d bytes (float32) to m bytes per vector, at the price of an approximate distance.

Example 9.5 — Vector search in Python (full scan + IVF)

import numpy as np
from sklearn.cluster import MiniBatchKMeans

rng  = np.random.default_rng(0)
X    = rng.normal(size=(100_000, 64)).astype("float32")   # vector "database"
q    = rng.normal(size=(64,)).astype("float32")

def normalize(A): return A / np.linalg.norm(A, axis=1, keepdims=True)
Xn, qn = normalize(X), q / np.linalg.norm(q)

# --- 1. exact search (brute force): O(n d)
d_all = Xn @ qn
top10_exact = set(np.argsort(d_all)[-10:])

# --- 2. IVF: nlist cells by k-means, we probe nprobe cells
nlist, nprobe = 256, 8
km = MiniBatchKMeans(n_clusters=nlist, n_init=3, random_state=0).fit(Xn)
cell_of_X = km.labels_
d_centro  = np.linalg.norm(km.cluster_centers_ - qn, axis=1)
probes    = np.argsort(d_centro)[:nprobe]
cand      = np.where(np.isin(cell_of_X, probes))[0]
top10_ivf = set(cand[np.argsort(Xn[cand] @ qn)[-10:]])

print(len(top10_exact & top10_ivf) / 10, "recall@10")
# ~0.3 here: isotropic random vectors have no cluster structure, the worst
# case for IVF; raise nprobe to watch the recall/cost trade-off
print(f"exact: {len(Xn)} distances; IVF: {len(cand) + nlist} distances")

Recall @k is traded against cost: it is the same trade-off as for B+ trees (exact, logarithmic, constrained by prefixes) — but with an assumed probabilistic tolerance. Relational DBMSs today integrate these indexes as extensions (pgvector; the Large Language Models module, ch. 7, shows the complete RAG pipeline that consumes them).

10. Web data and heterogeneous integration

The web is the largest database in the world — without schema, without keys, without constraints. The data feeding learning (and data journalism) arrives as HTML pages, semi-structured files (JSON, XML), tables, documents. This chapter addresses the specific problems: extract, match, merge.

Definition 10.1 — Semi-structured data and the web of data Data is semi-structured when its schema is implicit, carried by the data itself (JSON/XML trees, RDF graphs). The web of data (linked data) publishes RDF with shared IRIs, and also nestles into ordinary pages via embedded annotations (schema.org in JSON-LD): search engines derive “rich snippets” from them.

Interoperability rests on ontologies (shared vocabularies: FOAF, Dublin Core, schema.org) and alignments (owl:sameAs between IRIs denoting the same real-world entity).

Definition 10.2 — Extraction, entity linking, entity resolution Extraction turns unstructured data into structured data: DOM wrappers for pages, table extraction (header row detection, column typing), information extraction by models — the NLP module (ch. 6: NER) is its engine.

Entity linking attaches a text mention (“Macron”, “E. Macron”) to a reference entity (the president…); entity resolution (record linkage) decides whether two records describe the same thing.

Typical signature: Jaccard similarity over sets of n-grams, J(A,B) = |A \cap B|\,/\,|A \cup B| . For example, take the sets of character bigrams of “ALGA”, \{AL, LG, GA\}, and of “LGAX”, \{LG, GA, AX\}: intersection {LG, GA} (2 elements), union of 4, hence J = 0.5.

Proposition 10.3 — Blocking: making quadratic matching practical Comparing n = 10^6 records pairwise costs n(n-1)/2 \approx 5\times10^{11} \text{ comparisons}. Blocking compares only the pairs sharing a blocking key (name prefix, soundex, locality). With 1000 balanced blocks of 1000 records: 1000 \times \binom{1000}{2} \approx 5\times10^8 \text{ comparisons} — 1000× fewer.

The risk is the false negative (two duplicates in different blocks); one therefore multiplies the blocking keys and divides the comparisons by filtering (pairs whose name similarity is below a threshold are discarded before the costly comparisons).

Definition 10.4 — Integration: matching, fusion, provenance Integrating k sources requires:

  1. schema matching (“Prenom” ≈ “first_name”, by similarity of names, types, values);
  2. fusion of conflicting values (voting, freshness, trust in the source);
  3. provenance — which source produced which value, in order to audit and correct.

Architectures: ETL toward a schematized warehouse, or ELT (load raw, transform at query time); the data lake stores raw data, the lakehouse adds transactions and a catalog.

These topics (and data-assisted fact-checking) are the home ground of the CEDAR team — the subject of the next chapter.

Note — Quality and trust The GIGO rule (garbage in, garbage out) is constant: data preparation consumes the majority of ML project time. Knowing where data is born and how it is merged is not “data plumbing”: it is the condition for model reproducibility — and for the credibility of published figures.

11. Course research notes — CEDAR (Mohanty)

The module is led by Madhulika Mohanty, Inria researcher (chargée de recherche) in the CEDAR team (Inria Saclay — École Polytechnique), founded and led by Ioana Manolescu, Inria senior researcher (directrice de recherche), a specialist in complex and heterogeneous data management.

The team’s work draws the arc of this module: from the foundations of data systems to the web of data, then to the use of data to verify information — Madhulika Mohanty contributes in particular on the querying and exploration of graph data. Verified selection:

Note — What this changes for you Project topics draw on this work: summarizing an RDF graph, integrating two heterogeneous sources with provenance, verifying a published statistic against open datasets — modeling (ch. 3), querying efficiently (ch. 5-8), assessing trust (ch. 10).

Exercises

  1. ★ Algebra and SQL (solved). On the database of Example 1.2, express in algebra then in SQL: (a) the majors of the students enrolled in the DB course; (b) the students enrolled in all courses of at least 4 credits; (c) the courses with no enrollee.

    Solution.

    (a) The algebra expression is \pi_{\texttt{major}}(\texttt{Student} \Join \sigma_{\texttt{code}=DB}(\texttt{Enrolled})) — SQL: join + WHERE.

    (b) Division: \pi_{\texttt{id},\,\texttt{code}}(\texttt{Enrolled}) \div \pi_{\texttt{code}}(\sigma_{\texttt{credits}\geq4}(\texttt{Course})) (the dividend must contain the divisor’s attribute code, Def. 1.6), then join with Student: only 1043 (Anna) takes both DB and DL. In SQL, NOT EXISTS (SELECT … FROM Course c WHERE credits >= 4 AND NOT EXISTS (SELECT … FROM Enrolled i WHERE i.id = e.id AND i.code = c.code)) — the double negation of Prop. 2.2, applied to the division of ch. 1.

    (c) The difference \pi_{\texttt{code}}(\texttt{Course}) - \pi_{\texttt{code}}(\texttt{Enrolled}), SQL: NOT EXISTS or LEFT JOIN … WHERE i.id IS NULL — the anti-join.

  2. ★ Closure and 3NF (solved). R(A,B,C,D,E), F = \{A \to B,\; BC \to D,\; D \to E\}. (a) Compute the keys. (b) Is the relation in 3NF? (c) Decompose into 3NF without loss and verify each step with Theorem 3.5.

    Solution.

    (a) Closure of AC: (AC)^{+}:\ A \text{ gives } B,\ ABC \text{ gives } D,\ D \text{ gives } E \ \to\ \text{everything}, so AC is a key. Neither A nor C alone is: A^{+} = AB, C^{+} = C. Prime attributes: A, C.

    (b) No. Not 3NF: in D \to E the determinant is not a superkey (D^{+} = DE) and E is not prime; BC \to D fails in the same way (BC^{+} = BCDE).

    Not even 2NF: in A \to B the determinant A is a strict subset of the key AC and B is not prime — a partial dependency, the first defect that Example 3.6 removes.

    (c) By 3NF synthesis (minimal cover \{A\to B, BC\to D, D\to E\}, one schema per determinant, plus a key): (AB, BCD, DE, AC) — lossless and FD-preserving.

    The “by hand” decomposition (ABCD, DE) then (AB, ACD) is lossless (at the second step the intersection A determines AB, Thm 3.5) and preserves A\to B.

    But it loses the dependency BC \to D: no schema contains B, C and D together, so the constraint can no longer be checked locally. Synthesis avoids precisely this pitfall.

  3. ★ B+ by hand (solved). Insert in order 2, 4, 6, 8, 10, 12, 14 into a B+ tree of order d=1; give the final tree and its height (same conventions as Example 5.4: when a leaf [a,b,c] splits, the copied separator is c, the first key of the right leaf).

    Solution.

    • 2 then 4: leaf [2,4].
    • The 6 overflows [2,4,6] → [2,4] / [6], copy of 6: root [6].
    • The 8: leaf [6,8].
    • The 10: [6,8,10] → [6,8] / [10], copy of 10: root [6,10], leaves [2,4] [6,8] [10].
    • The 12: leaf [10,12].
    • The 14: [10,12,14] → [10,12] / [14], copy of 14: the root [6,10,14] overflows (3 keys); internal split: the median key 10 is moved to the new root, internal children [6] and [14].

    Final tree:

                  [10]
              ┌──────┴──────┐
            [ 6 ]         [ 14 ]
          ┌──┴──┐        ┌──┴──┐
        [2,4] [6,8]   [10,12] [14]

    Height: 3. Check: all leaves at the same level, 1 ≤ keys ≤ 2 everywhere — the invariants of Definition 5.1 hold at every step.

  4. ★ Serializability (solved). Let H = r_1(X)\, r_2(X)\, w_1(X)\, w_2(X)\, r_1(Y)\, w_1(Y)\, c_1\, c_2 . Build SG(H); is H serializable? recoverable? What would 2PL do?

    Solution.

    • Conflicts on X: r_1<w_2 (T1→T2), r_2<w_1 (T2→T1), w_1<w_2 (T1→T2). On Y: T2 does not write Y.
    • Graph: T1↔︎T2 → cycle → not serializable (Thm 6.2).
    • Recoverable? T2 reads X (original value) then writes X after w_1: T2 reads nothing written by T1 → yes, recoverable — but with a lost update.
    • Under 2PL with shared and exclusive locks, both transactions hold a shared lock on X after their reads; w_1(X) then waits for T_2’s lock and w_2(X) for T_1’s: a deadlock (Prop. 6.6).

    The DBMS aborts one of them, say T_2, lets T_1 finish and replays T_2 — the non-serializable interleaving is impossible, Theorem 6.5 in action, at the price of an abort.

  5. ★ External sort. N = 2\times10^5 pages, M = 500. Compute the initial runs, passes, total I/O cost; how long on a disk at 20,000 pages/s?

    Hint. 400 runs, fan-in 499 → \lceil\log_{499} 400\rceil = 1, p = 2, cost 2Np = 8\times10^5 pages → 40 s.

  6. ★ Vectors: sizing. 10^7 embeddings in d = 768 (float32): memory size? Number of multiplications for an exact k-NN? With IVF n_{list} = 4096, n_{probe} = 32 (balanced cells): comparative work performed, and gain?

    Hint. 30.7 GB; 7.68\times10^{10} products; IVF: 10^7/4096 \times 32 \approx 78\,000 vectors probed + distances to the centroids, i.e. ~1% of the exact work, at the price of recall.

  7. ★★ Plan choice. Join R \Join S with B_R = 200, B_S = 10\,000, M = 52, a selection on R of selectivity 1/100 that can be pushed before the join.

    Compare: sort-merge on (R,S); hashing; and the order S \Join \sigma(R) in block nested loops.

    Hint. Filtering R costs one scan (200 pages) and leaves \sigma(R) = 2 pages, which fit in memory. A block nested loop with \sigma(R) as the outer relation then scans S once: 200 + 10\,000 = 10\,200 \text{ pages in total}, against about 3\times(200 + 10\,000) = 30\,600 for a hash join of the unfiltered inputs.

    Rule: push the selection down, then keep the small relation in memory as the outer loop.

  8. ★★ SQLite recursion. Write in recursive SQL: (a) the degree (distance) of separation of each pair of users of a network (table follow(a,b)); (b) the connected components of an undirected graph.

    Hint. (a) propagate the length by accumulating and take the MIN per pair (window or GROUP BY), and stop at a depth bound (a shortest path has fewer edges than there are users): on a cyclic network the accumulated lengths keep growing, so even UNION would never reach a fixed point (Def. 2.6); (b) propagate the MIN of reached identifiers up to the fixed point.

  9. ★★★ Open design. A journalist receives 5 CSV files (elections, demographics, funding, companies, press). Propose an architecture to cross-check and trace the numerical claims of an article: target schema, indexes, provenance, typical queries (reread ch. 10-11).

Projects and assessment

The fiche of this page quotes the assessment of the SynapseS sheet: “50% final written exam (paper documents allowed) · 50% three 30-minute tests in small classes starting mid-October”. The course unit is not in the LLGA 2026–2027 lists, so no LLGA assessment applies to it. Everything below is a proposal of these notes, for a student who takes the course in another track or works through the notes for reference: the written exam keeps the sheet’s format, and a project can replace or extend the three short tests.

The projects exercise mainly O7 (hands-on and industry practice: SQL, transactions, execution plans, benchmarks), O3 through the guarded text-to-SQL subject and O5 through the provenance-aware knowledge graph that a GraphRAG retriever queries. O4 (graph-structured data) enters with property graphs and RDF, and O8 (trustworthy AI) with provenance and the verification of published numbers.

Solo projects

  1. [Solo · ≈ 20 h] Build your own ANN index: a paper study of product quantization. How much recall do the approximate indexes of Def. 9.4 give up, for how much speed and memory, and why does the answer depend on the data?
    • Implement: in NumPy, exact k-NN, IVF with k-means cells, product quantization with asymmetric distances, and IVF-PQ, following H. Jégou, M. Douze and C. Schmid, “Product Quantization for Nearest Neighbor Search”, IEEE TPAMI, 2011. Compare with HNSW from hnswlib (Malkov and Yashunin, 2018) and, as an option, with pgvector inside PostgreSQL.
    • Data: SIFT1M from the TEXMEX corpus of the PQ paper (one million 128-dimensional descriptors, 10,000 queries, exact neighbours given); GloVe-100 as packaged by ANN-Benchmarks (Aumüller, Bernhardsson and Faithfull, Information Systems, 2020); isotropic Gaussian vectors of the same sizes as a control.
    • Success criterion: recall@10 plotted against queries per second and bytes per vector for every index, swept over n_{\text{probe}}, the number m of sub-vectors and the HNSW search width; the hand-written IVF-PQ within 0.03 recall@10 of FAISS IndexIVFPQ at equal parameters, or the gap explained; the Gaussian-versus-real gap of Example 9.5 measured and explained.
    • Deliverables: code with tests, a 4–6 page report, a 10-minute talk. Chapters 5 and 9 (Def. 9.4, Example 9.5, Ex. 6). The index is the one behind the dense retrievers of Large Language Models, ch. 7 (the ANN box of §7.2).
  2. [Solo · ≈ 20 h] A mini storage engine with crash recovery: a paper study of ARIES. Do a page file, an external merge sort, a B+ tree and a write-ahead log written from scratch reach the I/O counts and the recovery guarantee that chapters 4, 5 and 7 predict?
    • Implement: in Python, a page file with an I/O counter behind a small buffer pool (Def. 4.5); external merge sort (Theorem 4.3); a B+ tree with leaf and internal splits (Def. 5.1); a WAL with LSNs and an ARIES-style analysis, redo and undo with compensation records (Prop. 7.2); a switch that kills the process at random points.
    • Data and paper: the lineitem table of the TPC-H benchmark, generated with its dbgen tool at scale factor 0.1 (about 600,000 rows); C. Mohan, D. Haderle, B. Lindsay, H. Pirahesh and P. Schwarz, “ARIES: A Transaction Recovery Method Supporting Fine-Granularity Locking and Partial Rollbacks Using Write-Ahead Logging”, ACM TODS, 1992.
    • Success criterion: counted sort I/Os equal to the 2Np of Theorem 4.3 for several pairs (N, M); tree heights within the bound of Theorem 5.2; after at least 1,000 random crashes, some of them during recovery itself, the database holds exactly the committed transactions; the trace of Example 7.3 replayed step by step.
    • Deliverables: code with tests, a 4–6 page report, a 10-minute talk. Chapters 4, 5, 7.

Group projects

  1. [Group of 3 · ≈ 30 h each] Guarded text-to-SQL over a normalized schema. Can an LLM answer questions in natural language over a database that the team designed, without ever writing to it or running a query above a cost budget — and where does it still return a plausible wrong number?
    • Implement: a 3NF schema derived from functional dependencies (ch. 3); an LLM (a small open model run locally, or an API) prompted with the schema; a guard that runs every generated query in a read-only transaction (ch. 6) and rejects it when PostgreSQL’s EXPLAIN estimates a cost above a budget (ch. 8), or, on SQLite, which gives no cost estimate, when an instruction budget set with set_progress_handler runs out; an execution-feedback loop on errors.
    • Data: the dev split of BIRD (J. Li et al., NeurIPS 2023 Datasets and Benchmarks track), plus 60 hand-written questions over the team’s own schema that stress NULL logic, universal quantification with NOT EXISTS and window functions (ch. 2).
    • Work split: schema design and loading; prompting and the feedback loop; the guard, the hand-written test set and the error analysis with the taxonomy of From Boosting to Foundation Models, ch. 7.
    • Success criterion: execution accuracy (Def. 7.4 of that page) on both sets, with and without the guard and the feedback loop; no write ever reaches the database; every failure classified, with the silent wrong answers reported apart from the queries that fail with an error.
    • Deliverables: a repository, a 6-page report, a 15-minute talk with a live demonstration. Chapters 2, 3, 6, 8.
  2. [Group of 3 · ≈ 30 h each] Heterogeneous sources into a provenance-aware knowledge graph. In the spirit of ConnectionLens (ch. 11), can a team merge a CSV register, a JSON dump and a set of texts about one domain into a single graph whose every value is traced to its source, and answer multi-hop questions over it?
    • Implement: entity extraction from the texts with an off-the-shelf NER model; entity resolution with blocking and Jaccard similarity (Def. 10.2, Prop. 10.3); loading into RDF with one named graph per source, or into a property graph with a source property on every edge (Def. 9.3); SPARQL or Cypher queries; a GraphRAG retriever that hands the matched subgraph to an LLM.
    • Data: the French companies of one sector, from three sources: the Sirene register of INSEE (CSV, on data.gouv.fr), their Wikidata entities as JSON from the Wikidata Query Service, and their Wikipedia articles as text.
    • Work split: ingestion and extraction; entity resolution and the provenance model; queries, the GraphRAG retriever and the evaluation.
    • Success criterion: entity-resolution precision and recall against the SIREN numbers that Wikidata stores (property P1616), held out from the matching features; blocking recall reported (the share of true pairs that share a block); 30 hand-written multi-hop questions answered with the source of every value; GraphRAG compared with text-only retrieval on these questions.
    • Deliverables: a repository, a 6-page report, a 15-minute talk. Chapters 9, 10, 11 and Ex. 9. It prepares the GraphRAG chapter of Graph ML.
  3. [Group of 3 · ≈ 25 h each] Checking published statistics against open data. Extending Ex. 9: how often do numerical claims in news articles match the official data they cite, and when they do not, is the claim or the query wrong?
    • Implement: a set of 100 numerical claims taken from news articles (unemployment, population, election results); for each one, a SQL aggregation over open datasets loaded in a normalized schema; a provenance store with the claim, the query, the dataset version and the verdict; a classification of the failures (join duplication, NULL handling, wrong period or perimeter).
    • Data and paper: INSEE local statistics and the official French election results published on data.gouv.fr; O. Balalau et al., “Fact-checking Multidimensional Statistic Claims in French”, Truth and Trust Online (TTO), 2022 — the StatCheck line of ch. 11.
    • Work split: claim collection and annotation; loading, schema and the claim-to-query mapping; the provenance store, the verdicts and the failure analysis.
    • Success criterion: two members map each claim independently and their agreement (Cohen’s κ) is reported; every verdict re-runs from the stored query and dataset version; the causes of failure tallied, with the query errors separated from the false claims.
    • Deliverables: a repository, a 6-page report, a 15-minute talk. Chapters 2, 3, 10, 11.

Written exam (proposal)

Format: 2 hours, paper documents allowed (as on the sheet), no electronic device; four independent questions — a course question, two computations and a short proof.

  1. Question (5 points). Course question. State the WAL rule. Explain why a STEAL / NO-FORCE buffer policy needs both undo and redo, then describe the three passes of ARIES and say why its redo pass also replays the writes of uncommitted transactions.

    Expected:

    • WAL: the log record of a modification reaches the disk before the modified page (Def. 7.1).
    • STEAL lets an uncommitted page reach the disk, so recovery must undo it from the before-image; NO-FORCE lets a committed change live only in memory, so recovery must redo it from the after-image.
    • Analysis rebuilds the transaction table and the dirty page table from the last checkpoint; redo replays, in LSN order, every change newer than the LSN stored in its page; undo rolls back the unfinished transactions in reverse LSN order and writes CLRs (Prop. 7.2).
    • Repeating history restores the exact state at the crash, so undo works on consistent pages; page LSNs make redo idempotent, and CLRs make undo restartable after a second crash.
  2. Question (6 points). Computation. A relation R of B_R = 2000 pages is joined with S of B_S = 400 pages, with M = 41 buffer pages; use Theorem 4.3 and Def. 8.2, and ignore the cost of writing the result.

    (a) Give the number of initial runs, the number of passes and the I/O cost of sorting R. (b) Give the cost of block nested loops with each relation as the outer one. (c) Give the cost of sort-merge and of the hash join, and say which plan the optimizer keeps.

    Expected:

    • (a) \lceil 2000/41\rceil = 49 runs; the fan-in is 40 < 49, so two merge passes and p = 3; cost 2 \times 2000 \times 3 = 12\,000 pages.
    • (b) S outer: 400 + \lceil 400/40\rceil \times 2000 = 20\,400; R outer: 2000 + 50 \times 400 = 22\,000. The smaller relation goes outside.
    • (c) Sorting S: 10 runs, one merge pass, cost 1,600. Sort-merge: 12\,000 + 1\,600 + 2\,400 = 16\,000. Hash join: 3 \times (2000 + 400) = 7\,200, feasible since 400 pages split into 40 partitions of about 10 pages, each fitting in memory. The optimizer keeps the hash join.
  3. Question (5 points). Computation. The table Emp(id, dept, salary) holds (1, ‘A’, 3000), (2, ‘A’, NULL), (3, ‘B’, 2000) and (4, NULL, 2500). (a) What does SELECT COUNT(*), COUNT(salary), AVG(salary) FROM Emp return? (b) How many rows does SELECT * FROM Emp WHERE dept <> 'A' return, and why? (c) Write with NOT EXISTS the departments in which every employee earns at least 2500, and give the answer on this instance.

    Expected:

    • (a) 4, 3 and 2500: COUNT(*) counts rows, the other aggregates ignore the NULL (Def. 2.5).

    • (b) One row, employee 3: for employee 4 the comparison NULL <> 'A' is UNKNOWN, and WHERE keeps only the TRUE rows.

    • (c) With the double negation of Prop. 2.2:

      SELECT DISTINCT d.dept FROM Emp d
      WHERE d.dept IS NOT NULL AND NOT EXISTS (
        SELECT 1 FROM Emp e WHERE e.dept = d.dept AND NOT (e.salary >= 2500));

      It returns A only. B fails through employee 3; the NULL salary of employee 2 makes NOT (e.salary >= 2500) UNKNOWN, so it is not a counterexample. Writing the last condition of the subquery as (NOT (e.salary >= 2500) OR e.salary IS NULL) would treat the unknown salary as a counterexample and exclude A as well.

  4. Question (4 points). Short proof. Using only Armstrong’s axioms (Theorem 3.2), prove (a) the union rule: if X \to Y and X \to Z then X \to YZ; (b) the pseudo-transitivity rule: if X \to Y and WY \to Z then WX \to Z.

    Expected:

    • (a) Augmenting X \to Y by X gives X \to XY, since XX = X.
    • Augmenting X \to Z by Y gives XY \to YZ; transitivity on the two results gives X \to YZ.
    • (b) Augmenting X \to Y by W gives WX \to WY; transitivity with WY \to Z gives WX \to Z.

Grading rubric (proposal)

Criterion Weight What earns the marks
Correctness and tests 30 % Results checked against a reference (FAISS, PostgreSQL, gold queries, held-out identifiers); unit and edge-case tests, including NULLs, cyclic data and crashes
Cost reasoning and measurement 25 % I/O, latency, recall or query cost predicted with chapters 4–9, then measured over several sizes and compared with the prediction
Data quality and provenance 15 % Every reported number traced to its source, query and dataset version; failure cases classified, not only counted
Reproducibility 15 % Fixed seeds, pinned dependencies, scripts that rebuild the database and rerun every figure from a fresh clone
Communication 15 % Report within the page limit, readable plots with units, a talk that fits its time slot

References