What you'll practice: modelling data across multiple tables. You'll add
PRIMARY KEYandFOREIGN KEYconstraints, understand one-to-many relationships, see what happens when foreign keys are violated, and useON DELETEto decide how referenced rows should behave when the parent row is removed.
- Add a
PRIMARY KEYconstraint withALTER TABLE - Add a
FOREIGN KEYconstraint thatREFERENCESanother table - Understand the difference between 1-to-1, 1-to-many, and many-to-many
- Insert rows that respect a foreign key
- Trigger — and understand — foreign key violations
- Use
ON DELETE CASCADE,ON DELETE SET NULL, andON DELETE RESTRICT
psql -U postgres -d sql_exercise \
-f 02-sql-fundamentals/06-relationships/schema.sql
psql -U postgres -d sql_exercise \
-f 02-sql-fundamentals/06-relationships/seed.sqlSET search_path TO sqlf_relationships;
SELECT COUNT(*) FROM person; -- expect 20
SELECT COUNT(*) FROM car; -- expect 25The loaded schema.sql already includes the constraints. The exercises below walk you through building them up on throwaway tables inside the same schema so you see every step. Don't worry — your real person and car tables won't be touched.
Run \d person and \d car in psql.
Question: in the car table, what does the line owner_id | integer | ... REFERENCES person(id) ON DELETE SET NULL tell you?
Create a table called author_tmp with id INTEGER and name VARCHAR(100). Then add a primary key constraint on id using ALTER TABLE.
Hint:
CREATE TABLE author_tmp (id INTEGER, name VARCHAR(100) NOT NULL);
ALTER TABLE author_tmp ADD CONSTRAINT pk_author PRIMARY KEY (id);Verify: \d author_tmp.
Insert (1, 'Robert Martin') and (2, 'Martin Fowler') into author_tmp.
Insert (1, 'Duplicate Bob') into author_tmp.
Question: what error do you get? Which constraint enforced it?
Insert (NULL, 'Anonymous') into author_tmp.
Question: why does this fail even though you never wrote NOT NULL on id?
Create:
CREATE TABLE book_tmp (
id INTEGER PRIMARY KEY,
title VARCHAR(200) NOT NULL,
author_id INTEGER
);Now add a foreign key on author_id that references author_tmp(id).
Hint:
ALTER TABLE book_tmp
ADD CONSTRAINT fk_book_author FOREIGN KEY (author_id) REFERENCES author_tmp(id);Insert (1, 'Clean Code', 1) into book_tmp.
Verify:
SELECT * FROM book_tmp WHERE id = 1;Try to insert (2, 'Mystery Book', 999) into book_tmp.
Question: what error do you get? Why is it protecting you?
Try DELETE FROM author_tmp WHERE id = 1;.
Question: what error do you get, and why? This is ON DELETE behaviour being the default — NO ACTION.
Drop book_tmp, recreate it with the foreign key clause inline:
DROP TABLE book_tmp;
CREATE TABLE book_tmp (
id INTEGER PRIMARY KEY,
title VARCHAR(200) NOT NULL,
author_id INTEGER REFERENCES author_tmp(id) ON DELETE CASCADE
);
INSERT INTO book_tmp VALUES (1, 'Clean Code', 1), (2, 'Refactoring', 2);Now DELETE FROM author_tmp WHERE id = 1;.
Verify: SELECT * FROM book_tmp; — "Clean Code" should be gone too. That's cascade.
DROP TABLE book_tmp;
DROP TABLE author_tmp;Using the loaded person and car tables, list every car with its owner_id. Show car.id, brand, model, owner_id.
Hint: no joins needed — owner_id is already on the car row.
Return every car where owner_id IS NULL. These are unassigned stock.
Verify: the seed has 3 such cars.
Group car by owner_id and return the count per owner, sorted by count descending.
Hint: GROUP BY owner_id.
Question: do unassigned cars (NULL owner) show up as a row? What does that row mean?
Insert (99, 'Mystery', 'Car', 2023, 50000.00, 999) into car. What happens?
The car.owner_id foreign key is declared ON DELETE SET NULL. Delete person id = 1 (Ahmed) and then check what happened to Ahmed's cars.
Safety: wrap this in a transaction so you can ROLLBACK and keep the seed intact:
BEGIN;
DELETE FROM person WHERE id = 1;
SELECT id, brand, model, owner_id FROM car WHERE id IN (1, 2, 25);
ROLLBACK;Verify after the ROLLBACK: Ahmed's cars should be back to owner_id = 1.
Drop and recreate book_tmp so the primary key is (id, author_id) instead of just id. When does a composite key actually make sense?
Add a column manager_id INTEGER REFERENCES person(id) to the person table (inside a transaction you'll ROLLBACK), then figure out how you'd query "everyone reporting directly to person 7."
Sketch a schema for books and authors where a book can have multiple authors and an author can write multiple books. What extra table do you need, and what are its columns?
DROP SCHEMA sqlf_relationships CASCADE;