How do you update the rows of one table using values read from another table?
There are two portable-ish ways. The first uses a correlated subquery in the SET clause: write the new value as a subquery that looks up the other table, and restrict the WHERE clause to the rows you want to change. The second is UPDATE ... FROM, which PostgreSQL, SQLite and SQL Server all support and which reads more like a join.
CREATE TABLE products (
id INTEGER PRIMARY KEY,
name VARCHAR(120) NOT NULL,
price_cents INTEGER NOT NULL
);
CREATE TABLE price_updates (
product_id INTEGER PRIMARY KEY,
new_price_cents INTEGER NOT NULL
);
INSERT INTO products (id, name, price_cents) VALUES
(1, 'Mechanical Keyboard', 7950),
(2, 'USB-C Hub', 3200),
(3, '27-inch Monitor', 24999);
INSERT INTO price_updates (product_id, new_price_cents) VALUES
(1, 7499);
UPDATE products
SET price_cents = (
SELECT u.new_price_cents
FROM price_updates AS u
WHERE u.product_id = products.id
)
WHERE id IN (SELECT product_id FROM price_updates);
SELECT id, name, price_cents FROM products ORDER BY id;Three rows come back. The Mechanical Keyboard is now 7499 cents, down from 7950, and the other two are unchanged. The WHERE clause is what keeps the update scoped: it restricts the change to the products that actually have a pending update, so the other rows do not receive a NULL.
That last point is the trap in this pattern. If you write the subquery in the SET clause but leave the WHERE clause out, every row gets the subquery's result, and for the products with no matching update the subquery returns NULL — so their price becomes NULL. The SET subquery and the WHERE clause have to agree about which rows are in scope.
The UPDATE ... FROM form expresses the same thing more directly. It joins the source table to the target in the update itself, so the WHERE clause can simply filter on the source, and there is no way to accidentally update the wrong rows. It is supported by PostgreSQL, SQLite from 3.33 and SQL Server, and MySQL uses a different syntax entirely — a multi-table UPDATE with a comma and an explicit join condition.