Course outline · 0% complete

0/29 lessons0%

Course overview →

Constraints: Rules the Database Enforces

lesson 7-3 · ~13 min · 21/29

Teaching the table to say no

In lesson 1-2 we promised that a database can refuse bad data. Constraints are those rules, declared right in CREATE TABLE:

CREATE TABLE users (
  id INTEGER PRIMARY KEY,
  email TEXT NOT NULL UNIQUE,
  age INTEGER CHECK (age >= 13),
  plan TEXT DEFAULT 'free'
);
  • PRIMARY KEY: this column uniquely identifies each row. In SQLite an INTEGER PRIMARY KEY also auto-numbers itself when you do not supply a value.
  • NOT NULL: the value may never be missing.
  • UNIQUE: no two rows may share a value. Perfect for emails and usernames.
  • CHECK (...): an arbitrary test every row must pass.
  • DEFAULT ...: the value used when an INSERT does not provide one.

A constraint violation makes the INSERT or UPDATE fail with an error instead of storing bad data. The mistake is stopped at the door.

Code exercise · sql

Run it. Neither INSERT mentions id, yet both rows get one automatically. Ana never chose a plan, so the DEFAULT filled in 'free'.

Code exercise · sql

Now watch constraints reject bad data. Run this as-is and READ THE ERRORS: the duplicate email violates UNIQUE, the 9-year-old violates the CHECK. Notice the final SELECT still runs and shows that only Ana made it in. (This block is expected to error, so there is no pass/fail check.)

The constraint between tables: FOREIGN KEY

Unit 5's foreign keys have been an honor system so far: nothing stopped an orders row from pointing at customer 9 when no customer 9 exists. Such a row is called an orphan, a pointer to nothing, and it silently breaks every join that touches it. Declaring the relationship turns the honor system into an enforced rule:

customer_id INTEGER NOT NULL REFERENCES customers(id)

REFERENCES customers(id) means every value stored in this column must exist in customers.id. An INSERT that points nowhere is rejected, and deleting a customer who still has orders is rejected too, so the two tables can never fall out of agreement.

One SQLite quirk: for historical reasons this checking is off by default, so scripts switch it on first with PRAGMA foreign_keys = ON;. Server databases like PostgreSQL and MySQL enforce it always.

Code exercise · sql

Run it and read the error. The first order points at a real customer and gets in. The second points at customer 9, who does not exist, so the FOREIGN KEY constraint rejects it, and the final SELECT proves only the valid order was stored. (This block is expected to error, so there is no pass/fail check.)

Quiz

Signups sometimes arrive with no email at all, and your app code has a bug that inserts them anyway. Which constraint stops the bad rows at the database?

Code exercise · sql

Your turn. Write the CREATE TABLE for a products table: id INTEGER PRIMARY KEY, name TEXT NOT NULL, price INTEGER with a CHECK that price > 0, and stock INTEGER DEFAULT 0. Then the given INSERTs will work and the SELECT will match.