如何在PostgreSQL中进行update + join操作?

775

基本上,我想要做到这个:

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

我相信这在MySQL(我的背景)中可以工作,但似乎在postgres中不起作用。 我收到的错误是:

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

肯定有一种简单的方法来做到这一点,但我找不到正确的语法。那么,在PostgreSQL中,我该怎么写呢?


6
PostgreSQL 的语法有所不同:http://www.postgresql.org/docs/8.1/static/sql-update.html - Marc B
11
车辆_车辆,货运_货运?这是一种有趣的表命名约定。 - CodeAndCats
5
哈哈……它看起来确实很有趣,不是吗?我想那时我正在使用Django,并且表是按特性分组的。所以会有一个名为 vehicles_* 的视图表和几个 shipments_* 表。 - mpen
如果在非唯一列上执行连接操作,则需要小心处理。这可能会导致不确定的结果 - Marmite Bomber
17个回答

1131

UPDATE语法如下:

[ 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 ] [, ...] ]

在你的情况下,我认为你想要这个:

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

或者如果您需要连接两个或多个表:

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;

3
如果更新依赖于整个表连接列表,那么这些连接应该放在UPDATE部分还是FROM部分? - ted.strauss
18
FROM语句可以包含一系列的表格名称。 - Mark Byers
4
从MySQL转来,发现在select语句中使用的相同连接方式添加一个set短语后不能用于update语句,这让人感到很不直观。不过,对于新手来说,这种语法可能更容易掌握。 - WEBjuju
1
@WEBjuju 我也是这么想的,使用这种方法将选择语句转换为更新语句需要额外的步骤,这很不方便。在我看来,这种语法方式也不够直观。 - cgage1
1
我在更新行中使用别名时遇到了错误;我将其删除后,就没有出现错误了。 - JosephDoggie
我不知道我已经访问过这个答案多少次了。但希望我能更新它超过一次。 - VishnuVS

276

在这种情况下,Mark Byers的答案是最优的选择。 尽管在更复杂的情况下,您可以采取返回行ID和计算值的选择查询,并将其附加到更新查询中,如下所示:

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

这种方法让你可以开发和测试选择查询语句,并在两个步骤中将其转换为更新查询语句。因此,在你的情况下,结果查询语句将是:
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

请注意,列别名是必需的,否则PostgreSQL会抱怨列名的歧义。

2
我真的很喜欢这个方法,因为我总是有点紧张,担心用“update”替换我的“select”,特别是在使用多个联接时。这减少了我在进行大规模更新之前需要执行的 SQL 转储数量 :) - dannysauer
8
不确定原因,但是这个查询的CTE版本比上面的“普通连接”解决方案快得多。 - paul.ago
2
这种解决方案的另一个优点是能够使用多个连接在with/select语句中从超过两个表中进行联接,以达到计算最终值的目的。 - Alex Muro
2
这真是太棒了。我已经完成了我的选择,并像@dannysauer一样,很害怕转换的过程。但这个工具简单地为我完成了所有的转换。完美! - frostymarvelous
1
你的第一个 SQL 示例存在语法错误。"update t1" 不能使用 t 子查询中的别名,它需要使用表名:"update table1"。你在第二个示例中做得很正确。 - EricS
显示剩余4条评论

190

让我通过我的例子进行更详细的解释。

任务:更正信息,即有些准备离开中学的学生(abiturients)在收到学校证书之前就已经提交了大学申请(是的,他们先收到了证书,而后才被发放(按照指定的证书日期)。因此,我们将增加申请提交日期以适应证书发放日期。

因此,下一个类似于 MySQL 的语句如下:

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;

以类似于PostgreSQL的方式变得如此

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

正如您所看到的,原始子查询的JOIN子句已经成为WHERE条件之一,这些条件由AND与其他条件组合而成,并且这些条件都没有被修改。而且不再需要将表格与自身进行JOIN(就像在子查询中一样)。


30
如何连接第三张表? - user3871
29
您只需要像往常一样在FROM列表中将其JOIN起来: FROM abiturients b JOIN addresses c ON c.abiturient_id = b.id - Envek
@Envek - 很遗憾,你不能在那里使用JOIN,我刚刚检查过了。https://www.postgresql.org/docs/10/static/sql-update.html - Adrian Smith
6
@AdrianSmith,您不能在UPDATE本身中使用JOIN,但可以在UPDATE的“from_list”子句中使用它(这是PostgreSQL对SQL的扩展)。此外,请参阅链接提供的有关连接表注意事项的注释。 - Envek
@Envek,你能给一个包含多个联接的from_list示例吗?A到B可能不是直接连接,有时还会有C,然后我们就需要a.c_id = c.id和c.b_id = b.id来连接A和B。 - user1735921

152

对于那些真正想要执行 JOIN 操作的人,你也可以使用以下方法:

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;

如果需要,你可以在等号右边的SET部分中使用a_alias。等号左边的字段不需要表引用,因为它们被视为来自原始的"a"表。


20
考虑到这是第一个在实际联接中(而不是在with子查询内部)使用的答案,这应该是真正被接受的答案。或者,为避免混淆,应该将此问题重命名,以确定postgresql是否支持更新中的联接。 - necklace
8
请注意,根据文档(https://www.postgresql.org/docs/11/sql-update.html),在from子句中列出目标表将导致目标表进行自身连接。我不太确定,但似乎这是一个跨自连接,可能会产生意外结果和/或性能影响。 - Ben Collins
4
只是提供信息,我尝试了这个操作,更新的行数与使用相同的连接和条件从选择查询返回的行数不同。 - Alvaro Carvalho

38

如果您想进行JOIN操作,只更新连接返回的行,请使用:

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;

这个问题在上面已经提到过,但只是通过评论的形式。由于它对于获得正确结果至关重要,因此需要发布一个新的可行答案。


@FlipVernooij 发表评论时,请具体说明所引用链接的哪一部分,并/或引用该部分,除非整个链接适用或链接的适用部分非常明显。在这种情况下,您所提到的链接中到底指的是什么,完全没有任何明显的迹象,这让我们所有人都感到困惑,浪费时间搜索链接文档并返回问题,“有什么副作用?? - Michael Goldshteyn
1
通过在答案中添加最后一行 AND a.pk_id = a_alias.pk_id这里没有交叉连接,答案是有效的。将链接和对Ben评论的引用放在那里只会让读者陷入一场完全浪费时间的野鸡追逐游戏中,试图理解你所指的内容。 - Michael Goldshteyn
我尝试了这个,它似乎可以工作 https://www.postgresqltutorial.com/postgresql-tutorial/postgresql-update-join/ - ssi-anik

11

开始吧:

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

我尽可能地简化了它。


@littlegreen 你确定吗?join 不会限制它吗? - mpen
5
我可以确认它会将所有记录更新为一个值。它并不会做你期望的事情。 - Adam Gordon Bell
为什么这个答案的一些文本被划掉了? - Lee Goddard
从之前的版本来看,显然它没有起作用,所以我将其划掉了。现在我会将其删除。 - mpen
我在博客https://www.postgresqltutorial.com/postgresql-tutorial/postgresql-update-join/中找到了类似的内容,看起来可以正常工作。@AdamGordonBell - ssi-anik

3

除了以上所有很好的答案之外,当您想要更新一个 join-table 时,可能会遇到两个问题:

  • 您不能使用您想要更新的表格与另一个表进行JOIN
  • Postgres在JOIN后需要一个ON子句,所以您不能仅使用where子句。

这意味着基本上以下查询是无效的:

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'

相反,你必须像这样在 FROM 子句中使用所有表:

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'

对于某些人来说,这可能很简单,但我却在这个问题上卡住了,不知道发生了什么,希望能帮助其他人。


2
这是一个简单的 SQL,它使用来自 Name 表的 Middle_Name 字段更新 Name3 表上的 Mid_Name:
update name3
set mid_name = name.middle_name
from name
where name3.person_id = name.person_id;

2
以下链接有一个示例,可以解决并更好地帮助理解如何使用postgres的updatejoin
UPDATE product
SET net_price = price - price * discount
FROM
product_segment
WHERE
product.segment_id = product_segment.id;

请参见:http://www.postgresqltutorial.com/postgresql-update-join/


2

第一张表格名称:tbl_table1(tab1)。 第二张表格名称:tbl_table2(tab2)。

将 tbl_table1 表格的 ac_status 列设置为 "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';

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接