888

Basically, I want to do this:

update vehicles_vehicle v join shipments_shipment s on v.shipment_id=s.id set v.price=s.price_per_vehicle; 

I'm pretty sure that would work in MySQL (my background), but it doesn't seem to work in postgres. The error I get is:

ERROR: syntax error at or near "join" LINE 1: update vehicles_vehicle v join shipments_shipment s on v.shi... ^ 

Surely there's an easy way to do this, but I can't find the proper syntax. So, how would I write this In PostgreSQL?

4
  • 6
    Postgres syntax is different: postgresql.org/docs/8.1/static/sql-update.html Commented Oct 23, 2011 at 22:12
  • 14
    vehicles_vehicle, shipments_shipment? That's an interesting table naming convention Commented Mar 2, 2017 at 4:33
  • 5
    @CodeAndCats Haha...it does look funny doesn't it? I think I was using Django at the time, and the tables are grouped by feature. So there would have been a view vehicles_* tables, and a few shipments_* tables. Commented Mar 2, 2017 at 20:04
  • Some care should be taken if the join is performed on a non-unique column. This may lead to a non deterministic outcome. Commented Feb 28, 2023 at 12:44

18 Answers 18

1305

The UPDATE syntax is:

[ WITH [ RECURSIVE ] with_query [, ...] ] UPDATE [ ONLY ] table [ [ AS ] alias ] SET { column = { expression | DEFAULT } | ( column [, ...] ) = ( { expression | DEFAULT } [, ...] ) } [, ...] [ FROM from_list ] [ WHERE condition | WHERE CURRENT OF cursor_name ] [ RETURNING * | output_expression [ [ AS ] output_name ] [, ...] ] 

In your case I think you want this:

UPDATE vehicles_vehicle AS v SET price = s.price_per_vehicle FROM shipments_shipment AS s WHERE v.shipment_id = s.id 

Or if you need to join on two or more tables:

UPDATE table_1 t1 SET foo = 'new_value' FROM table_2 t2 JOIN table_3 t3 ON t3.id = t2.t3_id WHERE t2.id = t1.t2_id AND t3.bar = True; 
Sign up to request clarification or add additional context in comments.

7 Comments

If the update relies on a whole list of table joins, should those be in the UPDATE section or the FROM section?
@ted.strauss: The FROM can contain a list of tables.
coming from mysql it's unintuitive that the same join used for select won't also update just by adding a set phrase :( still - the syntax for this is probably easier for a newcomer to sql to master.
@WEBjuju my thoughts exactly, converting a select statement into an update requires an additional step with this method which is inconvenient. The syntax is also not quite as intuitive this way (in my opinion).
I got an error with the alias in the update line; I removed it and there was NOT an error.
|
313

The answer of Mark Byers is the optimal in this situation. Though in more complex situations you can take the select query that returns rowids and calculated values and attach it to the update query like this:

with t as ( -- Any generic query which returns rowid and corresponding calculated values select t1.id as rowid, f(t2, t2) as calculatedvalue from table1 as t1 join table2 as t2 on t2.referenceid = t1.id ) update table1 set value = t.calculatedvalue from t where id = t.rowid 

This approach lets you develop and test your select query and in two steps convert it to the update query.

So in your case the result query will be:

with t as ( select v.id as rowid, s.price_per_vehicle as calculatedvalue from vehicles_vehicle v join shipments_shipment s on v.shipment_id = s.id ) update vehicles_vehicle set price = t.calculatedvalue from t where id = t.rowid 

Note that column aliases are mandatory otherwise PostgreSQL will complain about the ambiguity of the column names.

9 Comments

I really like this one because I'm always a tad nervous with taking my "select" off the top and replacing it with an "update," especially with multiple joins. This reduces the number of SQL dumps I should have to do before mass updates. :)
Not sure why, but the CTE version of this query is way way faster than the "plain join" solutions above
The other advantage of this solution is the ability join from more than two tables to get to your final calculated value by using multiple joins in the with / select statement.
This is awesome. I had my select crafted and like @dannysauer, I was scared of the conversion. This simply does it for me. Perfect!
Your first SQL example has a syntax error. "update t1" cannot use the alias from the t subquery, it needs to use the table name: "update table1". You do this correctly in your second example.
|
196

Let me explain a little more by my example.

Task: correct info, where abiturients (students about to leave secondary school) have submitted applications to university earlier, than they got school certificates (yes, they got certificates earlier, than they were issued (by certificate date specified). So, we will increase application submit date to fit certificate issue date.

Thus. next MySQL-like statement:

UPDATE applications a JOIN ( SELECT ap.id, ab.certificate_issued_at FROM abiturients ab JOIN applications ap ON ab.id = ap.abiturient_id WHERE ap.documents_taken_at::date < ab.certificate_issued_at ) b ON a.id = b.id SET a.documents_taken_at = b.certificate_issued_at; 

Becomes PostgreSQL-like in such a way

UPDATE applications a SET documents_taken_at = b.certificate_issued_at -- we can reference joined table here FROM abiturients b -- joined table WHERE a.abiturient_id = b.id AND -- JOIN ON clause a.documents_taken_at::date < b.certificate_issued_at -- Subquery WHERE 

As you can see, original subquery JOIN's ON clause have become one of WHERE conditions, which is conjucted by AND with others, which have been moved from subquery with no changes. And there is no more need to JOIN table with itself (as it was in subquery).

5 Comments

How would you join a third table?
You just JOIN it as usual in the FROM list: FROM abiturients b JOIN addresses c ON c.abiturient_id = b.id
@Envek - You can't use JOIN there alas, I just checked. postgresql.org/docs/10/static/sql-update.html
@AdrianSmith, you can't use JOIN in UPDATE itself, but can use it in UPDATE's from_list clause (which is PostgreSQL's extension of SQL). Also, see notes about joining tables caveats on the link you provided.
@Envek can you give a from_list example with multiple joins as well ? a to b might not be direct link, sometimes c is there too and then we have to do a.c_id = c.id and c.b_id = b.id , to connect a and b.
158

For those actually wanting to do a JOIN you can also use:

UPDATE a SET price = b_alias.unit_price FROM a AS a_alias LEFT JOIN b AS b_alias ON a_alias.b_fk = b_alias.id WHERE a_alias.unit_name LIKE 'some_value' AND a.id = a_alias.id; 

You can use the a_alias in the SET section on the right of the equals sign if needed. The fields on the left of the equals sign don't require a table reference as they are deemed to be from the original "a" table.

5 Comments

Considering this is the first answer with an actual join in (and not inside a with subquery), this should be the real accepted answer. Either that or this question should be renamed to avoid confusion whether postgresql supports joins in update or not.
It should be noted that according to the documentation (postgresql.org/docs/11/sql-update.html), listing the target table in the from clause will cause the target table to be self-joined. Less confidently, it also appears to me that this is a cross-self-join, which may have unintended results and/or performance implications.
Just FYI, I tried this and the number of rows updated was different than the number of rows returned from the select query with same join and where clauses.
I tried this with CockroachDB on a test env and it updated every record in the table regardless of JOIN and WHERE conditions.
@Vadzim "AND a.id = a_alias.id" is unnecessary. This is true for all rows, and might be causing all rows to update.
42

For those wanting to do a JOIN that updates ONLY the rows your join returns use:

UPDATE a SET price = b_alias.unit_price FROM a AS a_alias LEFT JOIN b AS b_alias ON a_alias.b_fk = b_alias.id WHERE a_alias.unit_name LIKE 'some_value' AND a.id = a_alias.id --the below line is critical for updating ONLY joined rows AND a.pk_id = a_alias.pk_id; 

This was mentioned above but only through a comment..Since it's critical to getting the correct result posting NEW answer that Works

3 Comments

@FlipVernooij When posting a comment referring to a link, please be specific about the part of the link being reference and/or quote the part, unless the entire link applies or the applicable portion of the link is very obvious. In this case there is nothing whatsoever obvious about what you are referring to at the link referenced, This leaves all of us bewildered, after wasting time searching the documentation linked and returning with the question, "What side-effects??"
@FlipVernooij With the addition of the last line in the answer, AND a.pk_id = a_alias.pk_id, there is no cross-join here and the answer is valid. The link and reference to Ben's comment can only lead readers to a wild goose chase and a complete waste of their time, trying to understand what you are referring to.
I tried this one, and it seemed to be working postgresqltutorial.com/postgresql-tutorial/…
11

Here we go:

UPDATE vehicles_vehicle v SET price = s.price_per_vehicle FROM shipments_shipment s WHERE v.shipment_id = s.id; 

Simple as I could make it.

5 Comments

@littlegreen You sure about that? Doesn't the join constrain it?
@mpen I can confirm that it updates all records to one value. it does not do what you would expect.
Why is some of this answer's text cross-out?
From an earlier revision. Apparently it didn't work so I crossed it out. I'll delete it now.
I found the similar one in a blog postgresqltutorial.com/postgresql-tutorial/… and it seems like working as expected. @AdamGordonBell
10

WORKS PERFECT!!!

POSTGRE SQL - UPDATE With a JOIN

BELOW CODE - Check the positioning of columns and IDs as below:

If you place it exactly as below, then only it will work!

---IF you want to update This table1 using table2 UPDATE table1 SET attribute1 = table2.attribute2 FROM table2 WHERE table2.product_ID = table1.product_ID; 

Comments

4

To add something quite important to all the great answers above, when you want to update a join-table, you may have 2 problems:

  • you cannot use the table you want to update to JOIN another one
  • Postgres wants a ON clause after the JOIN so you cannot only use where clauses.

This means that basically, the following queries are not valid:

UPDATE join_a_b SET count = 10 FROM a JOIN b on b.id = join_a_b.b_id -- Not valid since join_a_b is used here WHERE a.id = join_a_b.a_id AND a.name = 'A' AND b.name = 'B' 
UPDATE join_a_b SET count = 10 FROM a JOIN b -- Not valid since there is no ON clause WHERE a.id = join_a_b.a_id AND b.id = join_a_b.b_id a.name = 'A' AND b.name = 'B' 

Instead, you must use all the tables in the FROM clause like this:

UPDATE join_a_b SET count = 10 FROM a, b WHERE a.id = join_a_b.a_id AND b.id = join_a_b.b_id AND a.name = 'A' AND b.name = 'B' 

It might be straightforward for some but I got stuck on this problem wondering what's going on so hopefully, it will help others.

Comments

2

Here's a simple SQL that updates Mid_Name on the Name3 table using the Middle_Name field from Name:

update name3 set mid_name = name.middle_name from name where name3.person_id = name.person_id; 

Comments

2

The link below has a example that resolve and helps understant better how use update and join with postgres.

UPDATE product SET net_price = price - price * discount FROM product_segment WHERE product.segment_id = product_segment.id; 

See: http://www.postgresqltutorial.com/postgresql-update-join/

Comments

2

First Table Name: tbl_table1 (tab1). Second Table Name: tbl_table2 (tab2).

Set the tbl_table1's ac_status column to "INACTIVE"

update common.tbl_table1 as tab1 set ac_status= 'INACTIVE' --tbl_table1's "ac_status" from common.tbl_table2 as tab2 where tab1.ref_id= '1111111' and tab2.rel_type= 'CUSTOMER'; 

Comments

2

Some care should be taken if the join is performed on a non-unique column. I.e. the result of the join produces more values that can be used in the update.

Some RDMS raise an exception is this case, but PostgreSQL apparently performs the update with a non deterministic outcome.

Example

Tested on 14.1

create table tab as select * from (values (1,'a'), (2,'b') ) t(id, att); 

We use a CTE where the id = 1 id giving two possible values for the update. Using the order by in the CTE we get a different results.

with t as ( select * from (values (1,'c'), (1,'d') ) t(id, att) order by 2 /* Based on this order different update is performed */ ) update tab set att = t.att from t where tab.id = t.id 

With the ascendig order the column is updated to the value of d (highest value)

id|att| --+---+ 1|d | 2|b | 

while using a descending order in the CTE the column is updated to the value of c (lowest value)

id|att| --+---+ 1|c | 2|b | 

The moral of the story always check if the join produce a unique result.

The relevant part of the documentation

When using FROM you should ensure that the join produces at most one output row for each row to be modified. In other words, a target row shouldn't join to more than one row from the other table(s). If it does, then only one of the join rows will be used to update the target row, but which one will be used is not readily predictable.

Comments

1

To UPDATE one Table using another, in PostGRE SQL / AWS (SQL workbench).

In PostGRE SQL, this is how you need to use joins in UPDATE Query:

UPDATE TABLEA set COLUMN_FROM_TABLEA = COLUMN_FROM_TABLEB FROM TABLEA,TABLEB WHERE FILTER_FROM_TABLEA = FILTER_FROM_TABLEB; Example: 
Update Employees Set Date_Of_Exit = Exit_Date_Recorded , Exit_Flg = 1 From Employees, Employee_Exit_Clearance Where Emp_ID = Exit_Emp_ID 

Table A - Employees Columns in Table A - Date_Of_Exit,Emp_ID,Exit_Flg Table B is - Employee_Exit_Clearance Columns in Table B - Exit_Date_Recorded,Exit_Emp_ID

1760 rows affected

Execution time: 29.18s

Comments

0

--goal: update selected columns with join (postgres)--

UPDATE table1 t1 SET column1 = 'data' FROM table1 RIGHT JOIN table2 ON table2.id = table1.id WHERE t1.id IN (SELECT table2.id FROM table2 WHERE table2.column2 = 12345) 

Comments

0

In case you don't have the value in one column but instead had to calculate it from the other table (in this example price_per_vehicle from shipments_shipment). Then assuming that shipments_shipment has price and vehicle_id columns the update for a specific vehicle could look like this:

-- Specific vehicle in this example is with id = 5 WITH prices AS ( SELECT SUM(COALESCE(s.price, 0)) AS price_per_vehicle FROM shipments_shipment AS s WHERE s.vehicle_id = 5 ) UPDATE vehicles_vehicle AS v SET v.price = prices.price_per_vehicle FROM prices WHERE v.id = 5 

Comments

0

EDIT: do not use, execution time increases quadratically

It's a pity that the runtime is so bad, because the syntax was very elegant. I'm leaving this answer up to save others from going down this path.


This answer is different from the rest because you don't have to repeat the join condition.

  • You join once in the FROM clause
  • and the WHERE clause checks that rows from X are present in (X as X_joined join Y).

As a result this works with natural joins, which is very nice.

Example query

Say that you have a table shipment that you want to augment with information from table vehicle, and both tables have a column vehicle_id so you can use NATURAL JOIN.

---- DO NOT USE, quadratic runtime ---- EXPLAIN UPDATE shipment SET shipment.speed = vehicle.average_speed FROM shipment s_joined NATURAL JOIN vehicle WHERE -- This is the magic condition -- (EDIT: ... it probably causes the quadratic runtime, too) shipment = s_joined -- any further limitations go here: AND shipment.destination = 'Perth' 

Minimal working example

-- A table with shipments, some with missing speeds create temporary table shipment ( vehicle_id varchar(20), cargo varchar(20), speed integer ); insert into shipment values ('cart', 'flowers', 60), ('boat', 'cabbage', null), ('cart', 'potatos', null), ('foot', 'carrots', null); -- A table with vehicles whose average speed we know about create temporary table vehicle ( vehicle_id varchar(20), average_speed integer ); insert into vehicle values ('cart', 6), ('foot', 5); -- If the shipment has vehicle info, update its speed ---- DO NOT USE, quadratic runtime ---- UPDATE shipment SET speed = vehicle.average_speed FROM shipment as s_joined natural join vehicle WHERE shipment = s_joined AND shipment.speed is null; -- After: TABLE shipment; ┌────────────┬─────────┬───────┐ │ vehicle_id │ cargo │ speed │ ├────────────┼─────────┼───────┤ │ cart │ flowers │ 60 │ <- not updated: speed was not null │ boat │ cabbage │ │ <- not updated: no boat in join │ cart │ potatos │ 6 │ <- updated │ foot │ carrots │ 5 │ <- updated └────────────┴─────────┴───────┘ 

Comments

0

If you want to check whether your joins are proper or not you can run your update query in transaction, using this you can run select query on original table which gives you exactly those results which you are looking for, and after confirmation you can finalize the query.

BEGIN; -- Begin the transaction UPDATE schema.village vill SET wst_id = vill_view.wst_id_023::INT, --if you want to cast the column type wst_dist = vill_view.wst_dist_023::numeric --if you want to cast the column type FROM schema.village_view vill_view WHERE vill.id = vill_view.id::double precision; --if you want to cast the column type -- Verify the result SELECT * FROM schema.village -- ROLLBACK; -- if your query fails or fills that something is wrong you can reverse the all updates and table will remain as original -- COMMIT -- if you fills all are good (no return to original table once commited) 

Comments

-1

The first way is slower than the second way.

First:

DO $$ DECLARE page int := 10000; min_id bigint; max_id bigint; BEGIN SELECT max(id),min(id) INTO max_id,min_id FROM opportunities; FOR j IN min_id..max_id BY page LOOP UPDATE opportunities SET sec_type = 'Unsec' FROM opportunities AS opp INNER JOIN accounts AS acc ON opp.account_id = acc.id WHERE acc.borrower = true AND opp.sec_type IS NULL AND opp.id >= j AND opp.id < j+page; COMMIT; END LOOP; END; $$; 

Second:

DO $$ DECLARE page int := 10000; min_id bigint; max_id bigint; BEGIN SELECT max(id),min(id) INTO max_id,min_id FROM opportunities; FOR j IN min_id..max_id BY page LOOP UPDATE opportunities AS opp SET sec_type = 'Unsec' FROM accounts AS acc WHERE opp.account_id = acc.id AND opp.sec_type IS NULL AND acc.borrower = true AND opp.id >= j AND opp.id < j+page; COMMIT; END LOOP; END; $$; 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.