Skip to content

← Grade 10

Databases and SQL

Unit 3 · Data and analysis

Hide the stories, facts, photos, activities and interactives, for revising before an exam
In this unit 15 sections
  1. What a database is
  2. Designing a table
  3. Keys: telling records apart
  4. Linking tables
  5. Asking questions with SQL
  6. Counting and summarizing
  7. Joining tables
  8. Changing the data
  9. Keeping the data right
  10. A data dictionary
  11. Finding one record among millions
  12. A database from Python
  13. Project: a database for the school library
  14. In short
  15. Exercise

Walk into any school office in Nepal and you will find registers: an admission register, an attendance register for each class, a book of exam marks, the library’s list of who borrowed what. The same student is written in every one of them, by different people, sometimes with her name spelled differently. When the head teacher asks “Which Grade 10 students from Banepa scored under 40 in Mathematics?”, someone has to turn the pages of three books and copy names onto a fourth.

A computer can answer that question in less time than it takes to blink, and not only for one school. When the results of a national exam come out, hundreds of thousands of students type in their numbers on the same day, and each one gets their own result back in a moment. Behind that is a database.

In this unit you will see how a database keeps data in tables, how keys make every record findable and link one table to another, and how to ask a database questions in SQL, the language almost every database understands, on a real database that runs in this page. You will also design tables of your own, write a data dictionary that says exactly what each field may hold, judge the different ways of keeping wrong data out, and find out how a database finds one record among millions in an instant.

What a database is

You met tables in Grade 6, in Tables: records and fields: each row is one record (one student, one book), and each column is one field (name, class, town). In Grade 7 you kept tables in spreadsheets, sorted them, filtered them and summarized them. In Grade 9 and in Unit 1 your Python programs read tables from CSV files.

A database is an organized collection of data, kept so that it can be searched, changed and shared. A database is looked after by a program called a database management system (DBMS). You never open the database’s files yourself. You ask the DBMS, and it finds the data, changes it, checks it, and keeps it safe, even when many people use it at once.

Most databases in the world are relational databases: they keep their data in tables, and the tables are linked to each other by the values in them (you will see how in this unit). This kind of database is what this unit teaches.

Why not just a spreadsheet?

A spreadsheet is a table too, and for a class survey or a small shop’s budget it is the right tool. A database is built for jobs a spreadsheet does badly:

A database… which matters because…
lets many people and programs use the same data at once, safely the office, every class teacher and the school’s website all use the same records
checks every change against rules you set a mark of 130 or a class that doesn’t exist is refused before it is saved
links tables, so each fact is written once a student’s name is stored in one place, not in every register
finds one record among millions in an instant a results website can answer a whole country on one day
keeps every change safe, even if the power goes in the middle of one a change is either saved completely or not at all
lets each person see and change only what they should a student can see her own marks, not everyone’s

What is a DBMS?

Pick an answer to see why.

A school keeps every student’s marks in one spreadsheet that six teachers edit on their own copies and email to the office. Which problem is a database built to solve here?

Pick an answer to see why.

Designing a table

Before any data goes in, someone must design the tables: decide which fields each one has and what kind of data each field holds. A good design makes the data easy to search and hard to get wrong. A bad one causes trouble for years.

One fact per field

Each field holds one fact, the smallest piece anyone will want to search or sort by. If an address is stored as “Ward 4, Suryabinayak, Bhaktapur” in a single field, finding everyone from Bhaktapur means searching inside text, and someone will have typed “Bkt”. Stored as three fields, ward, municipality and district, it is one simple question.

Store facts, not things worked out from them. Store a student’s date of birth, never her age: the age changes every year, and a stored age is wrong a year later, while the date of birth never changes and the age can always be worked out from it. In the same way, store each subject’s mark and let the database add up the total when it is asked.

Data types

Each field has a data type: the kind of value it may hold. The type decides what can be done with the value (numbers can be added, dates compared, text searched) and lets the DBMS refuse a value of the wrong kind.

Kind of data Common type names Examples
whole numbers INTEGER, INT a student’s ID, a mark, a year
numbers with a decimal point REAL, DECIMAL, FLOAT a GPA of 3.45, a price of 125.50
text TEXT, VARCHAR(n), CHAR(n) a name, a town, a class like 10A
dates and times DATE, DATETIME 2011-05-13
yes or no BOOLEAN has paid the fee: yes

Different database systems name their types a little differently: VARCHAR(60) means text of up to 60 characters in MySQL, and SQLite, the database on this page, has just INTEGER, REAL, TEXT and BLOB (any bytes, such as a photo) and keeps dates as text written year-month-day, 2011-05-13, which sorts in date order.

Choose the type by what you will do with the value, not by what it looks like. A phone number is made of digits, but it is not a number: you never add two phone numbers, and a landline number in Kathmandu starts with 01, a zero a number type would throw away. So a phone number is TEXT. So is a ward number written as “Ward 4”? No: store the 4 as an INTEGER, if you will ever sort or compare wards, and write “Ward” only when you show it.

Which is the best way to store when a student was born?

Pick an answer to see why.

What data type should a field for a customer’s mobile number have?

Pick an answer to see why.

Keys: telling records apart

Our school database has two students called Sita Shrestha: one in Grade 9A, one in 10B. One scored 18 in Science, the other 67. If the office finds students by their names, one day the wrong Sita’s marks will be printed on the other’s report card.

So every table needs a field, or a set of fields, whose value is different in every record: a primary key. A primary key must be:

  • unique: no two records have the same value;
  • never empty: every record has one;
  • stable: it doesn’t change, because other records point to it (as you will see).

A name fails the first test, and a phone number fails the third. The best key is usually a number the system gives out itself: a student ID written on the admission form. Our school gives IDs from 101 up, so the two Sitas are student 108 and student 139, and can never be mixed up again.

You have used keys all your life. A symbol number on an exam admit card is different for every student who sits that exam. A citizenship certificate number, a bank account number, a SIM card’s mobile number, a book’s ISBN: each was made so that one record, and only one, can be found by it.

A key of two fields

Sometimes no single field is unique, but two together are. In the school’s marks table each row is one student’s mark in one subject. Student 139 has six rows, one for each subject, and Science has a row for each student who has marks. But the pair (student 139, Science) appears only once. A key made of two or more fields is a composite key, and it also makes a rule: a student can’t have two Science marks.

Why is a student’s name a poor primary key?

Pick an answer to see why.

Linking tables

Here is a students table a beginner might design, with the class teacher written in every row:

student_id name class class_teacher
122 Anisha Tamang 10A Ramesh Acharya
123 Binod Karki 10A Ramesh Acharya
124 Deepa Gurung 10A Ramesh Acharya
125 Ishwor Basnet 10A Ramesh Achraya

Look at the last row: the teacher’s name is spelled wrong, so a search for Ramesh Acharya’s students misses Ishwor. And if a new teacher takes over 10A, the name must be changed in every row; miss one, and the data says two things at once. Data written more than once is called redundant, and redundant copies drift apart.

The cure is to write each fact once, in its own table, and point to it. The class teacher belongs to the class, so it goes in a classes table, one row per class. Each student’s row keeps only the class’s key, 10A. A field that holds another table’s primary key is a foreign key: it points to one row of the other table.

Figure 3.1 shows the whole school database you will use in this unit, with five tables.

classesclass_idTEXTgradeINTEGERsectionTEXTteacher_idINTEGERstudentsstudent_idINTEGERnameTEXTclass_idTEXTbornTEXTtownTEXTmarksstudent_idINTEGERsubject_idTEXTmarkINTEGERteachersteacher_idINTEGERnameTEXTsubject_idTEXTjoinedINTEGERsubjectssubject_idTEXTnameTEXT classesclass_idTEXTgradeINTEGERsectionTEXTteacher_idINTEGERstudentsstudent_idINTEGERnameTEXTclass_idTEXTbornTEXTtownTEXTmarksstudent_idINTEGERsubject_idTEXTmarkINTEGERteachersteacher_idINTEGERnameTEXTsubject_idTEXTjoinedINTEGERsubjectssubject_idTEXTnameTEXT
Figure 3.1 The school database. Each box is a table, with its fields and their types; the primary key is underlined (marks has a composite key of two fields). Each line joins a foreign key to the key it points to: the forked end is the “many” side, the bar the “one” side. One class has many students; one student has many marks; one subject has many marks and its teachers.

Now the class teacher is written once, in classes. Changing the teacher is one change in one row, and every student of 10A sees the new teacher at once.

Kinds of relationship

How many records on each side can be linked is the relationship’s kind:

  • One-to-many: one class has many students, but each student is in one class. The foreign key goes on the “many” side: class_id is in students, not a list of students in classes. This is the most common kind.
  • One-to-one: one record matches exactly one on the other side, such as a student and her library card. These could often be one table; they are split when one part is private or rarely used.
  • Many-to-many: a student studies many subjects, and a subject is studied by many students. No single foreign key can hold this. It needs a third table in between, a linking table, whose rows each join one student to one subject. In our database that table is marks: each row joins a student and a subject, and carries the mark.

A foreign key must point to something

If a student’s row says class 10C and the school has no 10C, the record points nowhere. A DBMS can be told to refuse such a record: a foreign key rule says that every value in students.class_id must already be a class_id in classes. It also refuses to delete a class while students still point to it. Keeping every foreign key pointing at a real record is called referential integrity.

In the school database, why is the class teacher stored in classes and not in students?

Pick an answer to see why.

A library lets each member borrow many books over time, and each book is borrowed by many members over time. What kind of relationship is this, and what does it need?

Pick an answer to see why.

Asking questions with SQL

SQL (Structured Query Language, said “ess-cue-ell” or “sequel”) is the language for asking a relational database questions and for changing its data. A question in SQL is called a query. Its shape is close to English:

SELECT name, townFROM studentsWHERE class_id = '9A';

Read it as: select the name and town from the students table, where the class is 9A. The words in capitals are SQL’s keywords. SQL doesn’t mind whether you write them in capitals or small letters, but capitals make queries easier to read. A query ends with a semicolon.

The answer is itself a table: the rows that match, with only the columns you asked for. Notice what you didn’t say: you didn’t say how to search, which row to start from or when to stop. You said what you want, and the DBMS works out how. That is Codd’s idea at work.

Try it

Ask the school database

This is a real database, running in this page: the five tables of Figure 3.1, with 41 students. The query is already there, and its answer is below it. Change 9A to 10B and press Run. Then try SELECT * FROM students;: the star means “every column”. Nothing you do here can break anything: Reset brings the database back.

Loading the tables…

underlined = primary key; → a foreign key, and the table it points to

Rows
–

Starting the database in this page…

Picking rows with WHERE

The WHERE line is a condition, like an if in Python, and only the rows for which it is true are in the answer. Conditions compare a field with a value:

In SQL means Example
= equal to (one = sign, not two as in Python) town = 'Thimi'
<> or != not equal to town <> 'Thimi'
<, >, <=, >= less, more, at most, at least mark >= 80
BETWEEN … AND … in a range, both ends included mark BETWEEN 40 AND 59
IN (…) one of a list town IN ('Thimi', 'Banepa')
LIKE matches a pattern: % is any characters (or none), _ exactly one name LIKE 'S%' (names starting with S)
IS NULL the field is empty town IS NULL
AND, OR, NOT join conditions, as in Grade 7’s logic class_id = '10A' AND town = 'Bhaktapur'

Text and dates go in single quote marks, 'Thimi', '2011-01-01'; numbers don’t, 80. Without the quotes, Thimi would be read as the name of a column.

Try it

Born before 2011

Your turn. Which students were born before 1 January 2011? Show each one’s name and born, in that order. Dates are written year-month-day, and earlier dates are “less”. The box says when your answer is right.

Loading the tables…

underlined = primary key; → a foreign key, and the table it points to

Rows
–

Starting the database in this page…

Hint
Answer

Sorting with ORDER BY

A table has no order of its own. To get rows in an order, say so with ORDER BY, ASC for smallest first (the usual), DESC for largest first. LIMIT keeps only the first few rows of the answer.

SELECT student_id, markFROM marksWHERE subject_id = 'SCI'ORDER BY mark DESCLIMIT 5;

This gives the five best Science marks: students 122 and 132 with 98, then 130 with 94, 120 with 78 and 131 with 77. Several lines join in a set order: SELECT, FROM, WHERE, ORDER BY, LIMIT.

Try it

The lowest Maths marks

The query gives the five best Science marks. Change it to give the three lowest Mathematics marks, smallest first, with the student ID and the mark. (The subjects’ IDs are in the subjects table: SELECT * FROM subjects;.)

Loading the tables…

underlined = primary key; → a foreign key, and the table it points to

Rows
–

Starting the database in this page…

Hint
Answer

Which query lists the names of Grade 10A students from Bhaktapur?

Pick an answer to see why.

What does WHERE name LIKE '%Gurung' find?

Pick an answer to see why.

Counting and summarizing

Many questions aren’t about single records but about totals: how many students, the average mark, the best score. SQL has five aggregate functions that turn many rows into one value:

Function gives Example Answer
COUNT(*) the number of rows SELECT COUNT(*) FROM students; 41
SUM(field) the total SELECT SUM(mark) FROM marks WHERE student_id = 139; 462
AVG(field) the average SELECT AVG(mark) FROM marks WHERE subject_id = 'SCI'; 64.9 (rounded)
MIN(field), MAX(field) the smallest, the largest SELECT MAX(mark) FROM marks WHERE subject_id = 'MATH'; 97

COUNT(field) counts only the rows where that field isn’t NULL; COUNT(*) counts every row.

GROUP BY: a total for each group

In Grade 7 you summarized a survey by group: how many students walk, how many come by bus. SQL does this with GROUP BY: it puts the rows into groups with the same value, and the aggregate function works out one answer per group.

SELECT town, COUNT(*)FROM studentsGROUP BY townORDER BY COUNT(*) DESC;
town COUNT(*)
Bhaktapur 10
Banepa 10
Thimi 6
Kathmandu 6
Lalitpur 5
Nagarkot 4

Six towns, six rows, one count each, the largest first.

To choose groups by their totals, use HAVING, which works like WHERE but after the grouping. WHERE picks rows before they are grouped; HAVING picks groups after. This finds students whose average over all their subjects is 80 or more:

SELECT student_id, AVG(mark)FROM marksGROUP BY student_idHAVING AVG(mark) >= 80;

Three students: 122, 130 and 132.

Try it

How many in each class?

The query gives each subject’s average and best mark. Change it to count the students in each class: one row per class, with the class ID and the number of students.

Loading the tables…

underlined = primary key; → a foreign key, and the table it points to

Rows
–

Starting the database in this page…

Hint
Answer

What does this query give?
SELECT subject_id, MIN(mark) FROM marks GROUP BY subject_id;

Pick an answer to see why.

Joining tables

The marks table says student 132 scored 98 in Computing. But who is student 132? The name is in another table. Splitting the data kept each fact once; now a query must put the pieces back together. That is a join.

SELECT students.name, marks.markFROM marksJOIN students ON marks.student_id = students.student_idWHERE marks.subject_id = 'COMP'ORDER BY marks.mark DESC;

The JOIN line says: take each row of marks, find the row of students whose student_id is the same, and put the two together into one longer row. The ON part is the link, the foreign key matched to the primary key it points to. Because both tables have a column called student_id, each column is written with its table’s name and a dot: marks.student_id, students.name.

marks.student_id marks.subject_id marks.mark → students.student_id students.name
132 COMP 98 joins 132 Dipesh Prajapati
130 COMP 95 joins 130 Sabina Ghimire
122 COMP 93 joins 122 Anisha Tamang

Then WHERE, ORDER BY and SELECT work on the joined rows as on any table. So the answer starts Dipesh Prajapati 98, Sabina Ghimire 95, Anisha Tamang 93.

Try it

Names with the marks

The query joins marks to students to give the Computing marks with names. Change it to give the Mathematics marks of class 10A only, with names, best first.

Loading the tables…

underlined = primary key; → a foreign key, and the table it points to

Rows
–

Starting the database in this page…

Hint
Answer

Joins can go on: this finds each class’s teacher by name, through the foreign key in classes:

SELECT classes.class_id, teachers.nameFROM classesJOIN teachers ON classes.teacher_id = teachers.teacher_id;

It gives 9A Pratibha Shakya, 9B Nisha Silwal, 10A Ramesh Acharya and 10B Sushila Manandhar. And joins work with GROUP BY: here is each student’s total, with names, best first.

SELECT students.name, SUM(marks.mark) AS totalFROM studentsJOIN marks ON marks.student_id = students.student_idGROUP BY students.student_idORDER BY total DESCLIMIT 3;

Dipesh Prajapati 586, Anisha Tamang 582, Sabina Ghimire 501. AS total gives the new column a name, which ORDER BY can then use. Notice that the query groups by student_id, not by name: grouping by name would add the two Sita Shresthas’ marks together.

In JOIN students ON marks.student_id = students.student_id, what does the ON part do?

Pick an answer to see why.

Changing the data

Queries that ask questions start with SELECT. Three other statements change the data:

INSERT INTO students (student_id, name, class_id, born, town)VALUES (142, 'Rabin Shakya', '9A', '2012-04-18', 'Bhaktapur'); UPDATE studentsSET town = 'Thimi'WHERE student_id = 142; DELETE FROM studentsWHERE student_id = 142;

INSERT adds a row, with a value for each field named. UPDATE changes fields in the rows that match its WHERE. DELETE removes the rows that match its WHERE. And a new table is made with CREATE TABLE, which names each field, its type and its rules:

CREATE TABLE books (  book_id INTEGER PRIMARY KEY,  title TEXT NOT NULL,  author TEXT,  year INTEGER CHECK (year BETWEEN 1450 AND 2100));

Which statement corrects Bibek Thapa’s (student 102) Science mark to 72?

Pick an answer to see why.

Keeping the data right

A database is only as useful as the data in it. A mark of 130, a date of birth in the wrong calendar, a student in a class that doesn’t exist: each wrong value spoils every answer that uses it. Keeping data right is done in two ways:

  • Validation checks that a value is possible: the right type, inside the allowed range, in the right format. A computer does it automatically, every time.
  • Verification checks that a value is what was meant: that the 67 typed in is really the mark on the answer sheet. Validation can’t catch a mark of 76 typed as 67, because 67 is a perfectly possible mark.

Kinds of validation check

Check asks Example
type check Is it the right kind of value? a mark is a whole number, not “ninety”
range check Is it between the limits? a mark is from 0 to 100; a year from 1950 to 2100
presence check Is it there at all? every student has a name
format check Is it in the right pattern? a date is written 2011-05-13; a mobile number has 10 digits
list (lookup) check Is it one of the allowed values? a class is one of 9A, 9B, 10A, 10B
uniqueness check Is it different from all the others? no two students share an ID
cross-field check Do two fields agree? a book is returned on or after the day it was borrowed

In a database, these checks are written into the table as constraints, rules the DBMS enforces on every INSERT and UPDATE, whoever or whatever sends it: the data type, NOT NULL (presence), CHECK (…) (range, format and cross-field), UNIQUE and PRIMARY KEY (uniqueness), and REFERENCES (a foreign key: a lookup in another table).

Try it

Try to break the rules

The school’s marks table has the rule CHECK (mark BETWEEN 0 AND 100). Press Run to try to save a Science mark of 130 for student 141, and read why it is refused. Then try to break other rules: a mark for student 999, who doesn’t exist; a second Science mark for student 141; a class 10C for a new student in students. Each is refused with its reason.

Loading the tables…

underlined = primary key; → a foreign key, and the table it points to

Rows
–

Starting the database in this page…

Answer

Where to check: four places, each with its own job

The same check can be made in several places, and each place catches different mistakes:

Where How Good at Misses
in the form where the data is typed a menu of classes instead of a box to type in; a date picker; a field that accepts only digits stopping a mistake before it is made, and telling the person at once, while they can still fix it data that doesn’t come through the form (a file loaded in, another program); anyone who gets round the form
in the program that saves the data Python’s if not 0 <= mark <= 100: before saving rules too complicated for the database, and clear messages for the user other programs that write to the same database
in the database, as constraints types, NOT NULL, CHECK, UNIQUE, foreign keys the last line of defense: no data gets in without passing, whatever sent it anything possible but wrong (67 for 76); any rule nobody wrote down
after the data is in queries that look for strange values: SELECT * FROM marks WHERE mark < 10;, counts that should match data already saved before a rule existed, or loaded from somewhere else; things that are possible but unlikely nothing is stopped: the bad data is already there, and someone must fix it

So a careful system checks in more than one place: in the form, so people fix mistakes as they type, and in the database, so nothing gets past whatever happens. And because validation can’t tell a wrong value from a right one, important data also gets verification: typing the marks twice, by two people, and comparing (double entry); reading them back against the answer sheets; or a check digit, an extra digit worked out from the others, which the computer can check (ISBNs and the long numbers on bank cards have one).

Does your database check types?

You might expect every database to refuse “ninety” in a number field. Not all do. SQLite, the database on this page, normally stores whatever it is given: put a word in an INTEGER column, and it keeps the word. A table created with the word STRICT at the end checks types strictly and refuses such values. Most other database systems always check types, though some can be set up to quietly change a wrong value into another one (such as 0) instead of refusing it. So knowing exactly what your own DBMS checks, and what it lets through, is part of designing a database.

Try it

Does it check the type?

This database starts empty. The query makes two tables with an INTEGER column, one ordinary and one STRICT (IF NOT EXISTS makes each only once, so you can run the query again), saves the word 'ninety' in the ordinary one, and shows what was saved: typeof gives the type of the value really stored. Then change the INSERT and the SELECT to use strict_marks instead, and run it again. Try '90' too, in both.

Loading the tables…

underlined = primary key; → a foreign key, and the table it points to

Rows
–

Starting the database in this page…

What you should see

Try it

Which check catches it?

A mark of 130 typed in a mark field out of 100

Which check would catch this mistake?

Tap a group above.

Card
1 of 10
Right the first time
0

A data dictionary

How does a new programmer, or the office clerk, or you in a year’s time, know that born is written year-month-day in AD, that a mark is out of 100, or that class_id must be a class that exists? Not from the data. It must be written down, in a data dictionary: a document that describes every field of every table, so that everyone who uses the data understands it the same way.

For each field, a data dictionary gives at least:

  • its name and a description: what it means, in plain words;
  • its data type (and size, if there is one);
  • its allowed values: a range, a format, or a list, and whether it may be empty;
  • whether it is a key, and what a foreign key points to;
  • an example value.

It also describes the relationships: how the tables are linked, and the rules between fields. Here is the school database’s data dictionary for two of its tables.

students: one row per student in the school

Field Type Allowed values Key Description Example
student_id INTEGER from 101 up; unique; never empty, never changed primary key the ID given when the student is admitted 139
name TEXT not empty; up to 100 characters; any script the student’s full name, as on the admission form Sita Shrestha
class_id TEXT must be a class_id in classes; not empty foreign key → classes the class the student is in this year 10B
born DATE (TEXT in SQLite) written YYYY-MM-DD, in AD; between 2005-01-01 and 2016-12-31; may be empty if not known date of birth, from the birth certificate 2011-05-13
town TEXT may be empty the town or village the student lives in Bhaktapur

marks: one row per student per subject, for this term’s exam

Field Type Allowed values Key Description Example
student_id INTEGER must be a student_id in students primary key (with subject_id); foreign key → students whose mark it is 139
subject_id TEXT must be a subject_id in subjects primary key (with student_id); foreign key → subjects which subject SCI
mark INTEGER whole number, 0 to 100; not empty the mark out of 100 67

Relationships and rules. One class has many students (students.class_id → classes). One student has at most one mark in each subject (the composite key), so the marks table links students and subjects, many-to-many. A student without marks yet is allowed (a new admission). Dates are in AD and shown in BS on report cards.

A data dictionary is written before the tables are built: it is the design, and the CREATE TABLE statements and the form’s checks are made from it. Then it is kept up to date, because a field nobody understands is soon filled with guesses. Compare it with the database’s own constraints: the dictionary says born must be between 2005 and 2016, but our database checks only the format. That gap is worth knowing, and worth closing with a CHECK.

What does a data dictionary not contain?

Pick an answer to see why.

Finding one record among millions

Back to the question on this unit’s card. Imagine the results of a national exam: 200,000 students, in a table. You type a name. How does the database find the right rows?

The simplest way is to look at every row, from the first to the last, and keep those that match. This is a full table scan: the linear search of Grade 9. It works, but its work grows with the table: twice the rows, twice the time.

A book has a better way. To find “photosynthesis” in a science book, you don’t read every page: you turn to the index at the back, where the words are in alphabetical order, each with its page number. Libraries did the same with their card catalogs: a drawer of cards in alphabetical order, each card saying where a book stood on the shelves.

Rows of small wooden drawers with brass handles, each labelled with a stretch of the alphabet, such as COWLES C to CREATE, in a library
Figure 3.2 A library’s card catalog: an index made of paper. Each drawer holds cards in alphabetical order, one for each book, saying where it is on the shelves, and each drawer’s label gives the first and last words in it (COWLES C to CREATE, on the top drawer of the left-hand column), so you go straight to the right drawer and then to the right card. Photo: TBurmeister (WMF), CC BY-SA 4.0

A database index is the same idea. It is a second, hidden structure that keeps one field’s values in order, each with where its row is:

name (in order) where its rows are (their symbol numbers)
Aarati Adhikari 2000001, 2000161, 2001721, …
Aarati Bajracharya 2000321, 2001881, 2002041, …
… …
Dawa Sherpa 2000954, 2002514, 2002674, …

Because the index is in order, the database can find a name the way you find a word in a dictionary, halving the search each time, as in Grade 9’s binary search: 200,000 names need only about 18 halvings (2¹⁸ is 262,144). Real databases keep an index as a B-tree, a tree (Unit 2’s trees) whose every node holds hundreds of values in order, so a table of millions of rows is only three or four levels deep: a search reads a handful of nodes and goes straight to the row. The catalog’s drawers work the same way: the labels on the drawers are the top level, and the cards inside are the next.

See it for yourself in the next playground. It holds a different database: 200,000 results of a made-up national exam, built in your browser (that takes a moment). Its query finds every student called Dawa Sherpa, and the playground shows the time taken.

  1. Run the query and note the time.
  2. Put EXPLAIN QUERY PLAN in front of the query and run it: the database says how it would search. SCAN means it would read every row.
  3. Make an index on the name: replace the query with CREATE INDEX idx_name ON exam_results (name); and run it.
  4. Run the first query again, and its EXPLAIN QUERY PLAN. What changed?
  5. Find one student by symbol number: SELECT * FROM exam_results WHERE symbol_no = 2123456;. Is it fast without an index of yours? Why?

Try it

Find one student among 200,000

Follow the steps above, one query at a time.

Loading the tables…

underlined = primary key; → a foreign key, and the table it points to

Rows
–
Time taken
–

Starting the database in this page…

What you should see

A few milliseconds for 200,000 rows sounds fast already. But a scan’s time grows with the table, and an index search hardly grows at all. A real exam board’s database holds every student of many years; a bank’s holds every payment ever made. At a thousand times the rows, a scan takes a thousand times as long, seconds for every single search, while the index needs only one or two more levels of its tree. And a busy website answers thousands of searches a second.

Indexes aren’t free, though. Each one takes space, and every INSERT, UPDATE and DELETE must change the index too, so a table with many indexes is slower to change. Database designers make indexes on the fields that are searched most (a phone number in a wallet app, a symbol number for results), and not on every field.

Why does an index make finding a name faster?

Pick an answer to see why.

A shop’s database has an index on every one of its 30 fields. What is the cost?

Pick an answer to see why.

A database from Python

Programs talk to databases in SQL too. A school’s website, a wallet app or a results page is a program that sends SQL to a database and shows you the rows that come back. Python comes with SQLite, in the sqlite3 module (a library, as in Unit 1), so a Python program can make and use a database with no installing at all:

import sqlite3 db = sqlite3.connect(":memory:")    # a database kept in memory, just for this programdb.execute("CREATE TABLE marks (student TEXT, subject TEXT, mark INTEGER)") rows = [("Anisha", "SCI", 98), ("Binod", "SCI", 64), ("Deepa", "SCI", 71)]db.executemany("INSERT INTO marks VALUES (?, ?, ?)", rows) wanted = 70for name, mark in db.execute("SELECT student, mark FROM marks WHERE mark >= ? ORDER BY mark DESC", (wanted,)):    print(name, mark) average = db.execute("SELECT AVG(mark) FROM marks").fetchone()[0]print("Average:", round(average, 1))

Output

Anisha 98Deepa 71Average: 77.7

Each ? is a placeholder: the values travel to the database separately from the SQL, in the tuple after it, so they can only ever be data. This is the safe way you met in Grade 9’s SQL injection: never build SQL by joining in what a user typed. fetchone() gives the first row of the answer as a tuple, and [0] its first value.

A real program would connect("school.db") instead of ":memory:": then the database is a file that stays on the disk after the program ends, and the next run finds its tables still there.

Why does the program write WHERE mark >= ? and pass (wanted,) separately, instead of joining the number into the SQL text?

Pick an answer to see why.

Project: a database for the school library

In short

Exercise

In a table of books, what is one record?

Pick an answer to see why.

Which field is the best primary key for a table of a bank’s customers?

Pick an answer to see why.

A foreign key is…

Pick an answer to see why.

Which query gives the average mark in each subject?

Pick an answer to see why.

A form lets the clerk type a mark of 67 when the answer sheet says 76. Which would catch it?

Pick an answer to see why.

Which is the safest thing to do before running DELETE FROM loans WHERE returned < '2020-01-01';?

Pick an answer to see why.

a.

Explain these words, with an example of each from a school: database, DBMS, record, field, primary key, foreign key.

Answer
b.

A health post keeps this table of patient visits. Find three problems with its design, and design better tables.

visit patient age address phone doctor doctor’s phone
1 Hari Thapa 45 Ward 3, Banepa 98xxxxxxxx Dr. Rai 98yyyyyyyy
2 Hari Thapa 45 Wd 3 Banepa 98xxxxxxxx Dr. Rai 98yyyyyyyy
3 Mina KC 30 Ward 1, Dhulikhel 98zzzzzzzz Dr. Rai 98yyyyyyyy
Answer
c.

Using the school database (Figure 3.1), write SQL queries for:

  • i.the names of all students in 9B, in alphabetical order;
  • ii.the number of students from each town;
  • iii.the highest mark in Nepali;
  • iv.the names and Science marks of students who scored under 40 in Science;
  • v.each class’s average mark over all subjects.
Answer
d.

What is the difference between WHERE and HAVING? Give a query that uses both.

Answer
e.

Write a data dictionary for a table of mobile recharges sold by a shop: recharge ID, mobile number, amount in rupees, date and time, and network (one of the operators in Nepal). Include the allowed values and keys, and one rule that a database constraint can’t check.

Answer
f.

Evaluate four places a school could check that every mark is a whole number from 0 to 100: the form the teachers type into, the program that saves the marks, a CHECK constraint in the database, and a query run after the marks are in. Which would you use, and why?

Answer
g.

A results website searches 500,000 students by symbol number. Explain why it answers quickly, and what would happen if it searched by name with no index on the name.

Answer
h.

Explain why the school database groups by students.student_id, not by students.name, when it adds up each student’s marks.

Answer