UPDATE Join in PostgreSQL

Learn how PostgreSQL UPDATE JOIN uses the FROM clause to update rows in one table using values from another, with join conditions in WHERE and RETURNING support.

5 min read · Back to overview

Quick Answer

PostgreSQL UPDATE join uses the FROM clause to join a second table and update the first based on matching values. Write UPDATE target SET col = source.col FROM source WHERE target.join_col = source.join_col — there is no JOIN keyword in this syntax.

Spin up a Postgres database in 20 seconds with Vela.

Try Vela Sandbox

Sometimes you need to update rows in one table using values from another table — for example, applying discount rates stored in a separate table to products, or syncing a denormalized column from an authoritative source. PostgreSQL handles this with the UPDATE ... FROM syntax, which joins the target table to one or more additional tables without using the JOIN keyword.

Syntax

UPDATE target_table
SET target_table.column = source_expression
FROM source_table
WHERE target_table.join_column = source_table.join_column;

The joined table goes in a FROM clause placed immediately after SET, and the join condition is expressed in WHERE. Table aliases keep complex statements readable:

UPDATE t
SET t.col = s.val
FROM source s
WHERE t.id = s.t_id;

Practical Example

Create a products table and a discount_rules table that holds category-level discounts:

CREATE TABLE product_categories (
  id       SERIAL PRIMARY KEY,
  name     VARCHAR(100) NOT NULL,
  discount NUMERIC(4, 2) NOT NULL DEFAULT 0  -- e.g. 0.15 = 15%
);

CREATE TABLE products (
  id          SERIAL PRIMARY KEY,
  name        VARCHAR(200) NOT NULL,
  list_price  NUMERIC(10, 2) NOT NULL,
  sale_price  NUMERIC(10, 2),
  category_id INT REFERENCES product_categories(id)
);

INSERT INTO product_categories (name, discount)
VALUES ('Electronics', 0.10),
       ('Accessories', 0.20),
       ('Furniture',   0.05);

INSERT INTO products (name, list_price, category_id)
VALUES ('Wireless Headset',   129.99, 1),
       ('Laptop Stand',        49.99, 2),
       ('USB-C Hub',           35.00, 2),
       ('Ergonomic Chair',    399.00, 3),
       ('Monitor',            349.99, 1);

Calculate and store the sale_price for every product based on its category discount:

UPDATE products p
SET sale_price = p.list_price - (p.list_price * c.discount)
FROM product_categories c
WHERE p.category_id = c.id;

Output:

UPDATE 5

Verify the results:

SELECT p.name, p.list_price, c.discount, p.sale_price
FROM products p
JOIN product_categories c ON c.id = p.category_id
ORDER BY p.id;
       name        | list_price | discount | sale_price
-------------------+------------+----------+------------
 Wireless Headset  |     129.99 |     0.10 |     116.99
 Laptop Stand      |      49.99 |     0.20 |      39.99
 USB-C Hub         |      35.00 |     0.20 |      28.00
 Ergonomic Chair   |     399.00 |     0.05 |     379.05
 Monitor           |     349.99 |     0.10 |     314.99

Use RETURNING to capture the updated values in the same statement:

UPDATE products p
SET sale_price = p.list_price - (p.list_price * c.discount)
FROM product_categories c
WHERE p.category_id = c.id
  AND c.name = 'Electronics'
RETURNING p.id, p.name, p.sale_price AS new_sale_price;

Multi-Table Update Join

You can join more than two tables by listing additional tables in FROM:

UPDATE products p
SET sale_price = p.list_price - (p.list_price * r.discount)
FROM product_categories c,
     regional_overrides r
WHERE p.category_id = c.id
  AND c.id = r.category_id
  AND r.region = 'EMEA';

Testing with Vela

UPDATE JOIN statements that touch many rows based on business rules from a lookup table are high-risk operations in production. Use Vela database branching to create a branch from your live data, run the update on the branch, and inspect the RETURNING output before applying the same statement to production. Branches are copy-on-write and take no additional disk space until rows are actually modified.

Production Tips

  • The FROM clause performs an implicit inner join — rows in the target table with no match in the source table are not updated.
  • If the source table can match multiple rows per target row, add a subquery or DISTINCT ON to guarantee a single match and avoid non-deterministic updates.
  • Run a SELECT with the same FROM and WHERE conditions before executing the UPDATE to preview exactly which rows and values will change.
  • Use RETURNING to capture updated values for audit logging or downstream processing within the same transaction.
  • When joining on foreign key columns, an index on the join column in the source table will improve performance significantly on large tables.
  • Table aliases are strongly recommended when both tables share column names to avoid ambiguous references in SET or WHERE.

Continue in Modifying Data: DELETE.

Related in this section: INSERT · INSERT Multiple Rows · UPDATE

Frequently Asked Questions

What does UPDATE JOIN do in PostgreSQL?
UPDATE JOIN updates rows in a target table using values from a second (joined) table. In PostgreSQL this is expressed with the FROM clause after SET, and the join condition goes in WHERE. It is equivalent to MySQL's UPDATE ... JOIN syntax but uses different keywords.
Does UPDATE JOIN lock the table?
Yes. UPDATE acquires a ROW EXCLUSIVE lock on the target table and row-level locks on each updated row. The joined table is read with a regular table scan; its rows are not locked.
What happens if the FROM table matches multiple rows per target row?
PostgreSQL picks one matching row non-deterministically. Only one update is applied to each target row, but which source row is used is undefined when there are multiple matches. Ensure your join condition produces a one-to-one relationship or add filters to make it deterministic.
Can I use UPDATE JOIN with more than two tables?
Yes. Add additional tables to the FROM clause separated by commas and include their join conditions in the WHERE clause. For example: UPDATE t1 SET col = t2.val FROM t2, t3 WHERE t1.id = t2.id AND t2.cat_id = t3.id.
What is the safest way to use UPDATE JOIN in production?
Preview the rows that will be changed by running a SELECT with the same FROM and WHERE conditions before executing the UPDATE. Use RETURNING to confirm the new values, and wrap the statement in a transaction so you can roll back if the result is unexpected.