如何在PostgreSQL中进行更新+加入?

mpe*_*pen 467 postgresql syntax

基本上,我想这样做:

update vehicles_vehicle v 
    join shipments_shipment s on v.shipment_id=s.id 
set v.price=s.price_per_vehicle;
Run Code Online (Sandbox Code Playgroud)

我很确定它可以在MySQL(我的背景)中工作,但它似乎不适用于postgres.我得到的错误是:

ERROR:  syntax error at or near "join"
LINE 1: update vehicles_vehicle v join shipments_shipment s on v.shi...
                                  ^
Run Code Online (Sandbox Code Playgroud)

当然有一种简单的方法可以做到这一点,但我找不到合适的语法.那么,我如何在PostgreSQL中编写这个?

Mar*_*ers 724

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 
Run Code Online (Sandbox Code Playgroud)

  • @ ted.strauss:FROM可以包含一个表列表. (8认同)
  • 来自mysql,不直观的是,用于“select”的相同连接不会仅通过添加“set”短语来“更新”:(仍然-对于sql新手来说,这种语法可能更容易掌握。 (3认同)
  • 如果更新依赖于整个表连接列表,它们应该在 UPDATE 部分还是 FROM 部分? (2认同)

Env*_*vek 122

让我通过我的例子再解释一下.

任务:正确的信息,其中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;
Run Code Online (Sandbox Code Playgroud)

以这种方式成为类似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
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,原始子查询JOINON子句已成为其中一个WHERE条件,它AND与其他条件相结合,这些条件已从子查询中移除而没有任何更改.并且不再需要自己JOIN表(就像在子查询中一样).

  • 你只需要在`FROM`列表中"加入"它:```FROM abiturients b JOIN address c ON c.abiturient_id = b.id``` (15认同)
  • 你会如何加入第三张桌子? (14认同)
  • @AdrianSmith,您不能在UPDATE本身中使用JOIN,但可以在UPDATE的`from_list`子句(这是PostgreSQL的SQL扩展)中使用它。另外,请参阅您提供的链接上有关加入表注意事项的注释。 (2认同)

小智 114

Mark Byers的答案在这种情况下是最佳的.虽然在更复杂的情况下,您可以使用返回rowid和计算值的select查询,并将其附加到更新查询,如下所示:

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 t1
set value = t.calculatedvalue
from t
where id = t.rowid
Run Code Online (Sandbox Code Playgroud)

此方法允许您开发和测试您的选择查询,并分两步将其转换为更新查询.

因此,在您的情况下,结果查询将是:

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
Run Code Online (Sandbox Code Playgroud)

请注意,列别名是必需的,否则PostgreSQL会抱怨列名的含糊不清.

  • 不确定原因,但此查询的CTE版本比上面的"普通联接"解决方案更快 (5认同)
  • 我真的很喜欢这个,因为我总是对将“选择”从顶部去掉并用“更新”替换它感到有点紧张,尤其是在多个连接的情况下。这减少了我在批量更新之前必须执行的 SQL 转储的数量。:) (2认同)
  • 该解决方案的另一个优点是能够通过在 with / select 语句中使用多个联接来联接两个以上的表以获得最终的计算值。 (2认同)
  • 这太棒了。我精心设计了我的选择,就像@dannysauer 一样,我害怕这种转换。这对我来说很简单。完美的! (2认同)

Fas*_*ngy 58

对于那些真正想要做的人,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;
Run Code Online (Sandbox Code Playgroud)

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

  • 为此,我必须添加:`AND a.pk_id = a_alias.pk_id` (19认同)
  • 需要注意的是,根据文档(https://www.postgresql.org/docs/11/sql-update.html),在from子句中列出目标表将导致目标表自联接。不太自信的是,在我看来,这是一个交叉自连接,这可能会产生意想不到的结果和/或性能影响。 (9认同)
  • 仅供参考,我尝试了此操作,更新的行数与具有相同联接和 where 子句的选择查询返回的行数不同。 (5认同)
  • 考虑到这是带有实际连接的第一个答案(而不是带子查询的内部答案),这应该是真正的可接受答案。应该重命名该问题,以免混淆postgresql是否支持update中的联接。 (3认同)

小智 10

对于那些想要执行JOIN的用户,只更新您的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;
Run Code Online (Sandbox Code Playgroud)

上面已经提到了这一点,但仅是通过评论。由于获取正确结果并发布有效的新答案至关重要


mpe*_*pen 5

开始了:

update vehicles_vehicle v
set price=s.price_per_vehicle
from shipments_shipment s
where v.shipment_id=s.id;
Run Code Online (Sandbox Code Playgroud)

简单,因为我可以做到.多谢你们!

也可以这样做:

-- 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;
Run Code Online (Sandbox Code Playgroud)

但是那时你已经有两次车辆表,并且你只允许别名一次,你不能在"set"部分使用别名.

  • 第二个版本更新表​​中的所有记录,从而破坏它...... (6认同)
  • @mpen我可以确认它将所有记录更新为一个值.它不会做你所期望的. (4认同)