MySQL auto_increment

MySQL contains a feature for primary key columns called auto increment. A table column that has the AUTO_INCREMENT attribute causes a MySQL database to automatically store unique, strictly increasing integers in that column. Consider the following table definition:

DROP TABLE IF EXISTS customer;

CREATE TABLE IF NOT EXISTS customer (
    customer_id INT NOT NULL AUTO_INCREMENT,
    given_name VARCHAR(45) NOT NULL,
    family_name VARCHAR(45) NOT NULL,
    PRIMARY KEY (customer_id)
) ENGINE = InnoDB;

Because of the AUTO_INCREMENT attribute on the customer_id column, when we write an INSERT statement, we don’t have to give a value for the customer_id column.

INSERT INTO customer
(given_name, family_name)
VALUES
('Adrian', 'Baker'),
('Gianna', 'Santos'),
('Kevin', 'Wong');

SELECT * FROM customer;
customer_id given_name family_name
1 Adrian Baker
2 Gianna Santos
3 Kevin Wong

AUTO_INCREMENT is a nice feature that can save a developer some work. However, it is a mixed blessing. Consider trying to write an INSERT statement for a child table of customer, such as an order table.

DROP TABLE IF EXISTS `order`;

CREATE TABLE IF NOT EXISTS `order` (
    order_id INT NOT NULL AUTO_INCREMENT,
    customer_id INT NOT NULL,
    order_date DATE NOT NULL,
    ship_date DATE NOT NULL,
    PRIMARY KEY (order_id),
    CONSTRAINT fk_customer_id
        FOREIGN KEY (customer_id)
        REFERENCES customer (customer_id)
        ON DELETE RESTRICT
        ON UPDATE CASCADE
) ENGINE = InnoDB;

When we write the INSERT statement, how do we know what foreign key value to use? In other words, the INSERT statement for the customer table does not include primary key values, so if we want to make an order for Gianna Santos, we have to count or guess which primary key to use in the INSERT statement for the order table.

INSERT INTO `order`
(customer_id, order_date, ship_date)
VALUES
(2, '2022-07-15', '2022-07-18');

One possibility is to explicitly write the primary key values in the INSERT statement. Even though the primary key column has the AUTO_INCREMENT attribute, a developer can still explicitly write the primary keys. Then the developer will not have to count or guess the value to use for the foreign key column.

INSERT INTO customer
(given_name, family_name)
VALUES
(1, 'Adrian', 'Baker'),
(2, 'Gianna', 'Santos'),
(3, 'Kevin', 'Wong');

SELECT * FROM customer;
customer_id given_name family_name
1 Adrian Baker
2 Gianna Santos
3 Kevin Wong

Instead of counting or guessing a foreign key value, another possibility is to use a SELECT query to find the correct foreign key value.

INSERT INTO `order`
(customer_id, order_date, ship_date)
VALUES
((SELECT customer_id FROM customer
    WHERE given_name = 'Gianna' AND family_name = 'Santos'),
    '2022-07-15', '2022-07-18');

SELECT order_id, customer_id, given_name, family_name,
    order_date, ship_date
FROM `order` AS o INNER JOIN customer AS c
    ON c.customer_id = o.customer_id;
order_id customer_id given_name family_name order_date ship_date
1 2 Gianna Santos 2022-07-15 2022-07-18

One of the strange features of AUTO_INCREMENT is that even if we delete all the rows from a table, the next sequential value for an auto incremented column does not reset.

DELETE FROM `order`;
DELETE FROM customer;

INSERT INTO customer
(given_name, family_name)
VALUES
('Adrian', 'Baker'),
('Gianna', 'Santos'),
('Kevin', 'Wong');

SELECT * FROM customer;
customer_id given_name family_name
4 Adrian Baker
5 Gianna Santos
6 Kevin Wong

We can use an ALTER command to reset the AUTO_INCREMENT value.

DELETE FROM `order`;
DELETE FROM customer;

ALTER TABLE `order` AUTO_INCREMENT = 1;
ALTER TABLE customer AUTO_INCREMENT = 1;

INSERT INTO customer
(given_name, family_name)
VALUES
('Adrian', 'Baker'),
('Gianna', 'Santos'),
('Kevin', 'Wong');

SELECT * FROM customer;
customer_id given_name family_name
1 Adrian Baker
2 Gianna Santos
3 Kevin Wong

We may be interested to see if there are any gaps between primary key values. We can use a self join to find gaps if any exist.

DELETE FROM customer
WHERE customer_id = 2;

SELECT a.customer_id,
    MIN(b.customer_id) - a.customer_id AS gap
FROM customer AS a INNER JOIN customer AS b
    ON a.customer_id < b.customer_id
GROUP BY a.customer_id
HAVING gap > 1
ORDER BY a.customer_id;
customer_id gap
1 2