UPDATE — 更新表中的行
[ WITH [ RECURSIVE ]with_query[, ...] ] UPDATE [ ONLY ]table_name[ * ] [ FOR PORTION OFrange_column_namefor_portion_of_target] [ [ AS ]alias] SET {column_name= {expression| DEFAULT } | (column_name[, ...] ) = [ ROW ] ( {expression| DEFAULT } [, ...] ) | (column_name[, ...] ) = (sub-SELECT) } [, ...] [ FROMfrom_item[, ...] ] [ WHEREcondition| WHERE CURRENT OFcursor_name] [ RETURNING [ WITH ( { OLD | NEW } ASoutput_alias[, ...] ) ] { * |output_expression[ [ AS ]output_name] } [, ...] ] 其中for_portion_of_target为: { FROMstart_timeTOend_time| (portion) }
UPDATE会更改所有满足条件的行中指定列 的值。只需在SET子句中提及需要修改的列; 未被显式修改的列会保留其原有值。
有两种方法可以利用数据库中其他表所包含的信息来修改一个表:使用子选择, 或者在FROM子句中指定附加表。哪种技术更合适取决 于具体情况。
可选的RETURNING子句使UPDATE 基于每个实际更新的行计算并返回一个或多个值。可以计算任何使用该表列和/或 FROM中提到的其他表列的表达式。默认使用该 表列的新值(更新后值),但也可以请求旧值(更新前值)。 RETURNING列表的语法与SELECT 的输出列表相同。
如果使用FOR PORTION OF子句,更新只会影响与给定区间重叠的行。 而且,如果某行的应用时间超出FOR PORTION OF边界,那么更新只会 改变这些边界内的应用时间。实际上,只有由FOR PORTION OF所针对的 历史会被更新,边界之外的时间点不会受到影响。此外,在一行被更新后,范围或多范围会先被 缩短,使其应用时间不再超出被针对的FOR PORTION OF边界。随后, 可能会插入新的时态残留: 这些行的范围或多范围会接收位于被针对的FROM/TO 边界之外的剩余应用时间,而其他列保留原始值。对于范围列,可能插入零到两条记录,具体取决于原始 应用时间是完全被更新、在更改前后各有延伸,还是两者兼有。多范围永远不需要两条时态残留,因为 一个值就总能容纳剩余的应用时间。
必须拥有该表上的UPDATE权限,或者至少拥有 待更新列的UPDATE权限。对于其值会在 expressions或者 condition中读取的任何列, 还必须拥有SELECT权限。 当使用FOR PORTION OF时,二次插入不需要该表上的 INSERT权限。(这是因为概念上并没有新增信息;插入的行只是在保留 未被针对的时间段上的现有数据。)
with_query #WITH子句允许你指定一个或多个子查询,这些子查 询可在UPDATE查询中按名称引用。详见 Section 7.8和SELECT。
table_name #要更新的表名(可以是模式限定的)。如果在表名前指定了 ONLY,只会更新所提及表中的匹配行。如果未指定 ONLY,还会更新继承自该表的任何表中的匹配行。 可选地,可以在表名后指定*,以显式指示包含后代 表。
alias #目标表的替代名称。提供别名时,它会完全隐藏该表的实际名称。 例如,给定UPDATE foo AS f, UPDATE语句的其余部分必须将该表称为 f,而不是foo。
range_column_name #执行时间区间更新时使用的范围或多范围列。
for_portion_of_target #要更新的区间。如果针对的是范围列,则可以采用 FROM start_time TO end_time 形式。否则,它必须采用(portion) 形式,其中portion是一个表达式,其值的类型与 range_column_name相同。
start_time #时间区间更新中要更改的最早时间(含)。这必须是与 range_column_name所对应范围的基础类型匹配的值。 此处的空值表示更新的起点无边界(与范围类型一样)。
end_time #时间区间更新中要更改的最晚时间(不含)。这必须是与 range_column_name所对应范围的基础类型匹配的值。 此处的空值表示更新的终点无边界(与范围类型一样)。
column_name #由table_name命名 的表中的列名。如有需要,列名可以使用子字段名或数组下标 进行限定。指定目标列时不要包含表名 — 例如, UPDATE table_name SET table_name.col = 1是无 效的。
expression #要赋给该列的表达式。该表达式可以使用该表中这一列和其他列的 旧值。
DEFAULT #把该列设置为其默认值(如果没有为它指定特定的默认表达式,则该 值为 NULL)。标识列将被设置为由关联序列生成的新值。对于生成 列,允许指定这一项,但这仅仅指定了根据其生成表达式计算该列的 常规行为。
sub-SELECT #一个SELECT子查询,其输出列数必须与它前面圆括号中 列列表的列数相同。执行时,该子查询返回的行数不得超过一行。如 果返回一行,则其列值会赋给目标列;如果不返回任何行,则把 NULL 值赋给目标列。该子查询可以引用正在更新的表的当前行的旧值。
from_item #一个表表达式,允许其他表的列出现在WHERE条件 和更新表达式中。这里使用的语法与SELECT语句 的FROM子句相 同;例如,可以为表名指定别名。除非你打算进行自连接(这种情况 下它必须在from_item中带有别名出现), 否则不要把目标表重复写成from_item。
condition #一个返回boolean类型值的表达式。只有使这个表达式返 回true的行才会被更新。
cursor_name #要在WHERE CURRENT OF条件中使用的游标名。 要被更新的行是最近一次从该游标中取出的那一行。该游标必须是针 对UPDATE目标表的非分组查询。注意, WHERE CURRENT OF不能与布尔条件同时指定。有 关将游标用于WHERE CURRENT OF的更多信息,请见 DECLARE。
output_alias #在RETURNING列表中为OLD或 NEW行指定的可选替代名称。
默认情况下,可通过写 OLD. 或column_nameOLD.*返回目标表旧值,通过写 NEW. 或column_nameNEW.*返回新值。提供别名后,这些名称会被隐藏, 必须使用别名来引用旧行或新行。例如 RETURNING WITH (OLD AS o, NEW AS n) o.*, n.*。
output_expression #在每一行被更新后,由UPDATE命令计算并返回的 表达式。该表达式可以使用由table_name 命名的表或FROM中列出的表中的任何列名。写成 *可返回所有列。
列名或*可以用OLD或 NEW来限定,也可以用它们对应的 output_alias来限定, 以返回旧值或新值。未限定的列名或*,以及使用 目标表名或别名限定的列名或*,都会返回新值。
output_name #用于返回列的名称。
在成功完成时,一个UPDATE命令会返回以下形式 的命令标签:
UPDATE count
count是被更新的行数, 包括值没有改变的匹配行。注意,当更新被BEFORE UPDATE 触发器抑制时,这个数量可能小于匹配 condition的行数。如果 count为 0,则该查询没有更 新任何行(这不被视为错误)。如果使用了 FOR PORTION OF,则 count不包括已插入的 时态残留。
如果UPDATE命令包含RETURNING 子句,其结果将类似于一个SELECT语句,其中包含 RETURNING列表中定义的列和值,并在该命令更新的 行上进行计算。如果使用了FOR PORTION OF, RETURNING子句会为每个更新后的行给出一个结果,但 不包括已插入的时态残留。 应用时间列的值与更新后行的新值相匹配。
当存在FROM子句时,本质上会将目标表与 from_item列表中提到的表连接起来,而连接 的每一条输出行都代表对目标表的一次更新操作。使用 FROM时,应确保对每一个要修改的目标行,连接至多 生成一条输出行。换言之,一条目标行不应与其他表中的多于一行成功 连接。如果发生这种情况,则只会使用其中某一条连接行来更新目标行, 但具体使用哪一条并不容易预测。
因为存在这种不确定性,所以仅在子选择中引用其他表会更安全,尽管 这种写法通常比使用连接更难阅读、也更慢。
对于分区表,更新一行可能会导致它不再满足其所在分区的分区约束。 在这种情况下,如果在分区树中存在另一个满足该行分区约束的分区, 则该行会被移动到那个分区。如果不存在这样的分区,则会报错。在幕 后,行移动实际上是一次DELETE和一次 INSERT操作。
在被移动的行上并发执行UPDATE或 DELETE时,有可能收到序列化失败错误。假设会话 1 正在更新某个分区键,与此同时,一个对该行可见的并发会话 2 对该行 执行UPDATE或DELETE操作。在 这种情况下,会话 2 的UPDATE或 DELETE将检测到行移动,并引发序列化失败错误 (其 SQLSTATE 代码始终为'40001')。如果发生 这种情况,应用程序可能需要重试事务。在表未分区或没有发生行移动 的通常情况下,会话 2 会识别出新更新的那一行,并在这个新行版本上执行 UPDATE/DELETE。
请注意,虽然行可以从本地分区移动到外部表分区(前提是外部数据包装 器支持元组路由),但不能从外部表分区移动到另一个分区。
如果发现某个外键直接引用了源分区的某个祖先,并且这个祖先与 UPDATE查询中提到的祖先不是同一个,那么尝试把 一行从一个分区移动到另一个分区将会失败。
当使用FOR PORTION OF时,这可能会导致没有 INSERT权限的用户触发INSERT触发器。 使用SECURITY DEFINER触发器函数时应考虑这一点。
把表films的kind 列中的单词Drama改为Dramatic:
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;
-- 其他操作
SAVEPOINT sp1;
INSERT INTO wines VALUES('Chateau Lafite 2003', '24');
-- 假设上面的语句因唯一键冲突而失败,
-- 那么现在发出这些命令:
ROLLBACK TO sp1;
UPDATE wines SET stock = stock + 24 WHERE winename = 'Chateau Lafite 2003';
-- 继续执行其他操作,最后
COMMIT;
更改表films中游标c_films 当前定位那一行的kind列:
UPDATE films SET kind = 'Dramatic' WHERE CURRENT OF c_films;
一个时间区间更新的示例:
UPDATE products FOR PORTION OF valid_at FROM '2023-09-01' TO '2025-03-01' SET price = 12.00 WHERE product_no = 5;
影响很多行的更新可能对系统性能产生负面影响,例如表膨胀、复制延 迟增加以及锁争用加剧。在这类场景中,将操作分成较小批次来执行可 能更有意义,并且可以在各批次之间对该表执行VACUUM。 虽然UPDATE没有LIMIT子句, 但可以通过公共表表达式和自连 接获得类似效果。对于标准的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子句可以让命令优先更新哪些行;如果 其他更新操作也使用相同的顺序,它还可以防止死锁。如果担心锁争 用,可以在CTE中加入SKIP LOCKED, 以防多个命令更新同一行。不过,这样仍需要在最后执行一次不带 SKIP LOCKED或LIMIT的 UPDATE,以确保没有遗漏任何匹配行。
这个命令符合SQL标准,不过 FROM和RETURNING子句是 PostgreSQL扩展,把WITH 与UPDATE一起使用的能力也是扩展。
某些其他数据库系统提供一种FROM选项,要求在 FROM中再次列出目标表。PostgreSQL 并不是这样解释FROM的。在移植使用这种扩展的应 用时要小心。
根据标准,目标列名的一个圆括号子列表的源值可以是任何能够产生正 确列数的行值表达式。PostgreSQL只允许该 源值是一个行构造器 或子SELECT。在行构造器的情况下,单个列的更新值 可以指定为DEFAULT,但在子SELECT 中则不能这样做。