A relational database is a system that stores data in tables — rows and columns — and connects those tables through shared keys, so every fact is recorded exactly once and questions spanning many facts can be answered with one query. That one design decision is why the same model has run banks, shops and hospitals for forty years.
What actually makes a database relational?
Two properties work together. First, the data lives in tables: a named grid where every row is one real-world thing and every column is one attribute of it. Second, tables reference each other by key instead of copying each other's details. A row in a catalogue table does not repeat the full description of its category; it stores the category's identifier, and the engine can pull the matching row from the categories table on demand. That is the relational idea, and everything from the humble shop catalogue to a hospital patient record uses it.
Because the shape of the data is agreed in advance and enforced by the engine itself, a single mis-typed value either fails or surfaces immediately — the small store we will practise with in this course stores its catalogue this way:
CREATE TABLE categories (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL
);
CREATE TABLE catalogue_items (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
category INTEGER REFERENCES categories(id),
price REAL,
stock INTEGER
);
INSERT INTO categories (id, name) VALUES
(1, 'Hand tools'),
(2, 'Power tools'),
(3, 'Fasteners');
INSERT INTO catalogue_items (id, name, category, price, stock) VALUES
(1, 'Claw hammer', 1, 21.50, 34),
(2, 'Cordless drill', 2, 149.00, 12),
(3, 'Phillips screwdriver', 1, 9.75, 67),
(4, 'Wood screw box', 3, 12.40, 120),
(5, 'Circular saw', 2, 89.99, 8);
Read that out loud and the vocabulary falls into place: catalogue_items is a table, each INSERT adds one row, price and stock are columns, and category is a foreign key pointing at categories. SQL is the language you use to describe and query all of it.
What do the rows and columns actually look like in a query?
Ask the database for its entire catalogue table and it answers with the same grid you declared — which is the point: what you see in a query result is what physically lives in the storage layer.
SELECT * FROM catalogue_items;
id name category price stock
-- -------------------- -------- ----- -----
1 Claw hammer 1 21.5 34
2 Cordless drill 2 149.0 12
3 Phillips screwdriver 1 9.75 67
4 Wood screw box 3 12.4 120
5 Circular saw 2 89.99 8
The * means every column, in the order the table defines them. You rarely ship a SELECT * in production code, but as an instant truth-check on what a table contains it has no equal. One step up, a predicate picks out only the rows that match — and the engine returns exactly those:
SELECT name, price, stock FROM catalogue_items WHERE price > 50;
name price stock
-------------- ----- -----
Cordless drill 149.0 12
Circular saw 89.99 8
Notice what did not have to happen anywhere in that comparison: category names were never duplicated onto product rows, so no query can ever return a power tool that claims to be a fastener.
Why not just keep all of this in one big spreadsheet?
A spreadsheet can hold the same five rows, and for a week of running a shop that is fine. The pressure arrives with volume and repetition. Put the human-readable category name on every product row, as a spreadsheet-minded export usually does, and the moment someone renames Fasteners to Fixings and fasteners you have to hunt down 120 rows up and hope you caught them all. Databases make the correction trivial: one row in categories changes, every product inherits the new name automatically.
SELECT catalogue_items.name, categories.name AS category_name
FROM catalogue_items JOIN categories ON categories.id = catalogue_items.category
ORDER BY catalogue_items.id;
name category_name
-------------------- -------------
Claw hammer Hand tools
Cordless drill Power tools
Phillips screwdriver Hand tools
Wood screw box Fasteners
Circular saw Power tools
This single query already does things a flat sheet cannot express: it fetched a value stored in a different table, matched rows by key, and sorted without you touching anything. Module 5 spends six whole lessons on joins because this fetch-by-key move is the beating heart of reporting; here it is only a taste of the payoff.
Spreadsheets also fall apart on a second axis — concurrent writers. A relational engine serialises writes honourably: one transaction per row, no lock file left dangling when a laptop closes a document mid-save. When two people price-check the same item at once, the engine guarantees both see a coherent state.
Which engines count as relational databases?
SQL is a standard, and products differ mainly in scale, licensing and dialect. For practising, any of the first two is enough; at work you will meet the rest.
| Engine | Learner fit | Where you meet it |
|---|---|---|
| SQLite | Best first sandbox — zero installation, one file | Phones, browsers, apps that ship without a server |
| PostgreSQL | Server-based, strict standards-minded, free | Web backends, analytics pipelines |
| MySQL / MariaDB | Server-based, huge hosting ecosystem, free | CMS and commerce stacks |
| SQL Server / Oracle | Commercial engines with enterprise tooling | Banks, ERP suites, government systems |
The lessons in this course run their examples on SQLite so that every statement is checkable on your own machine in seconds, and they flag the moments where MySQL, PostgreSQL or SQL Server spell a feature differently. MySQL, for example, writes the auto-numbering column as AUTO_INCREMENT where SQLite says AUTOINCREMENT — difference in keywords, not in the idea.
Quick reference
- A relational database keeps data in tables whose rows are values plus identifiers, and links tables through keys rather than copies.
- SQL is how you create tables, insert rows, and question those tables; the results come back as tables too.
- One fact in one place — the relational design means renaming a category can never leave stale copies behind.
- Engines differ in dialect details, not in the model; skills transfer from SQLite to PostgreSQL to SQL Server with modest friction.
Try it yourself: recreate the two tables above in your own practice database, insert a sixth catalogue item of your choosing, then write one query that lists every product together with its category name. Check whether your new row shows a category you actually inserted — if it shows nothing, look again at what the foreign key holds.