<< Home

Chapter 10: Updating and Deleting Data

Databases evolve continuously; records change, become obsolete, or must be removed. The UPDATE, DELETE, and TRUNCATE statements perform these operations, and each carries risk if used without precision. This chapter shows best practices, safe filters, and recovery options when changes go wrong.

⚠️ Always run update and delete commands inside transactions during development and testing. Commit only after verifying the affected row count.

UPDATE and DELETE best practices

Updates modify data in place; deletes remove it. Both are straightforward, yet dangerous if filters are missing or too broad. Always specify a clear WHERE clause and verify the expected row count.

Basic UPDATE form

UPDATE employees
SET salary = salary * 1.05
WHERE department = 'Engineering';

Multiple columns can be updated in the same statement:

UPDATE products
SET price = price * 0.9,
    updated_at = CURRENT_TIMESTAMP
WHERE category = 'Clearance';
💡 Track who and when modified rows by using audit columns such as updated_by and updated_at. Populate them through triggers or in the same UPDATE statement.

Basic DELETE form

DELETE FROM sessions
WHERE last_active < CURRENT_DATE - INTERVAL '30 days';

Omitting WHERE removes every row from the table, which is almost never intended. Add safety limits in scripts that generate SQL automatically.

⚠️ A DELETE without a filter removes all data but keeps the table definition. Use TRUNCATE only when you are sure you want a complete reset.

Engine-specific batch updates

-- PostgreSQL
UPDATE accounts
SET balance = balance - 50
WHERE id = 123
RETURNING id, balance;

-- SQL Server
UPDATE dbo.accounts
SET balance = balance - 50
OUTPUT deleted.balance AS old_balance, inserted.balance AS new_balance
WHERE id = 123;

-- MySQL
DELETE FROM logs
WHERE created_at < NOW() - INTERVAL 90 DAY
LIMIT 10000;   -- avoid long locks
💡 For heavy workloads, perform updates or deletes in manageable batches using primary key ranges or date windows. This reduces locks and transaction log growth.

Using WHERE, subqueries, and safeguards

The WHERE clause filters which rows to affect. Combine it with joins or subqueries to match related records precisely.

Targeting related rows

-- Reduce salaries for departments that exceeded budget
UPDATE employees
SET salary = salary * 0.95
WHERE department_id IN (
  SELECT id FROM departments WHERE budget_used > budget_limit
);

You can also join directly in engines that support UPDATE ... JOIN or DELETE ... USING syntax.

-- MySQL and SQL Server
UPDATE e
SET e.salary = e.salary * 0.95
FROM employees e
JOIN departments d ON e.department_id = d.id
WHERE d.budget_used > d.budget_limit;

-- PostgreSQL
DELETE FROM orders o
USING customers c
WHERE o.customer_id = c.id
  AND c.status = 'INACTIVE';
💡 If your engine lacks joins in updates, use subqueries. They are logically equivalent but sometimes less efficient. Always verify execution plans for large datasets.

Verifying before execution

First preview the affected rows with a SELECT that uses the same filter. When certain, replace SELECT with UPDATE or DELETE.

-- Preview what would be deleted
SELECT id, email FROM users
WHERE email LIKE '%@test.invalid';

-- Then safely delete
DELETE FROM users
WHERE email LIKE '%@test.invalid';
⚠️ Accidental unfiltered updates are among the most common causes of data loss. Enable safe update modes in your client (for example sql_safe_updates=1 in MySQL) to block statements without WHERE or LIMIT.

Transactional safeguards

Use transactions to isolate changes and roll back on error.

BEGIN;

UPDATE inventory
SET quantity = quantity - 1
WHERE product_id = 1001;

DELETE FROM carts
WHERE product_id = 1001
  AND user_id = 42;

COMMIT;
💡 During interactive sessions, keep transactions short. Open transactions hold locks and can block other sessions. Always COMMIT or ROLLBACK before stepping away.

Using conditions from other tables

You can base updates or deletes on computed or aggregated values from related tables.

-- Deactivate customers with no orders in the past year
UPDATE customers
SET active = FALSE
WHERE id NOT IN (
  SELECT DISTINCT customer_id FROM orders
  WHERE order_date > CURRENT_DATE - INTERVAL '1 year'
);
⚠️ Subqueries in NOT IN can misbehave with nulls. Use NOT EXISTS instead for reliable results.
UPDATE customers c
SET active = FALSE
WHERE NOT EXISTS (
  SELECT 1 FROM orders o
  WHERE o.customer_id = c.id
    AND o.order_date > CURRENT_DATE - INTERVAL '1 year'
);

TRUNCATE and restoring from backups

TRUNCATE quickly removes all rows from a table while keeping its structure. It typically resets identity counters and uses less logging than DELETE.

TRUNCATE TABLE logs;
Engine Transactional Resets identity Notes
PostgreSQL Yes (within transaction) Yes Can cascade to related tables with TRUNCATE ... CASCADE.
MySQL No (implicit commit) Yes Cannot be rolled back; requires DROP + CREATE privileges.
SQL Server Yes (non-logged minimal mode) Yes Cannot target tables referenced by foreign keys.
SQLite Emulated via DELETE FROM Yes Acts as a fast full delete.
⚠️ TRUNCATE is irreversible. It usually skips triggers and does not fire row-level delete events. Always ensure no dependent tables rely on the data being cleared.

Alternatives for safe clearing

-- Use DELETE with WHERE to preserve control
DELETE FROM logs
WHERE created_at < CURRENT_DATE - INTERVAL '90 days';

To reclaim storage space after mass deletions, run engine-specific maintenance such as VACUUM (PostgreSQL, SQLite) or OPTIMIZE TABLE (MySQL).

Restoring deleted or truncated data

Accidental data removal happens. Your ability to recover depends on logging and backup strategies.

-- PostgreSQL example recovery
pg_restore --dbname=production --table=users backup.dump

-- MySQL
mysql -u root -p production < backup.sql

-- SQL Server
RESTORE DATABASE Production
FROM DISK = 'D:\backups\prod_full.bak'
WITH REPLACE, RECOVERY;
💡 Practice restoring backups regularly. The time to learn recovery syntax is not during an outage.

By combining disciplined WHERE filters, short transactions, and reliable backups, you can manage data changes confidently and avoid irreversible mistakes.