Skip to content

Intermediate: combining and changing data · Changing data, transactions and ACID · topic 37 of 62 · 35 min

INSERT

Explanation

INSERT INTO table (columns) VALUES (...) adds rows. Always name the columns, so the statement keeps working when the table changes. Columns you leave out get their DEFAULT value or NULL. INSERT ... SELECT copies rows from a query, and RETURNING (SQLite 3.35+, PostgreSQL) gives back the new rows, for example their generated ids.

From reading to writing

So far every query only read data. Real apps also add, change and remove rows: a new customer signs up, an order is placed, a ticket is closed. The statements that change data are INSERT, UPDATE and DELETE, together called DML (data manipulation language). In the tasks of this module you write the change and then a SELECT to show the result.

INSERT one row

sql
INSERT INTO customers (id, name, city, email, joined)
VALUES (7, 'Meena Joshi', 'Jaipur', 'meena@example.com', '2025-04-10');
The column list says which value goes where. Text and dates go in single quotes.

You may leave out the column list and give a value for every column in table order, but do not. If someone later adds a column or changes the order, an insert without names fails or, worse, puts values into the wrong columns without any error. Naming the columns makes the statement readable and safe.

Many rows at once

Most engines (SQLite, PostgreSQL, MySQL, SQL Server) accept several rows in one VALUES list, separated by commas. One statement with many rows is faster than many statements with one row each, because the database does the bookkeeping once.

sql
INSERT INTO products (id, name, category, price, stock)
VALUES (9,  'Desi Ghee 1L', 'Grocery', 620, 20),
       (10, 'Masala Box',   'Kitchen', 350, 18);

Defaults, NULLs and generated ids

A column you leave out of the list gets its DEFAULT (set when the table was created) or, if it has none, NULL. If the column is NOT NULL with no default, the insert fails. In SQLite an INTEGER PRIMARY KEY you leave out is filled with the next number automatically; other engines use AUTO_INCREMENT (MySQL), IDENTITY (SQL Server) or GENERATED AS IDENTITY / SERIAL (PostgreSQL).

sql
-- tickets: status DEFAULT 'Open', priority DEFAULT 'Normal', assigned_to has no default
INSERT INTO tickets (customer_id, subject) VALUES (4, 'Wrong size received');
-- new row: id 2 | 4 | Wrong size received | Open | Normal | NULL
id was generated; status and priority took their defaults; assigned_to became NULL.

PostgreSQL, MySQL and SQL Server also accept the word DEFAULT inside VALUES to ask for the default explicitly. SQLite does not (it gives a syntax error), so there you simply leave the column out. INSERT INTO t DEFAULT VALUES inserts a row made only of defaults in SQLite and PostgreSQL.

INSERT ... SELECT: copying rows

Instead of VALUES you can write a SELECT. Every row it returns is inserted. This is how teams copy old rows into an archive table, fill a summary table, or move data between tables, without the data ever leaving the database.

sql
INSERT INTO orders_archive (id, customer_id, order_date, status, archived_on)
SELECT id, customer_id, order_date, status, '2025-04-30'
FROM orders
WHERE status = 'Delivered';
-- 4 rows copied: 101, 102, 103, 106
The SELECT columns are matched to the insert columns by position, so keep both lists in the same order.

RETURNING: get the new rows back

After an insert, an app usually needs the generated id, for example to show "Ticket #3 created". RETURNING makes the INSERT itself return rows, like a SELECT. It works in SQLite 3.35 and later and in PostgreSQL. SQL Server writes OUTPUT INSERTED.id instead. MySQL has no RETURNING (apps call LAST_INSERT_ID()), while MariaDB supports it for INSERT and DELETE, so check your engine and version.

sql
INSERT INTO tickets (customer_id, subject, priority)
VALUES (2, 'Refund not received', 'High')
RETURNING id, status, priority;
-- 3 | Open | High

Common mistakes

  • Inserting without a column list. Always name the columns.
  • Values in the wrong order for the column list, such as a city in the email column. Read both lists side by side.
  • Putting text without quotes, or numbers in quotes. Write 'Pune' and 620.
  • Giving an id that already exists: "UNIQUE constraint failed: customers.id". Leave the id out and let the database generate it.
  • Forgetting a NOT NULL column that has no default: "NOT NULL constraint failed". Supply a value or add a default.

Need help?

Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.

Try:

Watch the lesson

INSERT · 1/7

INSERT

Until now we only read data. This module changes it, starting with INSERT, which adds rows.

Prefer a video by a teacher? Find free videos on “sql insert into values insert select returning tutorial” on YouTube ↗

In real life: Placing an order on a shopping site

When you press "Place order", the server inserts one row into an orders table and one row per cart item into order_items. It needs the new order id to link the items, so in PostgreSQL it typically writes INSERT ... RETURNING id, and in MySQL it reads LAST_INSERT_ID(). Old delivered orders are later copied to an archive table with INSERT ... SELECT, so the live table stays small.

sql
INSERT INTO orders (customer_id, order_date, status)
VALUES (5, '2025-04-12', 'Placed')
RETURNING id;
-- 109
In SHOP, the next id after 108 is 109; the app then inserts order_items rows with order_id 109.

Practice (0/4 done)

Task 1 · Code · SQL

Add a new customer: id 7, name 'Meena Joshi', city 'Jaipur', email 'meena@example.com', joined '2025-04-10'. Name the columns in the INSERT. Then return id, name and city of all customers.

Task 2 · Code · SQL

Add two products in ONE insert: (id 9, 'Desi Ghee 1L', 'Grocery', price 620, stock 20) and (id 10, 'Masala Box', 'Kitchen', price 350, stock 18). Then return id, name and price of all Grocery and Kitchen products, sorted by id.

Task 3 · Code · SQL

An empty table orders_archive (id, customer_id, order_date, status, archived_on) exists. Copy every Delivered order into it with INSERT ... SELECT, setting archived_on to '2025-04-30'. Then return id, customer_id, order_date and archived_on from orders_archive.

Task 4 · Code · SQL

tickets has defaults: status 'Open' and priority 'Normal'. Insert a ticket for customer_id 2 with subject 'Refund not received' and priority 'High', giving only those three columns, and make the INSERT itself return id, subject, status, priority and assigned_to with RETURNING.

Quick quiz

Question 1

1.Why should an INSERT always name its columns?

Question 2

2.A column is left out of an INSERT and has no DEFAULT and no NOT NULL. What value does it get?

Question 3

3.What does INSERT INTO a (x, y) SELECT p, q FROM b WHERE ... do?

Question 4

4.Which engine has no RETURNING clause, so apps read the new id with LAST_INSERT_ID()?

Question 5

5.In SQLite, you write INSERT INTO tickets (customer_id, subject, status) VALUES (3, 'Hi', DEFAULT). What happens?

Read more (free)

Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.

Why should an INSERT always name its columns?

Answer: It keeps working correctly if columns are added or reordered later

Without a column list, values are matched to columns by position, which breaks when the table changes.

A column is left out of an INSERT and has no DEFAULT and no NOT NULL. What value does it get?

Answer: NULL

With no default, a missing column becomes NULL.

What does INSERT INTO a (x, y) SELECT p, q FROM b WHERE ... do?

Answer: Inserts every row the SELECT returns, p into x and q into y

INSERT ... SELECT inserts one row per selected row, matching columns by position.

Which engine has no RETURNING clause, so apps read the new id with LAST_INSERT_ID()?

Answer: MySQL

MySQL has no RETURNING. SQL Server uses OUTPUT; MariaDB, SQLite and PostgreSQL support RETURNING.

In SQLite, you write INSERT INTO tickets (customer_id, subject, status) VALUES (3, 'Hi', DEFAULT). What happens?

Answer: A syntax error: SQLite does not accept DEFAULT in VALUES

Leave the column out instead. PostgreSQL, MySQL and SQL Server do accept DEFAULT there.

What does RETURNING id do at the end of an INSERT?Related

Answer: Gives back the id of the new row

RETURNING sends back values from the changed rows, such as the new id.

From INSERT, UPDATE and DELETE

Why is "SELECT to check, then INSERT if missing" in app code risky?Related

Answer: Two requests can both see "missing" and both try to insert

That gap between check and write is a race condition. An upsert is one atomic statement.

From Upserts: insert or update

In a students table, what is one row?Related

Answer: One student with all their details

A row is one item. A column is one detail that every row has.

From What is a database?

What does SELECT name FROM students WHERE marks > 80 return?Related

Answer: Names of students with marks above 80

SELECT name picks one column; WHERE marks > 80 keeps only the rows with more than 80.

From SELECT and WHERE: finding rows

Pass the quiz (60% or more) or mark this topic as done when you are ready.

Your progress is saved on this device only.

Next steps

  1. 4 more topics in “Changing data, transactions and ACID” before its exam: UPDATE and DELETE safely, Upserts: insert or update, Transactions: all or nothing, ACID, isolation and locking.
  2. Module 8 exam: Changing data, transactions and ACID · 14 questions · 25 min, without help. 5 topics of this module still to finish first.
  3. Then the next module, Schema design and normalisation. 62 topics left in the course before the final exam.

← Previous: Top-N per group and de-duplication