Outgrowing the Sheet: SQL & Databases
You hit one of the walls from Phase 1 - the file got slow, the IDs got mangled, or three "final" versions disagreed. That's not a failure. It's the signal that your data has grown up and needs a home built for the job. That home is a database, and the language you use to talk to it is SQL.
Here's the reassuring part: a database isn't a strange new world, it's a spreadsheet's ideas made sturdy. If you understand a sheet, you're most of the way to understanding a table.
The mental model: a table is a sheet that keeps its promises
📝 Terminology. A database is an organized store of data, managed by software built to keep it correct and fast. A table is one collection of data inside it - rows and columns, like a single spreadsheet tab. SQL (Structured Query Language) is how you ask the database to fetch, filter, or change data.
The shapes map almost one-to-one:
SPREADSHEET DATABASE
─────────── ────────
a sheet/tab → a table
a row → a row (one record: one order, one user)
a column → a column (but with a DECLARED type)
a cell → a value
a formula / VLOOKUP → a SQL query (asked of the whole table)
What just happened: The pieces are the same. The difference is everything around them. A table declares what each column holds and refuses anything that doesn't fit, the database is one shared store instead of a file you copy, and SQL lets you ask questions across millions of rows without dragging a formula anywhere. Each of those maps to a Phase 1 pain.
One shared source of truth
In Phase 1, files multiplied until nobody knew which number was real. A database fixes this at the root: there is one table, living in one place, and everyone connects to that same table. There's no copy in someone's Downloads folder, because there are no copies - only connections.
Spreadsheet world: Database world:
you ── budget_v2.xlsx you ─┐
me ── budget_FINAL.xlsx me ─┼─→ ONE orders table
her ── budget_USE_THIS.xlsx her ─┘ (everyone sees the same rows)
What just happened: The "which version is current?" question disappears, because there's only ever one
version. Read the orders table and you're reading the live, current data - the same data your teammate
sees at the same moment. Next time two reports disagree, there's no afternoon spent hunting for the
"right" file - there's one table, and the answer is whatever it says right now.
Real types and constraints
In Phase 1, a "date" column was a mix of real dates and look-alike text, and IDs got auto-mangled. A database makes you declare each column's type up front - and then it enforces that promise on every single row, forever.
CREATE TABLE orders (
id INTEGER, ← whole numbers only
customer TEXT, ← any text
amount DECIMAL, ← money, kept exact
ordered_at DATE ← real dates only; "March 1" with no year is rejected
);
What just happened: You told the database the rules once. Now if anything tries to put the word "soon"
into ordered_at, the database refuses it instead of silently storing text that looks like a date. The
guard rail the spreadsheet never had is now built into the column itself.
📝 Terminology. A constraint is a rule the database enforces on a column - for example, "this
value can't be empty," or "every id must be unique." It's the database catching bad data at the door,
rather than you finding it three weeks later in a broken report. The whole class of bug where one stray
text cell silently breaks a SUM, or a part number loses its leading zeros, is simply gone - the wrong
shape isn't allowed in.
Querying millions of rows
In Phase 1, the sheet slowed to a crawl and then hit a hard row ceiling. Databases are built for the
opposite: engineered to filter, sort, and summarize enormous tables quickly, because that's their entire
job. You don't drag a formula down a million rows - you write one sentence describing what you want, and
the database figures out how to get it fast. A SELECT query is you saying "give me these columns, from
this table, where these conditions hold."
SELECT customer, amount
FROM orders
WHERE amount > 100;
customer amount
──────── ──────
Ada 250.00
Grace 140.00
Lin 199.50
What just happened: You asked one question - "show me the customer and amount for every order over
100" - and the database scanned the whole orders table and handed back only the matching rows. It does
the same whether the table has a hundred rows or a hundred million, and it doesn't melt your laptop doing
it. "The file is too big to open" stops being a sentence you say - bigger data is just a longer scan, not
a wall.
The grammar of
SELECT … FROM … WHERE …is the single most useful thing to learn here, and it has its own guide: Querying Basics: SELECT & WHERE. If this is your first SQL query, read that next.
Multiple users, safely
A shared spreadsheet lets several people edit at once, but resolves clashes crudely - often the last save wins, quietly erasing someone else's change. Databases are designed from the start for many people and programs reading and writing at once without trampling each other.
📝 Terminology. A transaction is a group of changes the database treats as all-or-nothing: either every step succeeds, or none of them do. It's how a database moves money from one account to another without ever leaving it half-moved - even if two people press "go" at the same instant.
What just happened: Where the spreadsheet's answer to "two people, same data" was "hope for the best," the database's answer is a set of rules that keep the data correct no matter how many people are touching it - the difference between a tool for one person and a tool for a team. As soon as more than one person, or one automated script, depends on your data, you need it to stay correct under simultaneous use. The database gives you that for free; the spreadsheet never could.
A worked move: the same task, leveled up
Picture the Phase 1 ritual: every Monday, opening a sheet of this week's orders and, by hand, filtering to the big ones and eyeballing the total. In a database, that becomes a question you ask:
SELECT customer, SUM(amount) AS total
FROM orders
WHERE ordered_at >= '2026-06-15'
GROUP BY customer;
customer total
──────── ──────
Ada 490.00
Grace 140.00
What just happened: One query did the filtering and the per-customer totaling in a single step - no dragging, no manual cleanup, reading the same live table everyone else does. Same result you used to assemble by hand, now one repeatable sentence.
Try it yourself. Same idea on a tiny library dataset - join two tables and count, the kind of
question that would mean a fragile VLOOKUP in a sheet. Run it and tweak the WHERE:
SELECT a.name AS author, a.country, COUNT(b.id) AS books
FROM authors a
JOIN books b ON b.author_id = a.id
GROUP BY a.id
ORDER BY books DESC;
And that word - repeatable - is the bridge to Phase 3. You've fixed size, types, and the source of truth, but you're still the one running this query every Monday. What happens when even that should run on its own?
Recap
- A table is a spreadsheet's ideas made sturdy: rows and columns, but with declared, enforced types.
- A database gives you one shared source of truth - connections, not copies - so versions stop multiplying.
- Types and constraints catch bad data at the door, killing the "stray text broke my SUM" class of bug.
- Databases query millions of rows fast, and SQL (
SELECT … FROM … WHERE …) is how you ask. - They handle many users safely, with transactions keeping data correct under simultaneous use.
You can now hold a clean, shared, queryable source of truth. Next: what to do when the querying itself has to happen without you.
← Phase 1: Where Everyone Starts · Guide overview · Phase 3: When It Has to Run Itself →
Before the quiz: without looking back, say (or jot down) the core idea of this phase in your own words.
Check your understanding 3 questions
1. How does the guide describe a database table relative to a spreadsheet?
2. What does declaring a column's type buy you?
3. What problem does moving from spreadsheet files to a database solve about versions?