SQL Tutorial › Modifying Data › DELETE & TRUNCATE

SQL DELETE and TRUNCATE

BeginnerLesson 20 of 6910 min read9 queries run on MySQL 8.4

DELETE removes rows from a table; TRUNCATE empties a table completely. Both are permanent once committed, so the habits from the UPDATE lesson matter even more here: check the WHERE with a SELECT first, and use a transaction when the data is valuable.

Advertisement
In this lesson
  • Delete rows that match a condition
  • Delete using a subquery or a join
  • Understand foreign key errors on delete
  • Choose between DELETE, TRUNCATE and DROP

DELETE syntax

MySQL
DELETE FROM table_nameWHERE condition;
Watch out

DELETE FROM table_name; without WHERE removes every row. The table itself stays, empty.

Sample data

products · 9 rows
product_idproduct_namecategorypricestock
1Wireless MouseElectronics799120
2Mechanical KeyboardElectronics349945
3USB-C HubElectronics19990
4Office ChairFurniture899912
5Standing DeskFurniture189995
6Notebook PackStationery249300
7Gel PensStationery199250
8Desk LampFurniture149930
9Monitor ArmFurniture279918

Delete one row

Product 9 (Monitor Arm) has never been ordered and is discontinued:

SQL
DELETE FROM productsWHERE product_id = 9; SELECT product_id, product_name FROM products;
ResultMySQL 8.4
mysql> DELETE FROM products
Query OK, 1 row affected
mysql> SELECT product_id, product_name FROM products
product_idproduct_name
1Wireless Mouse
2Mechanical Keyboard
3USB-C Hub
4Office Chair
5Standing Desk
6Notebook Pack
7Gel Pens
8Desk Lamp
8 rows in set
How this query works, step by step
  1. WHERE product_id = 9 matches exactly one row.
  2. MySQL removes it and reports 1 row affected.
  3. The SELECT shows the 8 remaining products.

Delete several rows

A WHERE that matches several rows deletes them all. Remove every line of order 1001:

SQL
DELETE FROM order_itemsWHERE order_id = 1001; SELECT * FROM order_items WHERE order_id IN (1001, 1002);
ResultMySQL 8.4
mysql> DELETE FROM order_items
Query OK, 2 rows affected
mysql> SELECT * FROM order_items WHERE order_id IN (1001, 1002)
order_idproduct_idquantity
100241
1 row in set
How this query works, step by step
  1. Order 1001 has two lines (products 2 and 6), so 2 rows affected.
  2. Order 1002’s line is untouched.

Check first with SELECT

Before deleting, run the same WHERE as a SELECT:

SQL
SELECT * FROM orders WHERE status = 'Cancelled';
ResultMySQL 8.4
order_idcustomer_idorder_datestatus
100432025-02-14Cancelled
1 row in set

If the result is what you expect, change SELECT * to DELETE and run it.

Advertisement

Foreign keys block orphan rows

Order 1004 still has an order line. The foreign key from order_items to orders stops you from deleting the order and leaving the line pointing at nothing:

MySQL
DELETE FROM orders WHERE order_id = 1004;
ResultMySQL 8.4
ERROR 1451 (23000): Cannot delete or update a parent row: a foreign key constraint fails (`company`.`order_items`, CONSTRAINT `order_items_ibfk_1` FOREIGN KEY (`order_id`) REFERENCES `orders` (`order_id`))

Delete the child rows first, then the parent:

SQL
DELETE FROM order_items WHERE order_id = 1004;DELETE FROM orders WHERE order_id = 1004;SELECT order_id, status FROM orders WHERE status = 'Cancelled';
ResultMySQL 8.4
mysql> DELETE FROM order_items WHERE order_id = 1004
Query OK, 1 row affected
mysql> DELETE FROM orders WHERE order_id = 1004
Query OK, 1 row affected
mysql> SELECT order_id, status FROM orders WHERE status = 'Cancelled'
Empty set
How this query works, step by step
  1. The first DELETE removes the order’s line (1 row).
  2. Now nothing references order 1004, so the second DELETE succeeds.
  3. No cancelled orders are left: Empty set.
Tip

A foreign key declared with ON DELETE CASCADE deletes the child rows automatically. It is convenient, but make sure that is really what you want.

Delete with a subquery

Delete customers who have never placed an order:

SQL
DELETE FROM customersWHERE customer_id NOT IN (SELECT customer_id FROM orders); SELECT customer_id, customer_name FROM customers;
ResultMySQL 8.4
mysql> DELETE FROM customers
Query OK, 2 rows affected
mysql> SELECT customer_id, customer_name FROM customers
customer_idcustomer_name
1Ananya Sharma
2Rohit Kumar
3Fatima Khan
4David Fernandes
5Ishita Bose
5 rows in set
How this query works, step by step
  1. The subquery lists every customer id that appears in orders.
  2. NOT IN matches customers 6 and 7, who have no orders. (orders.customer_id is NOT NULL, so the NOT IN NULL trap cannot happen here.)
  3. Two rows are deleted.

Delete with JOIN (MySQL)

Delete the order lines of every delivered order placed before February 2025:

MySQL
DELETE oiFROM order_items oiJOIN orders o ON o.order_id = oi.order_idWHERE o.status = 'Delivered' AND o.order_date < '2025-02-01'; SELECT COUNT(*) AS remaining_lines FROM order_items;
ResultMySQL 8.4
mysql> DELETE oi
Query OK, 3 rows affected
mysql> SELECT COUNT(*) AS remaining_lines FROM order_items
remaining_lines
13
1 row in set
How this query works, step by step
  1. DELETE oi names the table to delete from; orders is only used for the condition.
  2. Orders 1001 and 1002 match, with three lines between them.
  3. 16 lines minus 3 leaves 13.
Advertisement

Delete with ORDER BY and LIMIT

MySQL can delete a limited number of rows. This is useful for removing huge amounts of old data in small batches:

MySQL
DELETE FROM order_itemsORDER BY order_idLIMIT 2; SELECT COUNT(*) AS remaining_lines FROM order_items;
ResultMySQL 8.4
mysql> DELETE FROM order_items
Query OK, 2 rows affected
mysql> SELECT COUNT(*) AS remaining_lines FROM order_items
remaining_lines
14
1 row in set
How this query works, step by step
  1. ORDER BY order_id decides which rows go first: the two lines of order 1001.
  2. LIMIT 2 stops after two rows, leaving 14.
  3. In a real clean-up job you would use something like LIMIT 1000 and repeat until no rows are affected, so each batch holds locks only briefly.

TRUNCATE TABLE

TRUNCATE TABLE removes all rows at once and resets AUTO_INCREMENT counters:

MySQL
CREATE TABLE audit_log (id INT AUTO_INCREMENT PRIMARY KEY, msg VARCHAR(50));INSERT INTO audit_log (msg) VALUES ('login'), ('logout');TRUNCATE TABLE audit_log;INSERT INTO audit_log (msg) VALUES ('fresh start');SELECT * FROM audit_log;
ResultMySQL 8.4
mysql> CREATE TABLE audit_log (id INT AUTO_INCREMENT PRIMARY KEY, msg VARCHAR…
Query OK, 0 rows affected
mysql> INSERT INTO audit_log (msg) VALUES ('login'), ('logout')
Query OK, 2 rows affected
mysql> TRUNCATE TABLE audit_log
Query OK, 0 rows affected
mysql> INSERT INTO audit_log (msg) VALUES ('fresh start')
Query OK, 1 row affected
mysql> SELECT * FROM audit_log
idmsg
1fresh start
1 row in set
How this query works, step by step
  1. Two rows are inserted with ids 1 and 2.
  2. TRUNCATE empties the table and resets the counter.
  3. The next insert gets id 1 again. After a DELETE FROM audit_log it would have got id 3.

DELETE vs TRUNCATE vs DROP

DELETETRUNCATEDROP TABLE
Removeschosen rows (WHERE)all rowsthe whole table
Table structurekeptkeptremoved
WHERE allowedyesnono
Speed on big tablesslow (row by row)fastfast
Resets AUTO_INCREMENTnoyesn/a
Fires triggersyesnono
Rollback in a transactionyesno (implicit commit)no (implicit commit)
Command typeDMLDDLDDL
Advertisement

Exercise: Remove out-of-stock products

Delete every product whose stock is 0. Run the matching SELECT first in your head, then show the remaining products.

Expected resultMySQL 8.4
mysql> DELETE FROM order_items WHERE product_id IN (SELECT product_id FROM pr…
Query OK, 1 row affected
mysql> DELETE FROM products WHERE stock = 0
Query OK, 1 row affected
mysql> SELECT product_id, product_name, stock FROM products
product_idproduct_namestock
1Wireless Mouse120
2Mechanical Keyboard45
4Office Chair12
5Standing Desk5
6Notebook Pack300
7Gel Pens250
8Desk Lamp30
9Monitor Arm18
8 rows in set
Hint 1

DELETE FROM products WHERE stock = 0;. The USB-C Hub has order lines, so delete those first.

Quick quiz

1. What does DELETE FROM customers; do?

2. Which command resets the AUTO_INCREMENT counter?

3. Why does deleting a parent row fail with error 1451?

4. Which can be rolled back inside a transaction?

Advertisement

DELETE FAQ

What is the difference between DELETE and TRUNCATE?

DELETE removes rows one by one, can use WHERE, fires triggers and can be rolled back. TRUNCATE removes all rows at once, resets AUTO_INCREMENT and cannot be rolled back in MySQL.

How do I delete duplicate rows in SQL?

Keep one row per duplicate group and delete the rest, for example with a self join on the duplicate columns and a.id > b.id, or with ROW_NUMBER() in a CTE. See Top N per Group.

Can I recover deleted rows?

Inside an uncommitted transaction, yes: ROLLBACK. After a commit, only from a backup or the binary log.

How do I delete rows from two tables at once in MySQL?

Name both tables after DELETE: DELETE o, oi FROM orders o JOIN order_items oi ON ... WHERE ...;