ChatGPT解决这个技术问题 Extra ChatGPT

How to do an update + join in PostgreSQL?

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?

Postgres syntax is different: postgresql.org/docs/8.1/static/sql-update.html
vehicles_vehicle, shipments_shipment? That's an interesting table naming convention
@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.

M
Mark Byers

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 

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.
A
Alvin

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.


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.
A
AntiPawn79

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).


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 - Ah, thank you for the clarification, I missed that.
D
Daniel L. VanDenBosch

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.


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.
N
Nate Smith

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


@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.
m
mpen

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. Thanks guys!

Can also do this:

-- Doesn't work apparently
update vehicles_vehicle 
set price=s.price_per_vehicle
from vehicles_vehicle v
join shipments_shipment s on v.shipment_id=s.id;

But then you've got the vehicle table in there twice, and you're only allowed to alias it once, and you can't use the alias in the "set" portion.


@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.
B
Blockost

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.


m
mpen

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;

A
Alessandro

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/


A
AritraDB

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';