Join the other table in an UPDATE … FROM: UPDATE products SET price = np.price FROM new_prices AS np WHERE np.product_id = products.id; That works in PostgreSQL and SQLite 3.33+. MySQL writes it as UPDATE … JOIN … SET, SQL Server as UPDATE p SET … FROM products AS p JOIN …, and the correlated subquery SET price = (SELECT …) WHERE EXISTS (SELECT …) runs everywhere.
Standard SQL's UPDATE names one table, so "update from a SELECT" or "update with a join" means pulling values from a second table that matches each row on a key. Every major database can do it, but the syntax is one of the places where they differ most. The examples below run on SQLite, which supports both the UPDATE … FROM form and the portable subquery form; the table in section 5 gives the PostgreSQL, MySQL and SQL Server versions. Each example builds its own small tables, so hit Run, change the data and run it again. To copy rows into a table instead of changing existing ones, see insert from a SELECT; to remove rows that match another table, see delete using a join.
1UPDATE … SET … FROM: join the other tableRecommended
Name the source table in a FROM clause after SET, and put the match condition in WHERE. Each target row that finds a partner gets that partner's value; rows without a partner aren't touched. Writing the same join as a SELECT first is a cheap way to see exactly which rows will change before you run the update.
Output
The preview lists products 1 and 3 with their old and new prices, and changes() confirms the update touched those 2 rows. The Desk goes to 135.0, the Lamp to 21.0, and the Chair and Shelf keep their prices because new_prices has nothing for them. Product 9 in new_prices has no product, so it is simply ignored. Don't list products again inside FROM: the table after UPDATE is already part of the join, and a second copy turns it into a cross join (see the FAQ). UPDATE … FROM needs SQLite 3.33 or later.
2The portable form: SET col = (SELECT …) WHERE EXISTS
A correlated subquery in SET looks up the new value for each row, and it works in every SQL database, including older ones without a join syntax for UPDATE. The catch is the WHERE clause. Without it the update visits every row, and for a row with no match the subquery returns no row, which counts as NULL. The first update below makes that mistake inside a transaction and rolls it back; the second adds WHERE EXISTS with the same match condition.
Output
Without the WHERE, the Chair and Shelf lose their prices: both come back as NULL, because nothing in new_prices matched them. With WHERE EXISTS only the Desk and Lamp change, the same result as section 1. WHERE id IN (SELECT product_id FROM new_prices) filters the same rows when the match is on one column. The subquery must return at most one row per target row: SQLite quietly uses the first one, while PostgreSQL stops with more than one row returned by a subquery used as an expression. On Oracle, this is the form that works on every version.
3Update several columns at once
With UPDATE … FROM, list each column in SET, separated by commas; every assignment reads from the same matched row. Each assignment can be an expression, so a source table that holds only some changes can leave the other columns alone with COALESCE(new, current), where employees.salary is the row's current value.
Output
Ana moves to Finance on 61000. Ben moves to Sales and keeps his 41000, because his change row has NULL for the salary and COALESCE falls back to the current value. Caro has no change row and is untouched. In the subquery form you would need one subquery per column, or a row-value assignment: SET (dept, salary) = (SELECT c.dept, c.salary FROM changes AS c WHERE …). SQLite and PostgreSQL accept that; MySQL and SQL Server reject it as a syntax error, so use their join forms from section 5 there.
4Update from an aggregate: totals per customer
To store a count or a sum, aggregate first and join the result. Joining the detail table directly is a common mistake: a customer with three orders matches three rows, and an UPDATE writes each target row only once, from one of those matches, not their sum. Put the GROUP BY in a subquery in FROM so each customer meets exactly one row.
Output
The direct join stores a single order amount, 40.0 for Acme and 30.0 for Globex; here that happened to be each customer's last order, but which match wins isn't defined. Aggregating first gives Acme 3 orders worth 390.0 and Globex 2 worth 105.5. Initech has no orders, so it has no row in the subquery and keeps its defaults of 0. The portable version is a correlated aggregate, SET total = COALESCE((SELECT SUM(amount) FROM orders WHERE orders.customer_id = customers.id), 0), which needs no WHERE EXISTS: the COALESCE turns the empty sum into 0 on purpose.
5Other databases: PostgreSQL, MySQL and SQL Server
The join form is spelled differently in each database, while the correlated subquery from section 2 is the same everywhere. Each join form below and the subquery form were run on PostgreSQL 18, MySQL 8.4 and SQL Server (Babelfish) with the products example from section 1, and gave the same prices. The row-value form ran on PostgreSQL and failed on the other two.
| Syntax | Database | Notes |
|---|---|---|
| UPDATE t SET col = s.col FROM s WHERE s.id = t.id | PostgreSQL, SQLite 3.33+ | Don’t repeat t in FROM; put the match in WHERE. MySQL rejects this form. |
| UPDATE t JOIN s ON s.id = t.id SET t.col = s.col | MySQL | The join comes before SET. A GROUP BY subquery can be joined the same way. |
| UPDATE a SET a.col = s.col FROM t AS a JOIN s ON s.id = a.id | SQL Server | Name the alias after UPDATE; the target table appears in FROM. |
| SET col = (SELECT …) WHERE EXISTS (SELECT …) | All of them | Portable. Without WHERE EXISTS, unmatched rows become NULL. |
| SET (a, b) = (SELECT a, b …) | PostgreSQL, SQLite | Several columns from one subquery. Syntax error on MySQL and SQL Server. |
Frequently asked questions
How do I UPDATE from a SELECT in SQL Server?
Put the join in a FROM clause and name the target by its alias: UPDATE p SET p.price = np.price FROM products AS p JOIN new_prices AS np ON np.product_id = p.id;. Only rows that the join matches are updated. To update from an aggregate, join a subquery instead: JOIN (SELECT customer_id, SUM(amount) AS total FROM orders GROUP BY customer_id) AS t ON t.customer_id = c.id.
How do I update with a JOIN in MySQL?
MySQL puts the join between UPDATE and SET: UPDATE products AS p JOIN new_prices AS np ON np.product_id = p.id SET p.price = np.price;. MySQL has no UPDATE … FROM; writing it gives ERROR 1064 (42000), a syntax error near FROM new_prices.
Why did my UPDATE set the other rows to NULL?
You used SET col = (SELECT …) without a WHERE clause. The update then visits every row, and for rows the subquery finds no match for, it returns NULL. Add WHERE EXISTS (SELECT 1 FROM other WHERE other.id = t.id) with the same condition, or use the UPDATE … FROM / UPDATE … JOIN form, which only touches matched rows.
Why did UPDATE … FROM change every row in PostgreSQL?
The target table was listed again in FROM, as in UPDATE products SET price = np.price FROM products AS p JOIN new_prices AS np ON np.product_id = p.id. That second products is a separate copy, so nothing ties it to the row being updated: PostgreSQL reported UPDATE 4 and gave all four products the price 135.00 (SQLite gave all four 21.0). Leave the target out of FROM and write the match in WHERE: FROM new_prices AS np WHERE np.product_id = products.id.
What happens if the other table has several matching rows?
Each target row is still updated once, from one of the matches, and which one is not defined; in section 4, Acme got a single order amount of 40.0 instead of its total. With the subquery form, PostgreSQL refuses instead, with more than one row returned by a subquery used as an expression. Aggregate or deduplicate the source first so each target row matches one row.
Run it yourself
Open any of these in the SQLite playground to change the tables and run the update again.
SQLite playground