UPDATE — Modifying Existing Rows
Change data in existing rows — single column updates, multi-column updates, computed updates, UPDATE from another table, and the golden rule: SELECT before UPDATE
// Part 01
The Most Dangerous Command in SQL
UPDATE modifies existing rows in a table. It is one of the four DML commands (SELECT, INSERT, UPDATE, DELETE) and the one most likely to cause irreversible damage when used carelessly. A SELECT returns wrong results — you rewrite the query. An UPDATE without a proper WHERE clause rewrites every row in a million-row table in seconds, and there is no undo.
This module teaches you not just the syntax but the discipline. Every professional SQL writer has a set of habits around UPDATE that they follow without exception — not because they doubt their own abilities, but because the cost of a mistake is high enough that verification is always worth the 30 seconds it takes.
The single most important rule in this module:
The Golden Rule
Before every UPDATE, run the equivalent SELECT with the same WHERE clause and verify the rows it returns are exactly the rows you intend to modify. Zero exceptions.
// Part 02
Basic Syntax — Single Column Update
Update one column on one specific row
// Part 03
Multi-Column UPDATE — Setting Multiple Values at Once
UPDATE can set multiple columns in a single statement by separating assignments with commas in the SET clause. This is always better than running separate UPDATE statements per column — it is atomic (all changes happen together) and more efficient (one table scan, one transaction).
// Part 04
Computed Updates — Using the Current Value
The new value in SET does not have to be a static literal. You can reference the column's current value in the calculation — the database reads the current value, applies the expression, and writes the result back. This is how you implement increments, percentage adjustments, and any update that depends on what is already there.
// Part 05
UPDATE with CASE WHEN — Conditional Updates
CASE WHEN in the SET clause lets you apply different values to different rows in a single UPDATE statement. Instead of running multiple UPDATE statements with different WHERE conditions, one UPDATE with CASE handles all cases in one pass.
// Part 06
UPDATE FROM — Updating Based on Another Table
Sometimes the new value for a column comes from another table. UPDATE FROM (PostgreSQL) or UPDATE with JOIN (MySQL) lets you reference another table in the SET or WHERE clause — powerful for synchronising data between tables.
PostgreSQL — UPDATE FROM syntax
MySQL — UPDATE with JOIN syntax
// Part 07
UPDATE Safety — The Rules That Prevent Disasters
These are not suggestions. They are non-negotiable habits that every professional SQL writer follows when running UPDATE on any database that contains real data.
Rule 1 — SELECT before UPDATE, always
Run the exact same WHERE clause as a SELECT first. Count the rows. Inspect a sample. Only proceed when the result matches what you intended.
Rule 2 — always include WHERE
An UPDATE without WHERE updates every single row in the table. There is almost never a legitimate reason to update every row without any condition — and if there is, you should be explicit about it with a comment explaining why.
Rule 3 — use transactions in production
Rule 4 — UPDATE only the minimum rows necessary
Use the most specific WHERE clause possible. UPDATE ... WHERE customer_id = 5 is safer than UPDATE ... WHERE city = 'Seattle' which is safer than UPDATE ... WHERE loyalty_tier = 'Bronze'. The tighter the WHERE, the smaller the blast radius if something is wrong.
Rule 5 — verify the affected row count
After UPDATE, check how many rows were changed. Most SQL clients show "N rows affected" — verify this number matches your expectation. 0 rows affected means your WHERE matched nothing (check for typos). 1000 rows affected when you expected 10 means something is very wrong.
🎯 Pro Tip
In production databases at Indian tech companies, senior engineers run UPDATE statements with a DBA (database administrator) watching the screen, or they write the UPDATE in a migration script that is peer-reviewed before execution. For updates affecting more than 10,000 rows, many teams require a JIRA ticket, a backup confirmation, and a rollback plan before the command is run. This is not excessive caution — it is professional discipline.
// Part 08
UPDATE with Subqueries — Dynamic New Values
The new value in SET can come from a subquery — a SELECT that computes the value dynamically from other data in the database.
// Part 09
RETURNING — See What Was Updated
Just like INSERT, PostgreSQL and DuckDB support RETURNING on UPDATE — it returns the values of the updated rows after the change. This is useful for confirming what changed, logging the update, or chaining the updated data into the next operation.
// Part 10
Batch Updates — Updating Large Tables Safely
Updating millions of rows in a single UPDATE statement can be dangerous in production. A long-running UPDATE holds locks on rows, blocking other queries. If the UPDATE fails midway, the transaction rolls back — but the rollback itself takes time proportional to the rows already changed.
For large-scale updates, the professional approach is to batch the update — process rows in chunks of 1,000 to 10,000, committing after each chunk.
// Part 11
What This Looks Like at Work
You are a data engineer at HDFC Bank's digital payments team. A compliance audit has identified that 47 merchant accounts were incorrectly tagged with the wrong risk tier during a bulk import last month. The correct risk tiers have been verified against the original source data and are in a correction table. You need to apply the corrections safely in production.
🎯 Pro Tip
The ratio in the story above — 30 minutes of verification for 5 minutes of execution — is the correct professional ratio for any UPDATE that touches financial, compliance, or customer data. The SQL itself is the easy part. The discipline of verifying before, executing carefully, verifying after, and documenting the change is what separates a data professional from someone who types commands and hopes for the best.
// Part 12
Interview Prep — 5 Questions With Complete Answers
Every single row in the table is updated. Without WHERE, there is no filter — the SET clause applies to all rows universally. UPDATE customers SET loyalty_tier = 'Bronze' with no WHERE condition changes every customer's loyalty tier to 'Bronze', regardless of what it was before. Gold customers, Platinum customers, Silver customers — all become Bronze simultaneously.
The damage is immediate and permanent without a transaction. If the UPDATE was not wrapped in a transaction with an uncommitted COMMIT, the change cannot be undone — there is no built-in undo in SQL. Recovery requires restoring from a backup (which loses all changes made after the backup was taken) or manually reconstructing the data from logs (which is time-consuming and error-prone).
Prevention: always include a WHERE clause, always run the equivalent SELECT first to verify which rows will be affected, and always wrap consequential UPDATEs in a transaction so ROLLBACK is available if the result is wrong. Many SQL clients have a "safe mode" that prevents UPDATE and DELETE without WHERE — enable it for interactive sessions on production databases. Some teams require a WHERE clause that always includes the primary key for any single-row UPDATE, and a code review for any UPDATE that affects more than 1,000 rows.
In PostgreSQL, use UPDATE ... FROM syntax. The FROM clause specifies additional tables to join, and those tables can be referenced in both the SET and WHERE clauses. UPDATE orders AS o SET total_amount = item_totals.sum FROM (SELECT order_id, SUM(line_total) AS sum FROM order_items GROUP BY order_id) AS item_totals WHERE o.order_id = item_totals.order_id. The subquery or table in FROM is joined to the target table using the WHERE clause.
In MySQL, use UPDATE ... JOIN syntax. The JOIN is declared between the UPDATE and SET: UPDATE orders o JOIN order_items_summary s ON o.order_id = s.order_id SET o.total_amount = s.total WHERE o.total_amount <> s.total. The JOIN condition is part of the UPDATE statement itself, not in a WHERE clause.
A correlated subquery in SET also works in both databases: UPDATE stores SET monthly_target = (SELECT AVG(total_amount) * 30 FROM orders WHERE orders.store_id = stores.store_id). The subquery references the outer table's current row via stores.store_id — this is evaluated once per row being updated. Correlated subqueries are more portable (work in both PostgreSQL and MySQL) but can be slower than the FROM/JOIN approach for large tables because the subquery runs once per row.
The SELECT-before-UPDATE pattern means running a SELECT with the exact same WHERE clause as your planned UPDATE, inspecting the results, verifying the row count and values are correct, and only then executing the UPDATE. It is the single most effective practice for preventing unintended updates.
The reason it matters: UPDATE errors are silent until they cause visible damage. A WHERE condition with a subtle bug — wrong column, wrong value, missing AND — returns results when run as a SELECT, but the wrong results. If you UPDATE directly, you discover the bug only after the damage is done. If you SELECT first, you discover the bug in a read-only context where no harm is done.
The concrete workflow: (1) Write the UPDATE statement. (2) Change UPDATE table SET column = value to SELECT * FROM table — keep the WHERE clause identical. (3) Run the SELECT. Count the rows — does the count match your expectation? Inspect a sample — do these rows look like the ones you meant to update? (4) If yes, change back to UPDATE and run. (5) Verify again with SELECT after the UPDATE. This adds 30–60 seconds to every UPDATE. It has saved careers by preventing accidental mass updates on production databases.
Multiple columns are updated in a single UPDATE statement by listing them all in the SET clause separated by commas: UPDATE employees SET role = 'Senior Manager', salary = 75000, department = 'Management' WHERE employee_id = 5. All assignments in the SET clause are evaluated using the row's values before any change is made — then all changes are applied simultaneously. This means SET col1 = col2, col2 = col1 correctly swaps the values rather than both becoming the original col2 value.
Single-statement multi-column UPDATE is better than separate UPDATE statements for three reasons. First, atomicity: all column changes happen in one transaction. If the UPDATE succeeds, all columns are changed. If it fails, none are. Separate UPDATEs can result in a partial state where some columns changed and others did not — an inconsistent intermediate state. Second, performance: the database scans the table once, applies all changes, and updates all indexes once. Separate UPDATEs scan the table N times and update indexes N times. Third, correctness: in a concurrent system, another session might read the row between two separate UPDATEs, seeing a state where some columns are old and some are new — a visibility anomaly. One UPDATE prevents this.
The only reason to use separate UPDATE statements is when each has a different WHERE clause — when different rows need different sets of columns updated. In that case, consider whether a single UPDATE with CASE WHEN in the SET clause can handle all cases in one statement, which is even more efficient.
Updating millions of rows in a single UPDATE statement holds a lock on those rows for the duration of the operation — potentially minutes or hours. During this time, other queries that need to read or write those rows are blocked, causing application timeouts and degraded performance. If the UPDATE fails, the rollback takes equally long. For tables actively used by a production application, a long-running UPDATE is essentially an outage.
The safe approach is batch updating — processing rows in chunks. Run the UPDATE with a LIMIT (or equivalent) of 1,000 to 10,000 rows per batch, commit after each batch, and repeat until zero rows are affected. Each batch holds locks for only a short time (milliseconds to seconds), commits immediately, and is individually rollbackable. The WHERE clause must be written so each successive batch picks up where the previous left off — typically by including AND updated_column <> new_value so already-updated rows are excluded from subsequent batches.
Batching is typically done from application code or a migration script in a loop: run the UPDATE, check the affected row count, sleep briefly to give other queries a chance to run, repeat until count is 0. For very large tables at companies like Amazon or DoorDash (billions of rows), even batching may not be sufficient — the alternative is a blue-green table migration: create a new table with the correct data, swap the table name atomically, and drop the old table. This approach has zero downtime but is operationally more complex and requires careful application code management during the swap window.
// Part 13
Errors You Will Hit — And Exactly Why They Happen
🎯 Key Takeaways
- ✓The golden rule: run SELECT with the identical WHERE clause before every UPDATE. Verify the row count and inspect a sample. Only run the UPDATE after confirming the rows are exactly what you intend to modify.
- ✓Always include a WHERE clause. UPDATE without WHERE modifies every row in the table — a mistake that is immediately permanent without a transaction.
- ✓Update multiple columns in one SET clause: SET col1 = val1, col2 = val2. All assignments are atomic — either all change or none do. Better than separate UPDATE statements per column.
- ✓Computed updates reference the current value: SET salary = salary * 1.10 reads the current salary, multiplies by 1.10, and writes the result back. All row values are read before any writes begin.
- ✓CASE WHEN in SET applies different values to different rows in a single UPDATE pass — more efficient than multiple UPDATE statements with different WHERE conditions.
- ✓UPDATE FROM (PostgreSQL) and UPDATE JOIN (MySQL) let you update rows using values from another table — essential for data synchronisation and correction workflows.
- ✓RETURNING on UPDATE (PostgreSQL/DuckDB) returns the updated row values immediately — no separate SELECT needed to see what changed. MySQL uses triggers or a separate SELECT instead.
- ✓Wrap consequential UPDATEs in a transaction: BEGIN; UPDATE...; SELECT (verify); COMMIT or ROLLBACK. A transaction gives you the ability to undo if the result is wrong.
- ✓For large tables (millions of rows), batch updates: add LIMIT and run in a loop until 0 rows affected. Keeps locks short, prevents application timeouts, and limits rollback cost per batch.
- ✓After UPDATE, always verify: check the affected row count matches expectation, run SELECT to confirm the changed values look correct. "No error" does not mean "correct result."
What comes next
In Module 22, you learn DELETE — removing rows from tables, soft delete patterns, truncate vs delete, and why DELETE is even more dangerous than UPDATE and requires even more care.
Module 22 → DELETEDiscussion
0Have a better approach? Found something outdated? Share it — your knowledge helps everyone learning here.