ЁЯПл The SchoolтА║ЁЯЧДя╕П DatabasesтА║ЁЯзй рдзрдбрд╛ 05 тАФ Data modelling: рдПрдХ рддрдереНрдп, рдПрдХ рдЬрд╛рдЧрд╛
ЁЯЦ╝я╕П See the drawing + lab ЁЯПа Course home ЁЯМ┐ Branch on GitHub тЬПя╕П View source
ЁЯЦ╝я╕П рдЖрдХреГрддреА рдЖрдгрд┐ labThe drawing + lab рдкреВрд░реНрдг рдкрд╛рдирд╛рд╡рд░ рдЙрдШрдбрд╛ тЖЧOpen full page тЖЧ

ЁЯзй рдзрдбрд╛ 05 тАФ Data modelling: рдПрдХ рддрдереНрдп, рдПрдХ рдЬрд╛рдЧрд╛

ЁЯУН рддреБрдореНрд╣реА рдЗрдереЗ рдЖрд╣рд╛рдд: 18 рдкреИрдХреА рдзрдбрд╛ 05 ┬╖ рдорд╛рдЧреЗ: lesson-04-transactions ┬╖ рдкреБрдвреЗ: lesson-06-indexes


ЁЯУж рдпрд╛ рдмреНрд░рдБрдЪрдордзреНрдпреЗ рдХрд╛рдп рдЖрд╣реЗ

рдзрдбреЗ 01тАУ04, рдЖрдгрд┐ рдиреЛрдВрджрд╡рд╣реНрдпрд╛ рдХреЛрдгрддреНрдпрд╛ рдЕрд╕рд╛рд╡реНрдпрд╛рдд рд╣реЗ рдХрд╕реЗ рдард░рд╡рд╛рдпрдЪреЗ: normalisation (рдПрдХ рддрдереНрдп, рдПрдХ рдЬрд╛рдЧрд╛), рд▓рд┐рдЦрд┐рдд рдирд┐рдпрдо рдореНрд╣рдгреВрди constraints, рдЖрдгрд┐ denormalisation тАФ рдПрдЦрд╛рджреА рдорд╛рд╣рд┐рддреА рдореБрджреНрджрд╛рдо copy рдХрд░рдгреЗ, рддреА рдЦрд░реА рдареЗрд╡рдгреНрдпрд╛рдЪреНрдпрд╛ рдирд┐рдпрдорд╛рд╕рд╣.

ЁЯзТ 5 рд╡рд░реНрд╖рд╛рдВрдЪреНрдпрд╛ рдореБрд▓рд╛рд▓рд╛ рд╕рдордЬрд╛рд╡рд▓реНрдпрд╛рд╕рд╛рд░рдЦреЗ

рдПрдХ рдирд╡реА clerk рдПрдХрдЪ рдкреНрд░рдЪрдВрдб рдиреЛрдВрджрд╡рд╣реА рд╕реБрдЪрд╡рддреЗ: рдкреНрд░рддреНрдпреЗрдХ рдУрд│реАрдд рд╡рд┐рджреНрдпрд╛рд░реНрдерд┐рдиреА, рд╡рд░реНрдЧ, рд╢рд┐рдХреНрд╖рд┐рдХрд╛, рд╢рд┐рдХреНрд╖рд┐рдХреЗрдЪрд╛ phone number, grade, рд╡рд┐рд╖рдптАж рд╡рд╛рдЪрд╛рдпрд▓рд╛ рд╕реЛрдкреЗ! рдордЧ рд╢рд┐рдХреНрд╖рд┐рдХрд╛ рдЖрдкрд▓рд╛ phone number рдмрджрд▓рддреЗ. рдореНрд╣рдгрдЬреЗ рддреАрд╕ рдУрд│реА рджреБрд░реБрд╕реНрдд рдХрд░рд╛рдпрдЪреНрдпрд╛, рдЖрдгрд┐ clerk рдПрдХреЛрдгрддреАрд╕ рджреБрд░реБрд╕реНрдд рдХрд░рддреЗ. рд╡рд░реНрд╖рднрд░ рдПрдХ рдУрд│ рдЦреЛрдЯреЗ рдмреЛрд▓рддреЗ.

рд╣реЗ рдЯрд╛рд│рдгрд╛рд░рд╛ рдирд┐рдпрдо: рдПрдХ рддрдереНрдп, рдПрдХ рдЬрд╛рдЧрд╛. рд╢рд┐рдХреНрд╖рд┐рдХреЗрдЪрд╛ phone number teachers рдиреЛрдВрджрд╡рд╣реАрдЪреНрдпрд╛ рдПрдХрд╛рдЪ рдУрд│реАрдд рд░рд╛рд╣рддреЛ; рд╡рд┐рджреНрдпрд╛рд░реНрдереА рдЖрдкрд▓реНрдпрд╛ рд╡рд░реНрдЧрд╛рдХрдбреЗ рдмреЛрдЯ рджрд╛рдЦрд╡рддрд╛рдд, рд╡рд░реНрдЧ рдЖрдкрд▓реНрдпрд╛ рд╢рд┐рдХреНрд╖рд┐рдХреЗрдХрдбреЗ. number рдПрдХрджрд╛ рдмрджрд▓рд╛, рдЖрдгрд┐ pointer рдордзреВрди join рд╣реЛрдгрд╛рд░рд╛ рдкреНрд░рддреНрдпреЗрдХ рдкреНрд░рд╢реНрди рдирд╡рд╛ number рдкрд╛рд╣рддреЛ. рд╣реЗрдЪ normalisation, рдЖрдгрд┐ рдкреНрд░рддреНрдпреЗрдХ рдиреЛрдВрджрд╡рд╣реАрд╕рд╛рдареА рддреНрдпрд╛рдЪреЗ рддреАрди рдкреНрд░рд╢реНрди:

  1. рдПрдХ рдУрд│ рдореНрд╣рдгрдЬреЗ рдХрд╛рдп? (рдПрдХ рд╡рд┐рджреНрдпрд╛рд░реНрдерд┐рдиреА тАФ рдкреНрд░рддреНрдпреЗрдХ рд╡рд┐рд╖рдпрд╛рд╕рд╛рдареА рдПрдХ рд╡рд┐рджреНрдпрд╛рд░реНрдерд┐рдиреА рдирд╡реНрд╣реЗ)
  2. рддреА рдХрд╢рд╛рдиреЗ рдУрд│рдЦрд▓реА рдЬрд╛рддреЗ? (рдУрд│ рдХреНрд░рдорд╛рдВрдХ; roll number UNIQUE рдЖрд╣реЗ)
  3. рдХреЛрдгрддреА рдорд╛рд╣рд┐рддреА рддрд┐рдЪреА рдЖрд╣реЗ, рддреА рдЬреНрдпрд╛рдХрдбреЗ рдмреЛрдЯ рджрд╛рдЦрд╡рддреЗ рддреНрдпрд╛рдЪреА рдирд╛рд╣реА? (рд╡рд┐рджреНрдпрд╛рд░реНрдерд┐рдиреАрдЪреЗ рдирд╛рд╡: рд╣реЛ; рд╡рд░реНрдЧрд╢рд┐рдХреНрд╖рд┐рдХреЗрдЪрд╛ phone: рдирд╛рд╣реА)

рдЖрдгрд┐ рдореБрджреНрджрд╛рдо рдХреЗрд▓реЗрд▓рд╛ рдЕрдкрд╡рд╛рдж: рдХрдзреАрдХрдзреА рдПрдЦрд╛рджрд╛ рдкреНрд░рд╢реНрди рдЗрддрдХреНрдпрд╛ рд╡реЗрд│рд╛ рд╡рд┐рдЪрд╛рд░рд▓рд╛ рдЬрд╛рддреЛ рдЖрдгрд┐ рдЗрддрдХреЗ join рдХрд░рддреЛ рдХреА рддреБрдореНрд╣реА рдПрдЦрд╛рджреА рдорд╛рд╣рд┐рддреА рддреА рдЬрд┐рдереЗ рд╡рд╛рдЪрд▓реА рдЬрд╛рддреЗ рддрд┐рдереЗрдЪ copy рдХрд░рддрд╛ тАФ рдПрдХ denormalised column тАФ рдЖрдгрд┐ рддреА copy рдЦрд░реА рдареЗрд╡рдгрд╛рд░рд╛ рдирд┐рдпрдо рддреБрдореНрд╣реА рд▓рд┐рд╣реВрди рдареЗрд╡рддрд╛ (trigger, рд░рд╛рддреНрд░реАрдЪрд╛ job, рдХрд┐рдВрд╡рд╛ "рдлрдХреНрдд рдпрд╛рдЪ рдПрдХрд╛ function рдордзреВрди"). рдХрд╛рд░рдг рдЖрдгрд┐ рдирд┐рдпрдо рдШреЗрдКрдирдЪ copy рдХрд░рд╛, рдХрдзреАрдЪ рдЪреБрдХреВрди рдирд╛рд╣реА.

ЁЯЧ║я╕П рдЖрдХреГрддреА

flowchart LR
    big["тЭМ one big register<br/>student ┬╖ class ┬╖ teacher ┬╖ teacher phone ┬╖ grade<br/>phone changes тЖТ 30 rows, 29 fixed"]
    norm["тЬЕ one fact, one place<br/>teachers(id, name, phone) тЖР classes(teacher_id) тЖР students(class_id) тЖР grades(student_id)"]
    rules["ЁЯзй constraints: NOT NULL ┬╖ UNIQUE ┬╖ CHECK ┬╖ FK<br/>the schema refuses nonsense"]
    den["тЪЦя╕П denormalise on purpose<br/>copy a hot value + a rule that keeps it true"]
    big -->|"1 normalise"| norm --> rules
    norm -.->|"2 measured reason only"| den

тЭУ рдХрд╛рдп

ЁЯдФ рдХрд╛

рдХрд╛рд░рдг schema рддреЛ рд╡рд╛рдкрд░рдгрд╛рд▒реНрдпрд╛ рдкреНрд░рддреНрдпреЗрдХ program рдкреЗрдХреНрд╖рд╛ рдЬрд╛рд╕реНрдд рдЬрдЧрддреЛ. рдЪрд╛рдВрдЧрд▓реНрдпрд╛ model рдХреЗрд▓реЗрд▓реНрдпрд╛ рдЦреЛрд▓реАрдд рдирд╡рд╛ рдкреНрд░рд╢реНрди рдПрдХрд╛ JOIN рдЪреНрдпрд╛ рдЕрдВрддрд░рд╛рд╡рд░ рдЕрд╕рддреЛ; рд╡рд╛рдИрдЯ model рдХреЗрд▓реЗрд▓реНрдпрд╛ рдЦреЛрд▓реАрдд рдкреНрд░рддреНрдпреЗрдХ рдирд╡рд╛ рдкреНрд░рд╢реНрди рдЖрдзреА data рд╕рд╛рдлрд╕рдлрд╛рдИ рдмрдирддреЛ. рдЖрдгрд┐ constraints "рд╣реЗ рдХреБрдареЗрддрд░реА validate рдХрд░рд╛рдпрд▓рд╛ рд╣рд╡реЗ" рдпрд╛рдЪреЗ рд░реВрдкрд╛рдВрддрд░ "рдХрдкрд╛рдЯ рдЖрдзреАрдЪ рддреЗ рдХрд░рддреЗ" рдордзреНрдпреЗ рдХрд░рддрд╛рдд.

ЁЯФз рдХрд╕реЗ (рдпрд╛ repo рдордзреНрдпреЗ)

db/schema.sql normalised рдЖрд╣реЗ: classes, students, grades, homework, рдкреНрд░рддреНрдпреЗрдХ рдорд╛рд╣рд┐рддреА рдПрдХрджрд╛рдЪ, keys рдиреЗ рдЬреЛрдбрд▓реЗрд▓реА, UNIQUE рдЖрдгрд┐ CHECK рдирд┐рдпрдорд╛рдВрд╕рд╣. demo.py рдордзреАрд▓ model() рдХрдкрд╛рдЯ рджреЛрди рдкреНрд░рдХрд╛рд░рдЪрд╛ рдореВрд░реНрдЦрдкрдгрд╛ рдирд╛рдХрд╛рд░рддрд╛рдирд╛ рджрд╛рдЦрд╡рддреЗ.

ЁЯзк рдХрд░реВрди рдкрд╛рд╣рд╛ тАФ capstone рдЪрд╛ рдкрд╣рд┐рд▓рд╛ рджрдЧрдб

# 1) add a teachers register, properly: in db/schema.sql
#    CREATE TABLE IF NOT EXISTS teachers (id INTEGER PRIMARY KEY, name TEXT NOT NULL, phone TEXT);
#    and give classes a pointer:  teacher_id INTEGER REFERENCES teachers(id)
# 2) seed one teacher per class in db/seed.sql, rebuild, and ask:
python3 db/demo.py model >/dev/null
python3 - <<'EOF'
import sqlite3; c = sqlite3.connect("db/school.db")
print(c.execute("SELECT s.name, t.name, t.phone FROM students s JOIN classes c ON c.id=s.class_id JOIN teachers t ON t.id=c.teacher_id ORDER BY s.name").fetchall())
c.execute("UPDATE teachers SET phone='000' WHERE id=1"); c.commit()     # change the fact ONCE
print(c.execute("SELECT DISTINCT t.phone FROM students s JOIN classes c ON c.id=s.class_id JOIN teachers t ON t.id=c.teacher_id WHERE c.id=1").fetchall())
EOF

тЬЕ рддрдкрд╛рд╕рд╛ тАФ рддреБрдореНрд╣рд╛рд▓рд╛ рдХрд╛рдп рджрд┐рд╕рд╛рдпрд▓рд╛ рд╣рд╡реЗ

3A рдЪреНрдпрд╛ рдкреНрд░рддреНрдпреЗрдХ рд╡рд┐рджреНрдпрд╛рд░реНрдерд┐рдиреАрдЪреНрдпрд╛ рдУрд│реАрдд рддреАрдЪ рд╢рд┐рдХреНрд╖рд┐рдХрд╛ рдЖрдгрд┐ phone рджрд┐рд╕рддреЛ; teachers рд╡рд░ рдПрдХрд╛ UPDATE рдирдВрддрд░, joined query [('000',)] рджрд╛рдЦрд╡рддреЗ тАФ рдПрдХ рдмрджрд▓, рдкреНрд░рддреНрдпреЗрдХ рд╡рд┐рджреНрдпрд╛рд░реНрдерд┐рдиреАрд▓рд╛ рджрд┐рд╕рддреЛ. рджреБрд╣реЗрд░реА roll number рдХрд┐рдВрд╡рд╛ Z рд╣рд╛ grade рдЕрдЬреВрдирд╣реА рдирд╛рдХрд╛рд░рд▓рд╛ рдЬрд╛рддреЛ.

ЁЯПБ рддреБрдореНрд╣реА рдЖрддреНрддрд╛рдЪ рдХрд╛рдп рд╕рд┐рджреНрдз рдХреЗрд▓реЗ

рддреБрдореНрд╣реА рдЦреЛрд▓реАрдд рдПрдХрд╛ рдирд╡реНрдпрд╛ рдкреНрд░рдХрд╛рд░рдЪреА рдорд╛рд╣рд┐рддреА рдПрдХрд╛рдЪ рдЬрд╛рдЧреА рдЬреЛрдбрд▓реА, рддреА key рдиреЗ рдЬреЛрдбрд▓реА, рдЖрдгрд┐ рд╕рд░реНрд╡рд╛рдВрд╕рд╛рдареА рдПрдХрджрд╛рдЪ рдмрджрд▓рд▓реА тАФ modelling рдЪрд╛ рдирд┐рдпрдо, рдЕрдиреБрднрд╡рд▓реЗрд▓рд╛.

тЪая╕П рдиреЗрд╣рдореАрдЪреНрдпрд╛ рдЪреБрдХрд╛

ЁЯПн рдкреНрд░рддреНрдпрдХреНрд╖ рд╡рд╛рдкрд░рд╛рдд рд╣реЗ рдХрд╛ рдорд╣рддреНрддреНрд╡рд╛рдЪреЗ: schema design рд╣рд╛ рдЕрд╕рд╛ рдПрдХрдореЗрд╡ рдирд┐рд░реНрдгрдп рдЖрд╣реЗ рдЬреЛ рдкрд╣рд┐рд▓реНрдпрд╛ рджрд┐рд╡рд╢реА рд╕реНрд╡рд╕реНрдд рдЕрд╕рддреЛ рдЖрдгрд┐ 400 рд╡реНрдпрд╛ рджрд┐рд╡рд╢реА рдмрджрд▓рдгреЗ рд╡рд┐рдирд╛рд╢рдХрд╛рд░реА рдЕрд╕рддреЗ. рдЖрдзреА normalise рдХрд░рдгрд╛рд▒реНрдпрд╛ рдЖрдгрд┐ рдкреБрд░рд╛рд╡реНрдпрд╛рд╕рд╣ denormalise рдХрд░рдгрд╛рд▒реНрдпрд╛ teams рдЖрдкрд▓реЗ рдкрд░реНрдпрд╛рдп рдЯрд┐рдХрд╡рддрд╛рдд; рдЙрд▓рдЯрд╛ рдХреНрд░рдо рдореНрд╣рдгрдЬреЗ rewrite.

тПня╕П рдкреБрдвреЗ

рдЦреЛрд▓реАрд▓рд╛ рдЪрд╛рдВрдЧрд▓рд╛ рдЖрдХрд╛рд░ рдЖрд▓рд╛ рдЖрд╣реЗ тАФ рдЖрддрд╛ рдкреНрд░рд╢реНрди рдЬрд▓рдж рдХрд░рд╛: indexes, рдХрд╛рд░реНрдб рдХреЕрдЯрд▓реЙрдЧ, рдЖрдгрд┐ EXPLAIN.

git checkout lesson-06-indexes

ЁЯзй Lesson 05 тАФ Data modelling: one fact, one place

ЁЯУН You are here: Lesson 05 of 18 ┬╖ Previous: lesson-04-transactions ┬╖ Next: lesson-06-indexes


ЁЯУж What's in this branch

Lessons 01тАУ04, plus how to decide what the registers are: normalisation (one fact, one place), constraints as the written rules, and denormalisation тАФ copying a fact on purpose, with a rule for keeping it true.

ЁЯзТ Explain like I'm 5

A new clerk suggests one giant register: every line has the student, the class, the teacher, the teacher's phone number, the grade, the subjectтАж Easy to read! Then the teacher changes her phone number. That is thirty lines to fix, and the clerk fixes twenty-nine. For a year, one line lies.

The rule that prevents it: one fact, one place. The teacher's phone number lives in one line of a teachers register; students point at their class, classes point at their teacher. Change the number once and every question that joins through the pointer sees the new one. That is normalisation, and its three questions per register are:

  1. What is one line? (one student тАФ not one student-per-subject)
  2. What identifies it? (the line number; roll number is UNIQUE)
  3. Which facts belong to it, not to something it points at? (the student's name: yes; the class teacher's phone: no)

And the exception, made on purpose: sometimes a question is asked so often and joins so much that you copy a fact next to where it is read тАФ a denormalised column тАФ and you write down the rule that keeps the copy true (a trigger, a nightly job, or "only via this one function"). Copy with a reason and a rule, never by accident.

ЁЯЧ║я╕П Diagram

flowchart LR
    big["тЭМ one big register<br/>student ┬╖ class ┬╖ teacher ┬╖ teacher phone ┬╖ grade<br/>phone changes тЖТ 30 rows, 29 fixed"]
    norm["тЬЕ one fact, one place<br/>teachers(id, name, phone) тЖР classes(teacher_id) тЖР students(class_id) тЖР grades(student_id)"]
    rules["ЁЯзй constraints: NOT NULL ┬╖ UNIQUE ┬╖ CHECK ┬╖ FK<br/>the schema refuses nonsense"]
    den["тЪЦя╕П denormalise on purpose<br/>copy a hot value + a rule that keeps it true"]
    big -->|"1 normalise"| norm --> rules
    norm -.->|"2 measured reason only"| den

тЭУ What

ЁЯдФ Why

Because the schema outlives every program that uses it. A well-modelled room lets a new question be one JOIN away; a badly-modelled one makes every new question a data clean-up first. And constraints turn "we should validate that somewhere" into "the shelf already does".

ЁЯФз How (in this repo)

db/schema.sql is normalised: classes, students, grades, homework, each fact once, connected by keys, with UNIQUE and CHECK rules. model() in demo.py shows the shelf refusing two kinds of nonsense.

ЁЯзк Try it тАФ the capstone's first stone

# 1) add a teachers register, properly: in db/schema.sql
#    CREATE TABLE IF NOT EXISTS teachers (id INTEGER PRIMARY KEY, name TEXT NOT NULL, phone TEXT);
#    and give classes a pointer:  teacher_id INTEGER REFERENCES teachers(id)
# 2) seed one teacher per class in db/seed.sql, rebuild, and ask:
python3 db/demo.py model >/dev/null
python3 - <<'EOF'
import sqlite3; c = sqlite3.connect("db/school.db")
print(c.execute("SELECT s.name, t.name, t.phone FROM students s JOIN classes c ON c.id=s.class_id JOIN teachers t ON t.id=c.teacher_id ORDER BY s.name").fetchall())
c.execute("UPDATE teachers SET phone='000' WHERE id=1"); c.commit()     # change the fact ONCE
print(c.execute("SELECT DISTINCT t.phone FROM students s JOIN classes c ON c.id=s.class_id JOIN teachers t ON t.id=c.teacher_id WHERE c.id=1").fetchall())
EOF

тЬЕ Verify тАФ what you should see

Every 3A student's line shows the same teacher and phone; after one UPDATE on teachers, the joined query shows [('000',)] тАФ one change, every student sees it. A duplicate roll number or a grade of Z is still refused.

ЁЯПБ What you just proved

You added a new kind of fact to the room in one place, connected it by a key, and changed it once for everyone тАФ the modelling rule, felt.

тЪая╕П Common mistakes

ЁЯПн Why this matters in production: schema design is the one decision that is cheap on day one and ruinous to change on day 400. Teams that normalise first and denormalise with evidence keep their options; the reverse order is a rewrite.

тПня╕П Next

The room is well shaped тАФ now make questions fast: indexes, the card catalogue, and EXPLAIN.

git checkout lesson-06-indexes
тЖР PrevioustransactionsNext тЖТindexes

This page is the lesson's README from the lesson-05-data-modelling branch, shown here so the whole School stays on one site. Code files open on GitHub at the same branch.