#INSERT
AddedINSERT adds new rows to a table. You name the table, optionally the columns you're supplying, and one or more rows of values. Every column you leave out gets its DEFAULT (or NULL, or the next AUTO_INCREMENT number); every constraint — NOT NULL, UNIQUE, foreign keys, CHECK, column types — is checked before the row goes in.
A good mental model is filling in a form that the table designed. The column list is the set of boxes you're filling in; boxes you skip get the form's pre-printed default; and the clerk (InnoDB in strict mode) refuses the whole form if a required box is empty or a value doesn't fit.
INSERT INTO customers (name, email, city, created_at)
VALUES ('Ava', 'ava@example.com', 'Paris', '2025-05-01 10:00:00'),
('Kenji', 'kenji@example.com', NULL, '2025-05-02 11:30:00');
SELECT LAST_INSERT_ID();
SELECT id, name, email, city FROM customers WHERE id >= 6;Query OK, 2 rows affected (0.003 sec)
Records: 2 Duplicates: 0 Warnings: 0
+------------------+
| LAST_INSERT_ID() |
+------------------+
| 7 |
+------------------+
1 row in set (0.000 sec)
+----+-------+-------------------+-------+
| id | name | email | city |
+----+-------+-------------------+-------+
| 6 | Zara | zara@example.com | Dubai |
| 7 | Ava | ava@example.com | Paris |
| 8 | Kenji | kenji@example.com | NULL |
+----+-------+-------------------+-------+
3 rows in set (0.000 sec)- The column list
(name, email, city, created_at)leaves outid, so InnoDB assigns the nextAUTO_INCREMENTvalues. The sample data ends at customer 6 (Zara), so Ava gets7and Kenji8. - One statement inserts two rows:
Query OK, 2 rows affectedandRecords: 2 Duplicates: 0 Warnings: 0. A multi-rowINSERTis atomic — if the second row had failed, neither would be inserted. LAST_INSERT_ID()returns7: for a multi-row insert it's the id of the first row inserted, not the last. It's per connection, so another client's inserts don't change your value.- Kenji's
cityis explicitlyNULL, which is allowed because the column is nullable. Leavingcityout of the column list would have done the same, since its default isNULL. - Always write the column list.
INSERT INTO customers VALUES (…)without it depends on the table's column order and breaks the day someone adds a column.
What strict mode rejects
MySQL 9.7 runs with STRICT_TRANS_TABLES in the default sql_mode, so bad data is an error, not a silent adjustment. Four common failures:
INSERT INTO customers (name, city, created_at)
VALUES ('NoEmail', 'Oslo', '2025-05-03 09:00:00');
INSERT INTO customers (name, email, created_at)
VALUES (REPEAT('x', 60), 'long@example.com', '2025-05-03 09:00:00');
INSERT INTO orders (customer_id, status, ordered_at)
VALUES (1, 'refunded', '2025-05-03 09:00:00');
INSERT INTO orders (customer_id, status, ordered_at)
VALUES (99, 'paid', '2025-05-03 09:00:00');ERROR 1364 (HY000): Field 'email' doesn't have a default value
ERROR 1406 (22001): Data too long for column 'name' at row 1
ERROR 1265 (01000): Data truncated for column 'status' at row 1
ERROR 1452 (23000): Cannot add or update a child row: a foreign key constraint fails (`ch04`.`orders`, CONSTRAINT `orders_ibfk_1` FOREIGN KEY (`customer_id`) REFERENCES `customers` (`id`))emailisNOT NULLwith no default and wasn't supplied:ERROR 1364 (HY000): Field 'email' doesn't have a default value.nameisVARCHAR(50)and we sent 60 characters:ERROR 1406 (22001): Data too long for column 'name' at row 1. Without strict mode MySQL would have cut it to 50 characters and only warned.'refunded'isn't one of theENUMvalues:ERROR 1265 (01000): Data truncated for column 'status' at row 1— a confusing message for "invalid enum value". In non-strict mode the row would be stored withstatus = ''.- Customer 99 doesn't exist, so the foreign key on
orders.customer_idrejects it withERROR 1452 (23000): Cannot add or update a child row: a foreign key constraint fails …. Foreign keys are enforced regardless ofsql_mode. - Every one of these would have been a quiet data-quality bug on a MySQL 5.6 server (strict mode became the default in 5.7). If you inherit an old server, check
SELECT @@sql_mode.
Duplicates and AUTO_INCREMENT gaps
A UNIQUE or primary key violation is ERROR 1062. Note what it does to the id sequence:
INSERT INTO customers (name, email, created_at)
VALUES ('Rohit again', 'rohit@example.com', '2025-05-04 08:00:00');
INSERT INTO customers (name, email, created_at)
VALUES ('Lena', 'lena@example.com', '2025-05-04 08:05:00');
SELECT id, name FROM customers WHERE id >= 6;ERROR 1062 (23000): Duplicate entry 'rohit@example.com' for key 'customers.email'
Query OK, 1 row affected (0.002 sec)
+----+-------+
| id | name |
+----+-------+
| 6 | Zara |
| 7 | Ava |
| 8 | Kenji |
| 10 | Lena |
+----+-------+
4 rows in set (0.000 sec)rohit@example.comalready exists, so the first insert fails:ERROR 1062 (23000): Duplicate entry 'rohit@example.com' for key 'customers.email'. The key name tells you which unique index was hit.- Lena is inserted next. Ava and Kenji (7 and 8) came from the first example in this topic, so you might expect Lena to be
9— but she's10. - InnoDB handed out
9to the failed insert before the unique check rejected it, and a failed insert doesn't give its value back, so the sequence skipped. - Gaps also come from rolled-back transactions,
INSERT … ON DUPLICATE KEY UPDATEand bulk inserts that over-reserve.AUTO_INCREMENTpromises unique, increasing ids — never gap-free ones. Don't use it for invoice numbers that must be consecutive.
Other INSERT forms
INSERT INTO categories SET id = 6, name = 'Tablets', parent_id = 1;
INSERT INTO products (name, category_id, price)
VALUES ('Pixel Tablet', 6, 499.00);
SELECT id, name, category_id, price, stock FROM products WHERE category_id = 6;Query OK, 1 row affected (0.003 sec)
Query OK, 1 row affected (0.002 sec)
+----+--------------+-------------+--------+-------+
| id | name | category_id | price | stock |
+----+--------------+-------------+--------+-------+
| 9 | Pixel Tablet | 6 | 499.00 | 0 |
+----+--------------+-------------+--------+-------+
1 row in set (0.000 sec)INSERT … SET col = value, …is MySQL's alternative syntax for a single row; it reads like anUPDATEand is easy to keep in sync with the column names. It's MySQL-only.- The second insert leaves out
stock, so the column'sDEFAULT 0applies. You can also write the keywordDEFAULTin aVALUESlist to ask for it explicitly. - The new product gets the next id after the eight sample products:
9. - MySQL has no
INSERT … RETURNING(PostgreSQL and MariaDB do). In MySQL you readLAST_INSERT_ID()(or the driver'sinsertId) and query again if you need defaults or generated columns.
INSERT INTO departments (id, name) VALUES (5, 'Legal'), (6, 'Sales'), (7, 'Design');
SELECT COUNT(*) FROM departments;Show answer
The insert fails and COUNT(*) is still 4.
INSERT INTO departments (id, name) VALUES (5, 'Legal'), (6, 'Sales'), (7, 'Design');
SELECT COUNT(*) FROM departments;ERROR 1062 (23000): Duplicate entry 'Sales' for key 'departments.name'
+----------+
| COUNT(*) |
+----------+
| 4 |
+----------+
1 row in set (0.000 sec)departments.nameisUNIQUE, and'Sales'already exists (department 2).- The second row fails with
ERROR 1062 (23000): Duplicate entry 'Sales' for key 'departments.name'. - InnoDB rolls back the whole statement, including the
Legalrow that was already written, so no rows are added. - The count stays at the original four. To insert the good rows and skip the bad one you'd use
INSERT IGNORE(see INSERT IGNORE and REPLACE) — with its own risks.