A relational database organises data into structured tables linked together by shared keys. In Leaving Certificate Computer Science, you need to understand how to choose suitable data types for each field, how primary and foreign keys reduce data redundancy and the inconsistencies it causes, how to write standalone SQL queries to create and retrieve data, and how to connect databases to Python applications.
File Systems vs Relational Databases
In a file-based system, data is kept in separate flat files, such as CSV or plain text files, where each individual program manages and reads its own files. Storing records this way quickly runs into trouble as systems grow.
A relational database stores all data in linked tables managed by a single Database Management System (DBMS). Using a relational database provides several distinct advantages:
- Less redundancy: Each fact is stored once and linked using keys.
- Better consistency: When a detail changes, updating it in one place updates it across the entire system.
- Easy retrieval: You can search, filter, sort, and combine data with SQL without writing a bespoke file-parsing program for every query.
- Integrity rules: Constraints like primary keys, foreign keys, and
NOT NULLrules automatically block invalid or orphan records. - Sharing and security: Multiple users can query data simultaneously, with permissions restricting who can view or alter sensitive tables.
Tables, Fields, and Procedural Data Types
A database provides persistent storage. In a relational database, data is organised into two-dimensional tables, historically called relations. Each table stores data about a single entity, such as a student, a book, or an order.
Textbooks and exam questions use different names for the same thing, so learn each pair:
| Primary Term | Alternative Name | Meaning in Practice | Example |
|---|---|---|---|
| Table | Relation | A structure holding data about one entity | Students |
| Field | Attribute / Column | A specific property of data with a set type | date_of_birth |
| Record | Tuple / Row | A single complete entry across all fields | (101, 'Aoife', 'Kelly') |
Note on terminology: In database theory, a single row is called a tuple. In Python programming, a tuple is an immutable sequence written with parentheses, such as (101, 'Aoife'). Python's database library returns each database record as a Python tuple.
Each field should be given a data type that suits its data. Most database engines enforce these types strictly to reject invalid input. SQLite is more relaxed: it stores booleans as 1 or 0 and dates as text, so you must keep your dates in YYYY-MM-DD format yourself.
Under Learning Outcome 2.16, you must know how procedural types map to database fields:
INTEGER: Whole numbers, such asstudent_idorquantity.REAL: Numbers with a fractional decimal part, such asexam_markorprice.TEXT/VARCHAR: Variable-length strings, such assurnameoreircode.CHAR: A fixed-length string. For example,CHAR(1)stores a single character such as a status flag'Y', whileCHAR(2)could store a Leaving Certificate grade such as'H1'.BOOLEAN: Logical truth values (TRUEorFALSE).DATE: Calendar dates. Storing them in the ISO formatYYYY-MM-DDallows chronological sorting directly using standard text or numeric comparisons.- Arrays: A field in a relational database should store an atomic value, not a list. To store an array or list of items (for instance, all the subjects a student takes), create a separate linked table with one row per item, such as
StudentSubjects(student_id, subject_code).
Primary Keys, Foreign Keys, and Creating Tables
To locate records quickly and connect separate tables without ambiguity, relational databases rely on keys.
Primary Key
A primary key is a field, or combination of fields, that uniquely identifies each individual record in a database table. A primary key must satisfy two strict rules:
- It must be unique for every record.
- It can never be empty (
NULL).
Good practice (not an enforced database rule): Choose an identifier that will not change over time, such as an allocated ID number rather than a phone number or email address.
Names and breeds are never suitable primary keys. Multiple students can share the surname Byrne, and thousands of dogs share the Golden Retriever breed. Instead, use a field guaranteed to be unique. This can be an identifier that already exists in the real world, such as a dog's microchip number, or a number generated by the system, such as student_id.
Composite Primary Key
When no single column is unique on its own, two or more columns can be combined to form a composite primary key. For example, in a table tracking student subject choices (exam_number, subject_code, level), neither exam_number nor subject_code is unique on its own because a student sits multiple subjects and many students take Computer Science. The pair (exam_number, subject_code) together forms a unique composite key for each row.
Foreign Key
A foreign key is a field in one table that references the primary key of another table, creating an explicit relationship between them.
Creating Tables in SQL
Before storing data, you define the schema of each table:
CREATE TABLE Owners (
owner_id INTEGER PRIMARY KEY,
owner_name TEXT NOT NULL,
owner_phone TEXT
);
CREATE TABLE Pets (
pet_id INTEGER PRIMARY KEY,
pet_name TEXT NOT NULL,
animal_type TEXT,
date_of_birth DATE,
vaccinated BOOLEAN,
owner_id INTEGER,
FOREIGN KEY (owner_id) REFERENCES Owners(owner_id)
);Notice that owner_phone is defined as TEXT, not INTEGER. Phone numbers, Eircodes, and PPS numbers should always be stored as text because an integer strips leading zeros (turning '0871234567' into 871234567) and you never perform arithmetic on them.
Data Redundancy and Data Inconsistency
Storing everything in one single flat file table causes two main problems.
Data redundancy is when the same piece of data is stored in more than one place in a database. It is usually the result of poor database design. Repeating the same details across multiple rows wastes storage space and makes maintenance error-prone.
Data inconsistency occurs when conflicting versions of what should be the same data exist in the database. Redundancy causes update inconsistencies: if author Arthur Conan Doyle changes his phone number, an administrator might update two book entries but overlook a third. The database now holds contradictory contact numbers for the same person.
Splitting tables so each entity is stored once reduces redundancy and stops update inconsistencies, so a change to an author's details is made in one row only.
However, not every inconsistency comes from redundancy. In Leaving Certificate exam questions, you are often asked to spot discrepancies in unvalidated data:
| Inconsistency Found | Why It Happened | How to Prevent It |
|---|---|---|
'€10' vs '8.95' in cost | Free-text entry without a data type | Store cost as REAL (10.00, 8.95). Add the currency symbol in the user interface when displayed. |
'02/03/1904' vs 'March 2, 1904' | Dates entered as unvalidated text | Store as DATE using the ISO standard YYYY-MM-DD (1904-03-02). |
'Y' vs 'Yes' in on_loan | No restriction on allowed input | Store as BOOLEAN (TRUE/FALSE) or validate input so only 'Y' or 'N' is accepted. |
'Dr. Seuss' vs 'Doc Seus' | Author name retyped for each book (redundancy) | Store the author once in an Authors table and link it with an author_id. |
Splitting tables solves the author name problem. The other three are solved by choosing the correct data type and validating user input.
Writing SQL Queries and Joins
Structured Query Language (SQL) is the standard declarative language used to query and update relational databases. On written examination papers, write clauses in their standard order:
SELECT column1, column2
FROM table_name
WHERE condition
ORDER BY column1 ASC;Conditions in WHERE use comparison operators (=, <, >, <=, >=, and <> or != for not equal), combined with AND or OR.
Consider this sample Students table:
| student_id | surname | first_name | year_group |
|---|---|---|---|
| 101 | Kelly | Aoife | 6 |
| 102 | Walsh | Ciaran | 5 |
| 103 | Byrne | Darragh | 6 |
| 104 | Murphy | Elena | 5 |
Running this query:
SELECT student_id, surname, first_name
FROM Students
WHERE year_group = 6
ORDER BY surname ASC;Trace:
FROM Studentsinspects the table.WHERE year_group = 6filters the rows, selecting records 101 and 103.ORDER BY surname ASCsorts them alphabetically by surname: Byrne comes before Kelly.- Output:
(103, 'Byrne', 'Darragh')(101, 'Kelly', 'Aoife')
Retrieving Data from Linked Tables
A foreign key lets you combine rows from two related tables in a single query using JOIN:
SELECT Pets.pet_name, Owners.owner_name, Owners.owner_phone
FROM Pets
JOIN Owners ON Pets.owner_id = Owners.owner_id
WHERE Pets.animal_type = 'Dog';The query matches each pet with its owner by equating Pets.owner_id with Owners.owner_id. It filters for dogs, outputting the pet's name alongside the owner's contact details, even though they live in separate tables.
Modifying Data
- Adding a record:
INSERT INTO Students (student_id, surname, first_name, year_group)
VALUES (105, 'O''Connor', 'Niamh', 5);Notice that an apostrophe in a text value like O'Connor is escaped by doubling it ('O''Connor').
- Updating records:
UPDATE Students
SET year_group = 6
WHERE student_id = 105;Always include a WHERE clause when updating. Omitting it updates every single row in the table.
- Deleting records:
DELETE FROM Students
WHERE student_id = 105;Database Programming in Python with sqlite3
Applied Learning Task 1 (ALT 1) asks you to build an interactive website that displays information from a database. Any language may be used for the applied learning tasks, but Python is the language assessed in the end-of-course examination, so this note uses Python's built-in sqlite3 module.
Connecting to a database in Python follows six clear steps:
- Connect: Establish a connection using
conn = sqlite3.connect("school.db"). - Cursor: Create a cursor to run commands using
cur = conn.cursor(). - Execute: Send the SQL statement using
cur.execute(query, params). - Fetch: Read results using
cur.fetchall()(returns a list of tuples) orcur.fetchone()(returns one tuple, orNoneif no match was found). - Commit: If you changed data using
INSERT,UPDATE, orDELETE, callconn.commit()to save your changes. - Close: Release the database lock using
conn.close().
import sqlite3
conn = sqlite3.connect("school.db")
cur = conn.cursor()
# Parameterised query using ? placeholder
year = 6
cur.execute("SELECT surname, first_name FROM Students WHERE year_group = ?", (year,))
students = cur.fetchall()
for student in students:
surname, first_name = student
print(f"{surname}, {first_name}")
conn.close()In web development (LO 3.3), that list of tuples is what your script loops through to build an HTML table or list for the user.
When passing variables into queries, always use the ? placeholder tuple. The execute() method treats the variable strictly as data, preventing attackers from injecting malicious SQL commands into your system.
Key terms
- Relational Database
- A database that stores information across multiple two-dimensional tables linked by shared keys.
- Primary Key
- A field or set of fields that uniquely identifies each record in a table, which cannot contain null values.
- Foreign Key
- A field in one table that references the primary key in another table to establish a link between them.
- Composite Key
- A primary key composed of two or more columns combined to create a unique identifier for a row.
- Data Redundancy
- Storing the same piece of data in more than one place, usually resulting from poor design and leading to wasted space and inconsistency.
- Data Inconsistency
- A state where different copies of the same data show conflicting values or formats due to redundancy or unvalidated input.
- Flat File
- A single data table or file without relational links, often leading to repeated data and update errors.
- Field
- A column in a database table representing a specific attribute of an entity.
- Record
- A single row in a database table containing all attribute values for one entity instance.
- SQL
- Structured Query Language; the standard declarative programming language used to manage and query relational databases.
- Commit
- An instruction (
conn.commit()) that permanently saves all changes made during the current database transaction.
Check yourself
What two rules must every primary key satisfy?
It must be unique for every record, and it can never be empty (null).
In a sample table, one record lists cost as '€12' and another as '9.50', while one date is '12/04/2023' and another is 'April 12, 2023'. State the two inconsistencies and the data types that prevent them.
1. Inconsistent currency format in cost; prevent by setting the data type to REAL and storing numeric values. 2. Inconsistent date style; prevent by setting the data type to DATE in standard YYYY-MM-DD format.
Why would breed not be a suitable primary key in a table of dogs?
A primary key must be unique for every row, but many dogs can share the same breed (such as Labrador).
What is the difference between a primary key and a foreign key?
A primary key uniquely identifies a record within its own table. A foreign key is a field in another table that references that primary key to link the two tables.
What does cur.fetchone() return if no matching records are found in the database?
It returns None, so you should check if the result is None before trying to unpack or read its values.
