PostgreSQL 中文参考文档
🌐 英文原版 官方 ↗

UPDATE

UPDATE — 更新表中的行

Synopsis

[ WITH [ RECURSIVE ] with_query [, ...] ]
UPDATE [ ONLY ] table_name [ * ] [ [ AS ] alias ]
    SET { column_name = { expression | DEFAULT } |
          ( column_name [, ...] ) = [ ROW ] ( { expression | DEFAULT } [, ...] ) |
          ( column_name [, ...] ) = ( sub-SELECT )
        } [, ...]
    [ FROM from_item [, ...] ]
    [ WHERE condition | WHERE CURRENT OF cursor_name ]
    [ RETURNING [ WITH ( { OLD | NEW } AS output_alias [, ...] ) ]
                { * | output_expression [ [ AS ] output_name ] } [, ...] ]

描述

UPDATE 修改满足条件的所有行中指定列的值。SET 子句中只需要提及要修改的列;未显式修改的列将保留其原来的值。

使用数据库中其他表所包含的信息来修改表有两种方式:使用子查询,或者在 FROM 子句中指定额外的表。哪种技术更合适取决于具体情况。

可选的 RETURNING 子句使得 UPDATE 基于实际被更新的每一行计算并返回值。可以计算使用该表的列和/或 FROM 中提到的其他表的列的任何表达式。默认情况下,使用表中列的新值(更新后的值),但也可以请求旧值(更新前的值)。RETURNING 列表的语法与 SELECT 的输出列表完全相同。

必须对表拥有 UPDATE 特权,或至少对被列出要更新的列拥有该特权。你还必须拥有 SELECT 特权,适用于其值在 expressions 或 condition 中被读取的任何列。

参数

with_query

WITH 子句允许你指定一个或多个子查询,这些子查询可以在 UPDATE 查询中按名称引用。详见 Section 7.8 和 SELECT。

table_name

要更新的表的名字(可以用模式限定)。如果在表名之前指定了 ONLY,则只在指定的表中更新匹配的行。如果没有指定 ONLY,则也会在继承自指定表的任何表中更新匹配的行。也可以在表名之后指定 *,以显式表示包含后代表。

alias

目标表的替代名称。提供别名后,它会完全隐藏表的实际名称。例如,给定 UPDATE foo AS f,UPDATE 语句的其余部分必须用 f 而不是 foo 来引用这个表。

column_name

由 table_name 命名的表中某一列的名称。如有需要,列名可以用子字段名或数组下标限定。不要在目标列的指定中包含表名 — 例如,UPDATE table_name SET table_name.col = 1 是无效的。

expression

要赋给该列的表达式。该表达式可以使用表中此列和其他列的旧值。

DEFAULT

将该列设置为它的默认值(如果没有为它指定特定的默认值表达式,则该值将为 NULL)。标识列将被设置为由关联序列生成的新值。对于生成列,允许指定此项,但这只是指定了从生成表达式计算该列的正常行为。

sub-SELECT

一个 SELECT 子查询,它产生的输出列与其前面带括号的列列表中所列出的列一样多。该子查询在执行时最多只能产生一行。如果它产生一行,其列值将被赋给目标列;如果它不产生行,则会将 NULL 值赋给目标列。该子查询可以引用被更新的表当前行的旧值。

from_item

一个表表达式,允许其他表的列出现在 WHERE 条件和更新表达式中。它使用的语法与 FROM 子句相同,如同在 SELECT 语句中一样;例如,可以为表名指定别名。不要将目标表重复作为 from_item,除非你打算进行自连接(在这种情况下,它必须以别名的形式出现在 from_item 中)。

condition

一个返回 boolean 类型值的表达式。只有该表达式返回 true 的行才会被更新。

cursor_name

在 WHERE CURRENT OF 条件中使用的游标的名称。要更新的行是最近从此游标取出的那一行。该游标必须是在 UPDATE 目标表上的非分组查询。注意,WHERE CURRENT OF 不能与布尔条件一起指定。请参见 DECLARE 以了解有关在 WHERE CURRENT OF 中使用游标的更多信息。

output_alias

RETURNING 列表中 OLD 或 NEW 行的可选替代名称。

默认情况下,可以通过写 OLD.column_name 或 OLD.* 来返回目标表中的旧值,通过写 NEW.column_name 或 NEW.* 来返回新值。提供别名后,这些名称将被隐藏,必须使用该别名来引用旧行或新行。例如 RETURNING WITH (OLD AS o, NEW AS n) o.*, n.*。

output_expression

在每一行被更新之后,由 UPDATE 命令计算并返回的表达式。该表达式可以使用由 table_name 命名的表或 FROM 中列出的表的任何列名。写入 * 可返回所有列。

列名或 * 可以用 OLD 或 NEW 限定,或者用相应的 output_alias(对应 OLD 或 NEW)来限定,以返回旧值或新值。不带限定的列名或 *,或者用目标表名或别名限定的列名或 *,将返回新值。

output_name

用于返回列的名称。

输出

成功完成时,UPDATE 命令会返回如下形式的命令标签:

UPDATE count

count 是被更新的行数,包括值没有变化的匹配行。注意,该数字可能小于匹配 condition 的行数,前提是更新被 BEFORE UPDATE 触发器所抑制。如果 count 为 0,则该查询没有更新任何行(这不被视为错误)。

如果 UPDATE 命令包含 RETURNING 子句,其结果将类似于一条 SELECT 语句的结果,该语句包含在 RETURNING 列表中定义的列和值,并针对该命令更新的行进行计算。

注记

当存在 FROM 子句时,实质上发生的事情是:目标表与 from_item 列表中提到的表进行连接,并且连接的每个输出行都代表对目标表的一次更新操作。使用 FROM 时,你应当确保连接对每个要修改的行至多产生一个输出行。换句话说,一个目标行不应与来自其他表的多个行相连接。如果发生了这种情况,那么只会用连接行中的一行来更新目标行,但无法轻易预测会使用哪一行。

由于这种不确定性,只在子查询中引用其他表会更安全,尽管这通常比使用连接更难阅读,也更慢。

对于分区表,更新一行可能导致它不再满足其所在分区的分区约束。在这种情况下,如果分区树中存在另一个分区且该行满足其分区约束,则该行会被移动到那个分区。如果不存在这样的分区,则会发生错误。在幕后,行的移动实际上是一次 DELETE 和 INSERT 操作。

对正在被移动的行并发执行 UPDATE 或 DELETE 有可能得到序列化失败错误。假设会话 1 正在对一个分区键执行 UPDATE,与此同时,该行对其可见的并发会话 2 正在对这一行执行 UPDATE 或 DELETE 操作。在这种情况下,会话 2 的 UPDATE 或 DELETE 会检测到行的移动并引发序列化失败错误(该错误总是以 SQLSTATE 代码 '40001' 返回)。如果发生这种情况,应用程序可能希望重试该事务。在表没有分区或没有行移动的通常情况下,会话 2 本会识别出刚更新的行,并对这个新行版本执行 UPDATE/DELETE。

注意,虽然行可以从本地分区移动到外部表分区(前提是外部数据包装器支持元组路由),但不能从外部表分区移动到另一个分区。

如果发现有一个外键直接引用了源分区的某个祖先,而该祖先与 UPDATE 查询中提到的祖先不是同一个,那么将行从一个分区移动到另一个分区的尝试将会失败。

示例

把 Drama 这个词改为 Dramatic,该词所在的 kind 列属于表 films:

UPDATE films SET kind = 'Dramatic' WHERE kind = 'Drama';

调整表 weather 中某一行的温度条目,并将降水量重置为其默认值:

UPDATE weather SET temp_lo = temp_lo+1, temp_hi = temp_lo+15, prcp = DEFAULT
  WHERE city = 'San Francisco' AND date = '2003-07-03';

执行相同的操作,并返回更新后的条目以及旧的降水量值:

UPDATE weather SET temp_lo = temp_lo+1, temp_hi = temp_lo+15, prcp = DEFAULT
  WHERE city = 'San Francisco' AND date = '2003-07-03'
  RETURNING temp_lo, temp_hi, prcp, old.prcp AS old_prcp;

使用另一种列列表语法执行同样的更新:

UPDATE weather SET (temp_lo, temp_hi, prcp) = (temp_lo+1, temp_lo+15, DEFAULT)
  WHERE city = 'San Francisco' AND date = '2003-07-03';

使用 FROM 子句语法,增加为 Acme Corporation 管理账户的销售人员的销售计数:

UPDATE employees SET sales_count = sales_count + 1 FROM accounts
  WHERE accounts.name = 'Acme Corporation'
  AND employees.id = accounts.sales_person;

使用 WHERE 子句中的子查询执行相同的操作:

UPDATE employees SET sales_count = sales_count + 1 WHERE id =
  (SELECT sales_person FROM accounts WHERE name = 'Acme Corporation');

更新 accounts 表中的联系人姓名,使之与当前分配的销售人员相匹配:

UPDATE accounts SET (contact_first_name, contact_last_name) =
    (SELECT first_name, last_name FROM employees
     WHERE employees.id = accounts.sales_person);

使用连接也可以得到类似的结果:

UPDATE accounts SET contact_first_name = first_name,
                    contact_last_name = last_name
  FROM employees WHERE employees.id = accounts.sales_person;

但是,如果 employees.id 不是唯一键,第二个查询可能给出意外的结果,而第一个查询在存在多个 id 匹配时保证会引发错误。此外,如果某个特定的 accounts.sales_person 条目没有匹配,第一个查询会将相应的姓名字段设置为 NULL,而第二个查询则根本不会更新那一行。

更新汇总表中的统计信息,使之与当前数据相匹配:

UPDATE summary s SET (sum_x, sum_y, avg_x, avg_y) =
    (SELECT sum(x), sum(y), avg(x), avg(y) FROM data d
     WHERE d.group_id = s.group_id);

尝试插入一个新的库存项及其库存数量。如果该项已经存在,则改为更新现有项的库存数量。为了在不使整个事务失败的情况下做到这一点,可以使用保存点:

BEGIN;
-- other operations
SAVEPOINT sp1;
INSERT INTO wines VALUES('Chateau Lafite 2003', '24');
-- Assume the above fails because of a unique key violation,
-- so now we issue these commands:
ROLLBACK TO sp1;
UPDATE wines SET stock = stock + 24 WHERE winename = 'Chateau Lafite 2003';
-- continue with other operations, and eventually
COMMIT;

更改 kind 列,它属于表 films 中游标 c_films 当前所定位的那一行:

UPDATE films SET kind = 'Dramatic' WHERE CURRENT OF c_films;

影响很多行的更新可能会对系统性能产生负面影响,例如表膨胀、副本延迟增加和锁竞争加剧。在这种情况下,以较小的批次执行该操作可能更有意义,并且可以在各批次之间对表执行 VACUUM 操作。虽然 LIMIT 子句并不适用于 UPDATE,但可以通过使用 Common Table Expression 和自连接来获得类似的效果。使用标准的 PostgreSQL 表访问方法时,基于系统列 ctid 的自连接是非常高效的:

WITH exceeded_max_retries AS (
  SELECT w.ctid FROM work_item AS w
    WHERE w.status = 'active' AND w.num_retries > 10
    ORDER BY w.retry_timestamp
    FOR UPDATE
    LIMIT 5000
)
UPDATE work_item SET status = 'failed'
  FROM exceeded_max_retries AS emr
  WHERE work_item.ctid = emr.ctid;

需要重复执行该命令,直到没有剩余的行需要更新为止。(这种对 ctid 的使用之所以安全,只是因为该查询被反复运行,从而避免了 ctid 发生变化的问题。)使用 ORDER BY 子句可以让该命令决定哪些行将被优先更新;如果其他更新操作使用相同的排序,它还可以防止死锁。如果担心锁竞争,可以将 SKIP LOCKED 加入 CTE,以防止多个命令更新同一行。但是,那样的话最后还需要一条 UPDATE,不带 SKIP LOCKED 或 LIMIT,以确保没有遗漏任何匹配的行。

兼容性

此命令符合 SQL 标准,但 FROM 和 RETURNING 子句是 PostgreSQL 的扩展,将 WITH 与 UPDATE 一起使用的能力也是如此。

一些其他数据库系统提供 FROM 选项,其中目标表应当再次列在 FROM 中。PostgreSQL 不是这样解释 FROM 的。移植使用此扩展的应用程序时要小心。

根据标准,带括号的目标列名子列表的源值可以是任何能产生正确列数的行值表达式。PostgreSQL 只允许源值是一个 row constructor 或一个子 SELECT。在行构造器的情形下,单个列的更新值可以指定为 DEFAULT,但在子 SELECT 内部则不行。