Intermediate: combining and changing data · Changing data, transactions and ACID · topic 38 of 62 · 35 min
UPDATE and DELETE safely
Explanation
UPDATE changes rows that already exist
UPDATE names the table, SET gives the new values, and WHERE picks the rows. Every row that matches the WHERE is changed; the others are untouched.
UPDATE orders SET status = 'Shipped' WHERE id = 107;Preview with SELECT first
A safe habit used by careful developers and DBAs: write the WHERE as a SELECT, check the rows and the count, then change SELECT * into UPDATE ... SET or DELETE, keeping the same WHERE. Doing this inside a transaction (topic 4) adds a second safety net.
-- 1. Preview: which rows will change?
SELECT id, name, price FROM products WHERE category = 'Kitchen';
-- 3 | Steel Tiffin Box | 450
-- 4 | Pressure Cooker 3L | 1450
-- 2. Same WHERE, now the change:
UPDATE products SET price = ROUND(price * 1.10) WHERE category = 'Kitchen';
-- 3 | 495 , 4 | 1595Why ROUND? price * 1.10 is a decimal calculation, and 450 * 1.10 is really 495.00000000000006 inside the computer, which SQLite would store as is. Rounding keeps money clean. SET can use the old value of any column of the same row: price = price * 1.10, stock = stock - 1, and several columns at once separated by commas.
UPDATE with a subquery
The new value, or the WHERE, can come from another table through a subquery (module 6). Here every product loses the quantity sitting in Placed orders. The subquery is correlated: it runs for each product, using products.id.
UPDATE products
SET stock = stock - (SELECT SUM(i.qty)
FROM order_items i JOIN orders o ON o.id = i.order_id
WHERE o.status = 'Placed' AND i.product_id = products.id)
WHERE id IN (SELECT i.product_id
FROM order_items i JOIN orders o ON o.id = i.order_id
WHERE o.status = 'Placed');
-- Basmati Rice 40 → 39, Cotton Kurta 30 → 29DELETE removes rows
DELETE FROM order_items
WHERE order_id IN (SELECT id FROM orders WHERE status = 'Cancelled');
-- 1 row removed (the shoes in order 104)Delete child rows (order_items) before their parent (orders). With foreign keys switched on, the database refuses to leave items pointing at a missing order, unless the key was declared ON DELETE CASCADE. SQLite only checks foreign keys after PRAGMA foreign_keys = ON.
How many rows changed?
Every UPDATE and DELETE reports a row count, and apps should check it. In SQLite, SELECT changes() returns the rows changed by the last statement; drivers in every language give the same number (rowcount in Python, affectedRows in Node.js MySQL drivers). An unexpected 0 or 5000 means something is wrong.
DELETE, TRUNCATE and DROP
| Statement | What it removes | Notes |
|---|---|---|
| DELETE FROM t WHERE ... | The matching rows | Row by row, can be rolled back, fires triggers |
| DELETE FROM t | All rows; the table stays | Slow on huge tables |
| TRUNCATE TABLE t | All rows, very fast; the table stays | Not in SQLite. In MySQL it commits at once and cannot be rolled back; in PostgreSQL and SQL Server it can be rolled back inside a transaction |
| DROP TABLE t | The rows AND the table itself | Structure, indexes and data are gone |
Soft delete
Many apps never really delete customers or orders, because of audits, refunds and legal record-keeping. Instead they set a flag, such as is_active = 0 or deleted_at = '2025-04-20'. This is a soft delete. The cost: every query must remember WHERE is_active = 1, which is why teams often create a view of the active rows (module 10).
Common mistakes
- Running UPDATE or DELETE without WHERE. Write the WHERE first; MySQL Workbench even blocks such statements in its default "safe updates" mode.
- Skipping the preview SELECT. Always check which rows and how many will change.
- Storing price * 1.10 without ROUND and getting 495.00000000000006.
- An UPDATE with a subquery that returns NULL for some rows, wiping their values. Limit the WHERE to rows that have a match, or use COALESCE.
- Deleting parents before children and leaving orphan rows. Delete children first, or use ON DELETE CASCADE on purpose.
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
UPDATE and DELETE safely · 1/8
UPDATE changes rows that already exist. DELETE removes them. Both are powerful, so we learn to use them safely.
Prefer a video by a teacher? Find free videos on “sql update delete where safely truncate vs delete vs drop” on YouTube ↗
In real life: A price revision on a grocery app
When a supplier raises prices, an operations team at an online grocery runs an UPDATE on one category or brand. A careful team runs the SELECT preview, shares the row count for approval, runs the UPDATE inside a transaction, compares the reported count, and only then commits. Products that are discontinued are usually soft-deleted (hidden) rather than removed, so old invoices still show their names.
SELECT COUNT(*) FROM products WHERE category = 'Kitchen'; -- 2
UPDATE products SET price = ROUND(price * 1.10) WHERE category = 'Kitchen';
SELECT changes(); -- 2, matches the previewPractice (0/4 done)
Raise the price of every Kitchen product by 10%, rounded to a whole number with ROUND(price * 1.10). Then return id, name and price of the Kitchen products.
Orders still 'Placed' with an order_date before '2025-04-01' are too old to ship. Set their status to 'Cancelled'. Then return id and status of all orders, sorted by id.
Delete the order_items of every Cancelled order (use a subquery on orders). Then return two numbers from order_items: the number of rows left, named lines, and the number of different orders they belong to, named orders.
customers now has an is_active column (1 = active). Soft-delete Arjun Reddy (id 6) by setting is_active to 0; do not DELETE the row. Then return id and name of the active customers.
Quick quiz
Read more (free)
Related help
Before this, learn
This topic builds on these. If something here feels new, open them first.
Related topics
- Upserts: insert or updateChanging data, transactions and ACID · same module
- Transactions: all or nothingChanging data, transactions and ACID · same module
- INSERT, UPDATE and DELETEBackend Development · Databases and SQL basics
- Subqueries, CTEs and window functionsBackend Development · SQL in depth
- Correlated subqueries and derived tablesSubqueries, CTEs and set operations
- CREATE TABLE and constraintsDatabases and the relational model
Questions people ask about this
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.
What does UPDATE products SET stock = 0; do?
Answer: Sets stock to 0 for every product
Without WHERE, every row is updated.
What is the safest way to run a DELETE on a live table?
Answer: Preview the same WHERE with a SELECT, then run the DELETE, ideally inside a transaction
Check the rows and the count first, and keep a way back.
Which statement removes the table itself, not just its rows?
Answer: DROP TABLE
DROP removes the structure, indexes and data.
Why do many apps use a soft delete (is_active = 0) for customers?
Answer: Old orders, invoices and audits still need the row
The row is hidden from normal use but kept for history.
In SQLite, how do you see how many rows the last UPDATE changed?
Answer: SELECT changes()
changes() returns the rows changed by the most recent INSERT, UPDATE or DELETE.
What happens with UPDATE products SET price = 0; (no WHERE)?Related
Answer: Every product gets price 0
Without WHERE, UPDATE changes every row in the table.
The subquery SELECT dept_id FROM employees returns 1, 2, 3, 4 and one NULL. How many rows does WHERE id NOT IN (that subquery) return from departments (ids 1 to 5)?Related
Answer: 0
id <> NULL is unknown, so the NOT IN is never true for any row.
In PostgreSQL, a subquery used with = returns 3 rows. What happens?Related
Answer: An error: more than one row returned by a subquery
PostgreSQL, MySQL and SQL Server raise an error; SQLite quietly uses the first row.
From Scalar subqueries
What does WHERE price > (SELECT AVG(price) FROM products) do?Related
Answer: Keeps products whose price is above the overall average
The subquery returns one number, the average; the outer WHERE compares each price with it.
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
- 3 more topics in “Changing data, transactions and ACID” before its exam: Upserts: insert or update, Transactions: all or nothing, ACID, isolation and locking.
- Module 8 exam: Changing data, transactions and ACID · 14 questions · 25 min, without help. 5 topics of this module still to finish first.
- Then the next module, Schema design and normalisation. 62 topics left in the course before the final exam.