Technopedia

The technologist’s encyclopedia

Schema

A relational schema defines the static structure of a database: which tables exist, what columns they have, and how they relate. It is the foundation on which all queries and transactions operate. Designing a good schema is the first step in any persistent-data system: it determines storage cost, query performance, and the integrity guarantees the database can enforce.

Authoritative sources

Tables and attributes

A table (relation) RR is a set of tuples (rows), each with the same set of typed attributes (columns). (In SQL, tables are bags/multisets that allow duplicate rows; see Relational Algebra.) The schema of RR is the list of attribute names and their domains.

Data types

Each attribute is declared with a data type (domain) that constrains the values it can hold. The choice of type affects storage size, index efficiency, and query semantics:

CategoryExamplesDesign consideration
IntegersSMALLINT, INTEGER, BIGINTMatch the expected range; BIGINT costs 8 bytes vs 4 for INTEGER
Fixed-point / exactNUMERIC(p,s), DECIMALUse for money; avoids floating-point rounding
Floating-pointREAL, DOUBLE PRECISIONUse for measurements; never for exact arithmetic
StringsCHAR(n), VARCHAR(n), TEXTCHAR pads to n; VARCHAR stores actual length; TEXT has no limit
TemporalDATE, TIME, TIMESTAMPDATE = calendar day; TIMESTAMP = instant (with optional timezone)
BinaryBLOB, BYTEAUnstructured payloads; not indexable in most engines

A common rule: choose the narrowest type that safely covers the domain. An INTEGER (4 bytes) is preferable to BIGINT (8 bytes) when values fit, because smaller types pack more rows per page and compare faster.

Details

Referential integrity: if R.fkR.fk references S.pkS.pk, then every value of R.fkR.fk must either be NULL or match some S.pkS.pk value. Deleting a row from SS that is referenced by RR is rejected (or cascaded, depending on the ON DELETE clause).

Composite keys: a primary key can span multiple attributes, e.g., (studentid,courseid)(student_id, course_id) for an enrollment table. The whole tuple must be unique; no single attribute need be.

Surrogate keys: in practice, databases often add an auto-incrementing integer idid as the PKPK even when a natural key exists, to keep indexes small and stable.

Type choice in practice:

Example problem

A hospital stores patient records. A patient’s age is at most 150, their diagnosis code is a 5-character string, and their admission timestamp must be comparable across servers in different timezones. Which types do you choose?

Solution
  • Age: SMALLINT (2 bytes, range −32768 to 32767, more than enough for 0–150).
  • Diagnosis code: CHAR(5) (fixed-length, always exactly 5 characters, compact for indexing).
  • Admission: TIMESTAMP WITH TIME ZONE (stores an absolute instant; displays in the viewer’s local timezone).

Entity-Relationship modeling

Before writing a relational schema, designers typically model the domain with an Entity-Relationship (ER) diagram. An ER diagram captures:

The ER diagram is a design tool: it is not stored in the database. The next step is mapping the ER model to relational tables.

Mapping rules

ER elementRelational mapping
Entity with keyOne table; key becomes the PK
1:N relationshipFK in the "many" table pointing to the "one" table
M:N relationshipA separate junction table with FKs to both entities; the pair of FKs forms the PK
Relationship with its own attributesThe junction table also carries those attributes
Details

Step 1 – Identify entities and keys. Walk through the domain description and list every distinct “thing” that has its own identity. For each, pick a natural key or decide on a surrogate.

Step 2 – Identify relationships and cardinality. For every pair of entities that interact, determine whether the relationship is 1:1, 1:N, or M:N. A common test: can one instance of A relate to multiple instances of B? Can one instance of B relate to multiple instances of A?

Step 3 – Map to tables. Apply the mapping rules above. Every M:N relationship becomes a junction table. Every 1:N relationship adds a foreign key to the “many” side.

Step 4 – Add relationship attributes. If the relationship itself has data (e.g., a loan has a date), those attributes go in the junction table.

Example: Library system.

The ER diagram for this example:

Book book_id title, author isbn Member member_id name, email Loan loan_id book_id (FK) member_id (FK) borrowed_at, returned_at 1 : N 1 : N

Mapping result:

books(book_id PK, title, author, isbn)
members(member_id PK, name, email)
loans(loan_id PK, book_id FK→books, member_id FK→members, borrowed_at, returned_at)

Authoritative sources

Example problem

Model a university: a Student can enroll in many Courses; a Course can have many Students; each enrollment records a grade. What tables do you create?

Solution
  • Entities: Student (key: student_id), Course (key: course_id).
  • Relationship: Student ↔ Course is M:N, with attribute grade.
  • Mapping: three tables.
students(student_id PK, name, email)
courses(course_id PK, title, credits)
enrollments(student_id FK→students, course_id FK→courses, grade,
           PK(student_id, course_id))

The composite key (student_id, course_id) enforces that a student cannot enroll in the same course twice.

Normal forms

Normal forms are canonical forms that eliminate redundancy and update anomalies:

Details

Why normalize? Redundancy causes update anomalies:

  • Insert anomaly: cannot add a fact without also adding unrelated data.
  • Update anomaly: changing a department name in one row but not another creates inconsistency.
  • Delete anomaly: removing a row loses unrelated information.

Normal forms eliminate these by ensuring each fact is stored in exactly one place.

Functional dependencies: a dependency X→YX \to Y holds if two tuples that agree on XX must also agree on YY. The closure X+X^+ (all attributes determined by XX) is computed by the Armstrong axioms (reflexivity, augmentation, transitivity).

The following diagram shows a transitive dependency that violates 3NF:

X (PK) Y Z X → Y Y → Z Transitive: X → Z (violates 3NF)

Concrete example of a 2NF violation:

Consider a table enrollments(student_id, course_id, course_title, grade) with PK (student_id, course_id).

  • course_title depends only on course_id (a part of the PK) → partial dependency → violates 2NF.
  • Fix: split into courses(course_id PK, course_title) and enrollments(student_id, course_id FK, grade).

Denormalization: in practice, read-heavy systems (OLAP, reporting) denormalize (join tables, add redundant columns) to reduce join cost. The trade-off is write amplification and update anomalies. A common pattern: keep the OLTP schema normalized, and build a denormalized read replica or materialized view for analytics.

Authoritative sources

Example problem

A table orders(order_id, customer_id, customer_city, product_id, product_price, quantity) has PK order_id. Identify all normal-form violations and propose a fix.

Solution
  • customer_city depends on customer_id, not on order_id → transitive dependency → violates 3NF.
  • product_price depends on product_id, not on order_id → transitive dependency → violates 3NF.

Fix (3NF):

customers(customer_id PK, customer_city)
products(product_id PK, product_price)
orders(order_id PK, customer_id FK, product_id FK, quantity)

Indexes

For how B-trees and hash tables work internally, see Key-Value Stores. For how the optimizer chooses to use an index at query time, see SQL & Query Planning.

An index is a secondary data structure that accelerates lookup by a column (or set of columns).

  • B-tree index on column cc: O(log⁡n)O(\log n) lookup by cc, supports range queries (c>5c > 5, cBETWEEN3AND7c BETWEEN 3 AND 7).
  • Hash index on column cc: O(1)O(1) expected equality lookup only.
  • Composite index on (c1,c2,…)(c_1, c_2, \ldots): ordered by c1c_1, then c2c_2, etc. Supports prefix queries (c1=5c_1 = 5), but not c2c_2 alone.
  • Covering index: includes all columns needed by a query, avoiding a table lookup (“index-only scan”).
Details

Write overhead: every INSERT, UPDATE, or DELETE must also update every index on the affected columns. A table with 5 indexes pays 5× the index-maintenance cost on writes. For write-heavy workloads, LSM-tree indexes (see Key-Value Stores) batch writes and merge in the background.

Index selection heuristics:

  • Equality predicates on the leading column benefit most.
  • Range predicates on the leading column are supported by B-trees but not hash indexes.
  • A composite index $(a, b)$ serves queries on aa alone or (a,b)(a, b), but not bb alone.
  • The leftmost prefix rule: the index can only be used if the query constrains the leading column(s) with equality (or a range on the last constrained column).

Partial indexes: an index on a subset of rows (e.g., WHERE status = 'active') is smaller and faster to maintain, at the cost of not serving queries on the excluded rows.

B-tree structure (simplified, order-3):

[50 | 75] [10 | 30] [55 | 65] [80 | 95] [5, 10] [20, 30] [40, 50] [55, 60] [65, 70] [80, 90] [95] Root → internal nodes → leaf pages (each leaf holds a key + row pointer)

Authoritative sources

Example problem

A table events(event_id, user_id, created_at, status) is queried with:

  1. WHERE user_id = 42 AND created_at > '2025-01-01'
  2. WHERE status = 'pending'

Which composite index serves query 1 efficiently? Can the same index serve query 2?

Solution
  • Query 1: a composite B-tree index on (user_id, created_at) works. The equality on user_id narrows to a range of created_at values within that user’s slice.
  • Query 2: the same index does not help, because status is not the leading column. A separate index on status (or a partial index WHERE status = 'pending') is needed.

Interactive normalization checker

The simulation below lets you drag attributes between tables and see which normal form is violated. Try moving dept_name into the employees table to observe a transitive dependency (3NF violation), or split a composite key to see a partial dependency (2NF violation).

Quizz

Example problem

Problem: Design a schema for a library system with books, members, and loans. A book can be loaned to at most one member at a time. A member can have many loans (over time). Each loan records the date borrowed and the date returned.

First, sketch the ER diagram, then map it to relational tables, then verify the result is in 3NF.

Solution

ER diagram:

Book book_id title, author, isbn Member member_id name, email Loan loan_id book_id, member_id borrowed_at, returned_at 1 : N 1 : N

Relational schema:

books(book_id PK, title, author, isbn)
members(member_id PK, name, email)
loans(loan_id PK, book_id FK→books, member_id FK→members, borrowed_at, returned_at)

Why this is 3NF:

  • books: title and author depend on book_id (the PK). No transitive dependency.
  • members: name and email depend on member_id. No transitive dependency.
  • loans: all non-key attributes depend on loan_id. The composite (bookid,memberid,borrowedat)(book_id, member_id, borrowed_at) could serve as an alternative PK, but a surrogate loan_id keeps the PK small.

Constraint: “at most one active loan per book” is enforced by a partial unique index: UNIQUE(book_id) WHERE returned_at IS NULL.

Index: a B-tree on loans(book_id) supports the query “is this book currently available?” in O(log⁡n)O(\log n). A B-tree on loans(member_id) supports “what does this member currently have?”.