Skip to content

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

UPDATE and DELETE safely

Explanation

UPDATE table SET column = value WHERE ... changes existing rows, and DELETE FROM table WHERE ... removes them. Without a WHERE they hit every row, so always write the WHERE first and preview it with a SELECT. SET can use expressions and subqueries. TRUNCATE and DROP are different tools, and a soft delete only marks a row as removed.

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.

sql
UPDATE orders SET status = 'Shipped' WHERE id = 107;
One row changes: order 107 goes from Placed to Shipped.
Careful: An UPDATE or DELETE without WHERE changes or removes EVERY row in the table. UPDATE orders SET status = 'Shipped' would mark all 8 orders as shipped, including the cancelled one. There is no "undo" button once it is committed.

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.

sql
-- 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 | 1595
A 10% price rise. ROUND gives 495 and 1595.

Why 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.

sql
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 → 29
The WHERE matters: without it, products with no Placed orders would get stock - NULL = NULL.

DELETE removes rows

sql
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

StatementWhat it removesNotes
DELETE FROM t WHERE ...The matching rowsRow by row, can be rolled back, fires triggers
DELETE FROM tAll rows; the table staysSlow on huge tables
TRUNCATE TABLE tAll rows, very fast; the table staysNot 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 tThe rows AND the table itselfStructure, 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 and DELETE

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.

sql
SELECT COUNT(*) FROM products WHERE category = 'Kitchen';   -- 2
UPDATE products SET price = ROUND(price * 1.10) WHERE category = 'Kitchen';
SELECT changes();   -- 2, matches the preview
The count from the preview and from changes() should agree.

Practice (0/4 done)

Task 1 · Code · SQL

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.

Task 2 · Code · SQL

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.

Task 3 · Code · SQL

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.

Task 4 · Code · SQL

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

Question 1

1.What does UPDATE products SET stock = 0; do?

Question 2

2.What is the safest way to run a DELETE on a live table?

Question 3

3.Which statement removes the table itself, not just its rows?

Question 4

4.Why do many apps use a soft delete (is_active = 0) for customers?

Question 5

5.In SQLite, how do you see how many rows the last UPDATE changed?

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.

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.

From INSERT, UPDATE and DELETE

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.

From IN, EXISTS and NOT EXISTS

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.

From Subqueries, CTEs and window functions

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. 3 more topics in “Changing data, transactions and ACID” before its exam: 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: INSERT