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 |