Skip to main content
All SQL lessons

Level 3 of 13 · Every row gets a passport

SQL Primary Keys and Foreign Keys Explained for Beginners

Why every row needs an id, what AUTO_INCREMENT does, and how a foreign key stops one table pointing at a row that does not exist.

Covers: PRIMARY KEY · AUTO_INCREMENT · FOREIGN KEY · REFERENCES

The lesson

Every example runs against the same small pizza shop database: pizzas, customers, orders and staff.

# What are keys? A key is a column that identifies rows. A primary key is a row's own id. A foreign key is a column holding the id of a row in another table. Why you need them: two customers can have the same name, so the database needs a value that tells every row apart. And an order has to say which customer placed it, without copying the customer's name into every order. - `PRIMARY KEY` the row's own unique id, like id in orders - `FOREIGN KEY` a column that points at another table's primary key, like customer_id in orders Here every order has its own id, and its customer_id points at a row in customers. > Keys are what JOINs follow later on: orders.customer_id = customers.id.

SELECT id, customer_id, qty
FROM orders
LIMIT 3

# Primary key: a unique id for every row Two customers can have the same name. A primary key gives each row a value no other row has, so you can always point at exactly one row. - `PRIMARY KEY` unique, and never empty > Use a plain number that means nothing else as the key. Names repeat and emails change; an id never has to.

CREATE TABLE drivers (
  id INT PRIMARY KEY,
  name VARCHAR(50));
INSERT INTO drivers
VALUES (1, 'Zoe'), (2, 'Max');
SELECT * FROM drivers

# Duplicate keys are refused - `INSERT INTO drivers VALUES (1, 'Zed')` fails: id 1 is already taken You'll see "UNIQUE constraint failed": you broke the rule that each key appears once. MySQL words it as "Duplicate entry '1' for key 'PRIMARY'". > In an interview, "what is a primary key?" wants two words: unique and not null.

CREATE TABLE drivers (
  id INT PRIMARY KEY,
  name VARCHAR(50));
INSERT INTO drivers
VALUES (1, 'Zoe');
INSERT INTO drivers
VALUES (1, 'Zed')

# AUTO_INCREMENT: the database numbers the rows - `AUTO_INCREMENT` each new row gets the next number: 1, 2, 3 - `INSERT INTO drivers (name)` leave id out, and the database fills it in > A number is never reused. Delete row 3 and the next new row still gets 4, so gaps in ids are normal.

CREATE TABLE drivers (
  id INT AUTO_INCREMENT PRIMARY KEY,
  name VARCHAR(50)
);
INSERT INTO drivers (name)
VALUES ('Zoe'), ('Max'), ('Ali');
SELECT * FROM drivers

# Foreign key: a link to another table The orders table doesn't store the customer's name. It stores customer_id: the id of a row in customers. - `FOREIGN KEY` a column that holds another table's primary key - `customer_id` in orders points at id in customers "Foreign" just means "from another table". > Most JOINs you'll write follow a foreign key: orders.customer_id = customers.id.

SELECT id, customer_id, qty
FROM orders

# The foreign key rule A foreign key must point at a row that exists. There is no customer 99, so this INSERT is refused with "FOREIGN KEY constraint failed". - `constraint` a rule the database enforces > The database checks this rule, not your app. Even a buggy script can't create an order for a customer who doesn't exist.

INSERT INTO orders (id, customer_id,
  pizza_id, qty, status)
VALUES (13, 99, 1, 1, 'delivered')

# Create a foreign key - `FOREIGN KEY (pizza_id)` this column is a foreign key - `REFERENCES pizzas(id)` it must match an id in the pizzas table Now every review has to be about a real pizza. > Joins run on foreign key columns, so they need an index. MySQL adds one for you; most other databases don't.

CREATE TABLE reviews (
  id INT PRIMARY KEY,
  pizza_id INT,
  stars INT,
  FOREIGN KEY (pizza_id) REFERENCES
    pizzas(id)
);
INSERT INTO reviews
VALUES (1, 2, 5);
SELECT * FROM reviews

Appian corner

Every record type needs a primary key: Appian uses it to find, update and delete records. With an AUTO_INCREMENT key, Write Records creates a new row when you leave the key empty, and updates the existing row when you pass one. Record type relationships join on these same columns, a many-to-one from Order to Customer on customer_id.

Practice questions

Try each one before opening the answer. To write and run them against the database in your browser, practise the level.

  1. 1. What makes the best primary key for customers?

    • The customer's name
    • An id that is unique and never changes
    • The city
    • The email, because it never changes
    Show the answer

    An id that is unique and never changes. Names repeat and emails change. A plain id that means nothing else is the safest key.

  2. 2. drivers has id as its PRIMARY KEY and already holds id 1. What happens?

    INSERT INTO drivers
    VALUES (1, 'Zed')
    • A second row with id 1 is added
    • Zoe is replaced by Zed
    • An error: id 1 is already taken
    • Zed gets id 2
    Show the answer

    An error: id 1 is already taken. A primary key must be unique, so the database refuses the duplicate. MySQL says "Duplicate entry '1' for key 'PRIMARY'".

  3. 3. Make id the primary key.

    CREATE TABLE drivers (
      id INT ___ ___,
      name VARCHAR(50))
    Show the answer
    CREATE TABLE drivers (
      id INT PRIMARY KEY,
      name VARCHAR(50))

    Two words: the kind of key, then KEY.

  4. 4. Create a table drivers where id is a primary key that numbers itself (AUTO_INCREMENT) and name is text up to 50. Then add 'Zoe' and 'Max' without giving ids.

    Show the answer
    CREATE TABLE drivers (
      id INT AUTO_INCREMENT PRIMARY KEY,
      name VARCHAR(50)
    );
    INSERT INTO drivers (name)
    VALUES ('Zoe'), ('Max')

    id INT AUTO_INCREMENT PRIMARY KEY, then INSERT INTO drivers (name) VALUES ('Zoe'), ('Max')

  5. 5. This order fails: there is no customer 99, so the foreign key refuses it. It was meant for George, customer 7. Fix it.

    INSERT INTO orders (id, customer_id,
      pizza_id, qty, status)
    VALUES (13, 99, 1, 1, 'delivered')
    Show the answer
    INSERT INTO orders (id, customer_id,
      pizza_id, qty, status)
    VALUES (13, 7, 1, 1, 'delivered')

    customer_id has to be an id that exists in customers: 7.

  6. 6. Boss question! Create a table reviews: id (primary key), pizza_id (whole number, must point at a real pizza) and stars (whole number). Then add review 1: pizza 2, 5 stars.

    Show the answer
    CREATE TABLE reviews (
      id INT PRIMARY KEY,
      pizza_id INT,
      stars INT,
      FOREIGN KEY (pizza_id) REFERENCES
        pizzas(id));
    INSERT INTO reviews VALUES (1, 2, 5)

    Finish the column list with FOREIGN KEY (pizza_id) REFERENCES pizzas(id), or write pizza_id INT REFERENCES pizzas(id).