{"course":{"slug":"data-and-sql","title":"Data, SQL and Getting to the Answer","url":"https://addaly.com/learn/data-and-sql","level":"beginner","track":"build"},"lesson":{"slug":"what-a-database-is","title":"When a spreadsheet stops working","url":"https://addaly.com/learn/data-and-sql/what-a-database-is","position":1,"of":66,"minutes":7,"answer":"A database stores each fact once and enforces rules; a spreadsheet stores copies and enforces nothing.","keyIdea":"A database stores each fact once and enforces rules; a spreadsheet stores copies and enforces nothing."},"body":{"format":"markdown","text":"## The sheet that worked for two years\n\nRukmini runs a tuition centre in Hyderabad. She keeps one spreadsheet: one row per payment, with the student's name, phone number, class, month, amount and payment mode. For two years and about 3,000 rows it is fine. Then four things happen in the same month.\n\nA student's family changes their phone number. That number appears in 34 rows. Rukmini fixes 31 of them.\n\nHer assistant types \"Class 9\" in one row where everyone else has typed \"Grade 9\". The class-wise total is now wrong by eleven students, and nothing looks broken.\n\nThe file takes forty seconds to open. Two people edit it at the same time and one of them loses a morning's work.\n\nA payment gets entered for a student who left last year. Nothing stops it. Nothing even notices.\n\nOnly one of these is a size problem. The other three are the same problem wearing different clothes: **the same fact is stored in many places, and nothing forces the copies to agree.**\n\n## One kind of thing per table\n\nA database splits that sheet into tables. A table holds one kind of thing, one row per thing, with named columns that have declared types.\n\n> Figure: The same four problems, in a sheet and in two tables. One sheet, one row per payment: The phone number appears in 34 rows. Thirty-one of them get corrected; One row says Class 9 where the rest say Grade 9, and the class-wise total is quietly wrong; A payment is entered for a student who left last year, and nothing notices; Two people edit at once and one of them loses a morning. students and payments, two tables: The phone number lives in one row. Change it once and every payment follows; class_level is an int, so Grade 9 is refused at the moment it is typed; REFERENCES students(id) rejects a payment for a student who does not exist; Forty people write at once and nobody overwrites anybody. Only the fourth of these is a size problem. The other three are one problem (the same fact stored in several places, with nothing forcing the copies to agree) and the right-hand column removes it by storing each fact once and refusing the rows that break a rule.\n\n```sql\nCREATE TABLE students (\n  id          bigserial     PRIMARY KEY,\n  full_name   text          NOT NULL,\n  phone       text,\n  class_level int           NOT NULL,\n  joined_on   date          NOT NULL\n);\n\nCREATE TABLE payments (\n  id          bigserial     PRIMARY KEY,\n  student_id  bigint        NOT NULL REFERENCES students(id),\n  paid_on     date          NOT NULL,\n  amount      numeric(10,2) NOT NULL,\n  method      text          NOT NULL\n);\n```\n\nRead what each line actually buys.\n\n`PRIMARY KEY` means every student has one permanent id. Names change, spellings vary, two families are both called Reddy. The id is the thing the rest of the system points at.\n\nThe phone number now lives in exactly one row. Change it once and every payment ever made is instantly attached to the new number, because payments never stored a phone number at all.\n\n`NOT NULL` means the database refuses a student with no name. Not a warning. A refusal.\n\n`REFERENCES students(id)` means a payment for student 9,999 who does not exist is rejected. This is the rule the spreadsheet could never enforce, and it is the reason databases stay trustworthy while spreadsheets rot.\n\n`numeric(10,2)` rather than a floating-point type, because money in binary floating point drifts. Rupees, naira and pesos all deserve exact arithmetic.\n\n`int` on `class_level` means \"Grade 9\" typed into that column is an error, immediately, at the moment someone types it. Not a silent wrong total three months later.\n\n## What you get that a sheet cannot give you\n\n**Types and constraints.** Bad data is rejected at the door instead of being cleaned up later. Later never comes.\n\n**Transactions.** A refund that moves money out of one row and into another either fully happens or fully does not. There is no state where half of it landed.\n\n**Concurrency.** Forty people can write at once. Nobody overwrites anybody.\n\n**Scale.** Ten million rows, answered in milliseconds, with an index. You will see how in a later lesson.\n\n**A query language.** This is the big one. A spreadsheet answers the questions you designed it to answer. A database answers questions you had not thought of when you built it. \"Which students who joined before June have paid every month since?\" is one query against the tables above and impossible against the sheet.\n\n## The honest part\n\nA spreadsheet is not a failure mode. Under a few thousand rows, with one person writing and a throwaway question to answer, a spreadsheet is faster than a database and you should use it. Anyone who tells you otherwise is selling something.\n\nThe moment to move is specific: when more than one person writes, or when the same fact appears in two rows, or when you need the data to still be trustworthy in a year. Rukmini crossed all three lines in one month and did not notice any of them, because crossing them does not produce an error message. It produces a number that is wrong."},"check":{"question":"A small shop keeps one sheet with a row per sale, including the customer's name, phone and full address on every row. A regular customer moves house. What is the real problem this exposes?","options":[{"text":"Her address now sits in many rows and nothing forces those rows to agree with each other","correct":true},{"text":"The sheet will eventually grow too large to open, which is the point at which a database becomes necessary","correct":false,"tempting":"Treats file size as the reason to move off a spreadsheet, when size is the symptom that shows up last."},{"text":"Addresses are being stored as free text when they should be structured into separate fields","correct":false,"tempting":"Assumes the flaw is in how the value is formatted rather than in how many times it is stored."},{"text":"Each sale has no unique identifier, so there is no way to tell two sales apart","correct":false,"tempting":"Reaches for the missing primary key, which is a real gap but not what breaks when the address changes."}]},"prev":null,"next":{"slug":"a-place-to-practise","title":"Getting a database onto the machine you have","url":"https://addaly.com/learn/data-and-sql/a-place-to-practise"},"authorship":"Founder and series editor: Dr Mustafa Mun. Written by AI models to a written standard. Exam keys checked blind by a second model: 3,665 of 3,666 agreed. Academic review invited.","review":{"status":"Draft, open for academic review"},"cite":"Addaly (2026) 'When a spreadsheet stops working', in Data, SQL and Getting to the Answer. Edited by M. Mun. Addaly. Available at: https://addaly.com/learn/data-and-sql/what-a-database-is (Accessed: 31 October 2026).","licence":{"name":"CC BY-NC-SA 4.0","url":"https://creativecommons.org/licenses/by-nc-sa/4.0/"},"reader":{"schema":1,"title":"When a spreadsheet stops working","courseTitle":"Data, SQL and Getting to the Answer","module":{"index":1,"slug":"tables-types-and-the-question","title":"Tables, types and the question you are actually asking"},"answer":null,"keyIdea":"A database stores each fact once and enforces rules; a spreadsheet stores copies and enforces nothing.","hero":{"kind":"figure","figure":{"t":"figure","label":"Figure 1.1","figure":{"left":{"title":"One sheet, one row per payment","items":["The phone number appears in 34 rows. Thirty-one of them get corrected","One row says Class 9 where the rest say Grade 9, and the class-wise total is quietly wrong","A payment is entered for a student who left last year, and nothing notices","Two people edit at once and one of them loses a morning"]},"type":"compare","right":{"title":"students and payments, two tables","items":["The phone number lives in one row. Change it once and every payment follows","class_level is an int, so Grade 9 is refused at the moment it is typed","REFERENCES students(id) rejects a payment for a student who does not exist","Forty people write at once and nobody overwrites anybody"]},"title":"The same four problems, in a sheet and in two tables","caption":"Only the fourth of these is a size problem. The other three are one problem (the same fact stored in several places, with nothing forcing the copies to agree) and the right-hand column removes it by storing each fact once and refusing the rows that break a rule."},"alt":"Figure: The same four problems, in a sheet and in two tables. One sheet, one row per payment: The phone number appears in 34 rows. Thirty-one of them get corrected; One row says Class 9 where the rest say Grade 9, and the class-wise total is quietly wrong; A payment is entered for a student who left last year, and nothing notices; Two people edit at once and one of them loses a morning. students and payments, two tables: The phone number lives in one row. Change it once and every payment follows; class_level is an int, so Grade 9 is refused at the moment it is typed; REFERENCES students(id) rejects a payment for a student who does not exist; Forty people write at once and nobody overwrites anybody. Only the fourth of these is a size problem. The other three are one problem (the same fact stored in several places, with nothing forcing the copies to agree) and the right-hand column removes it by storing each fact once and refusing the rows that break a rule."}},"blocks":[{"t":"h2","id":"the-sheet-that-worked-for-two-years","runs":[{"text":"The sheet that worked for two years"}]},{"t":"p","runs":[{"text":"Rukmini runs a tuition centre in Hyderabad. She keeps one spreadsheet: one row per payment, with the student’s name, phone number, class, month, amount and payment mode. For two years and about 3,000 rows it is fine. Then four things happen in the same month."}]},{"t":"p","runs":[{"text":"A student’s family changes their phone number. That number appears in 34 rows. Rukmini fixes 31 of them."}]},{"t":"p","runs":[{"text":"Her assistant types “Class 9” in one row where everyone else has typed “Grade 9”. The class-wise total is now wrong by eleven students, and nothing looks broken."}]},{"t":"p","runs":[{"text":"The file takes forty seconds to open. Two people edit it at the same time and one of them loses a morning’s work."}]},{"t":"p","runs":[{"text":"A payment gets entered for a student who left last year. Nothing stops it. Nothing even notices."}]},{"t":"p","runs":[{"text":"Only one of these is a size problem. The other three are the same problem wearing different clothes: "},{"text":"the same fact is stored in many places, and nothing forces the copies to agree.","b":true}]},{"t":"h2","id":"one-kind-of-thing-per-table","runs":[{"text":"One kind of thing per table"}]},{"t":"p","runs":[{"text":"A database splits that sheet into tables. A table holds one kind of thing, one row per thing, with named columns that have declared types."}]},{"t":"code","lang":"sql","label":"SQL","text":"CREATE TABLE students (\n  id          bigserial     PRIMARY KEY,\n  full_name   text          NOT NULL,\n  phone       text,\n  class_level int           NOT NULL,\n  joined_on   date          NOT NULL\n);\n\nCREATE TABLE payments (\n  id          bigserial     PRIMARY KEY,\n  student_id  bigint        NOT NULL REFERENCES students(id),\n  paid_on     date          NOT NULL,\n  amount      numeric(10,2) NOT NULL,\n  method      text          NOT NULL\n);"},{"t":"p","runs":[{"text":"Read what each line actually buys."}]},{"t":"p","runs":[{"text":"PRIMARY KEY","code":true},{"text":" means every student has one permanent id. Names change, spellings vary, two families are both called Reddy. The id is the thing the rest of the system points at."}]},{"t":"p","runs":[{"text":"The phone number now lives in exactly one row. Change it once and every payment ever made is instantly attached to the new number, because payments never stored a phone number at all."}]},{"t":"p","runs":[{"text":"NOT NULL","code":true},{"text":" means the database refuses a student with no name. Not a warning. A refusal."}]},{"t":"p","runs":[{"text":"REFERENCES students(id)","code":true},{"text":" means a payment for student 9,999 who does not exist is rejected. This is the rule the spreadsheet could never enforce, and it is the reason databases stay trustworthy while spreadsheets rot."}]},{"t":"p","runs":[{"text":"numeric(10,2)","code":true},{"text":" rather than a floating-point type, because money in binary floating point drifts. Rupees, naira and pesos all deserve exact arithmetic."}]},{"t":"p","runs":[{"text":"int","code":true},{"text":" on "},{"text":"class_level","code":true},{"text":" means “Grade 9” typed into that column is an error, immediately, at the moment someone types it. Not a silent wrong total three months later."}]},{"t":"h2","id":"what-you-get-that-a-sheet-cannot-give-you","runs":[{"text":"What you get that a sheet cannot give you"}]},{"t":"p","runs":[{"text":"Types and constraints.","b":true},{"text":" Bad data is rejected at the door instead of being cleaned up later. Later never comes."}]},{"t":"p","runs":[{"text":"Transactions.","b":true},{"text":" A refund that moves money out of one row and into another either fully happens or fully does not. There is no state where half of it landed."}]},{"t":"p","runs":[{"text":"Concurrency.","b":true},{"text":" Forty people can write at once. Nobody overwrites anybody."}]},{"t":"p","runs":[{"text":"Scale.","b":true},{"text":" Ten million rows, answered in milliseconds, with an index. You will see how in a later lesson."}]},{"t":"p","runs":[{"text":"A query language.","b":true},{"text":" This is the big one. A spreadsheet answers the questions you designed it to answer. A database answers questions you had not thought of when you built it. “Which students who joined before June have paid every month since?” is one query against the tables above and impossible against the sheet."}]},{"t":"h2","id":"the-honest-part","runs":[{"text":"The honest part"}]},{"t":"p","runs":[{"text":"A spreadsheet is not a failure mode. Under a few thousand rows, with one person writing and a throwaway question to answer, a spreadsheet is faster than a database and you should use it. Anyone who tells you otherwise is selling something."}]},{"t":"p","runs":[{"text":"The moment to move is specific: when more than one person writes, or when the same fact appears in two rows, or when you need the data to still be trustworthy in a year. Rukmini crossed all three lines in one month and did not notice any of them, because crossing them does not produce an error message. It produces a number that is wrong."}]}],"check":{"question":[{"text":"A small shop keeps one sheet with a row per sale, including the customer’s name, phone and full address on every row. A regular customer moves house. What is the real problem this exposes?"}],"options":[{"text":[{"text":"Her address now sits in many rows and nothing forces those rows to agree with each other"}],"correct":true,"tempting":null},{"text":[{"text":"The sheet will eventually grow too large to open, which is the point at which a database becomes necessary"}],"correct":false,"tempting":[{"text":"Treats file size as the reason to move off a spreadsheet, when size is the symptom that shows up last."}]},{"text":[{"text":"Addresses are being stored as free text when they should be structured into separate fields"}],"correct":false,"tempting":[{"text":"Assumes the flaw is in how the value is formatted rather than in how many times it is stored."}]},{"text":[{"text":"Each sale has no unique identifier, so there is no way to tell two sales apart"}],"correct":false,"tempting":[{"text":"Reaches for the missing primary key, which is a real gap but not what breaks when the address changes."}]}],"order":[1,2,0,3],"why":null},"prev":null,"next":{"slug":"a-place-to-practise","title":"Getting a database onto the machine you have","minutes":8}}}