SQL COMMAND · 查询与数据操作
MERGE
有条件地插入、更新或删除表中的行
MERGE查询与数据操作引入 15现存至 20 devel2 次语法变更
- 动词
MERGE- 对象
—- 分组
- 查询与数据操作
- 引入版本
- 15
- 状态
- 现存
- 语法变更次数
- 2
- 手册小节数
- 6
版本轨迹
相对 PostgreSQL 17:语法概要新增 1 行,移除 0 行。
语法铁道图 PostgreSQL 18
沿轨道从左向右阅读,分岔表示选择,绕行表示可选,回环表示重复。方框为参数,点击带下划线的参数可展开子规则。
data_source
when_clause
merge_insert
merge_update
merge_delete
锁模式 PostgreSQL 18
具体锁模式取决于操作变体、目标对象和执行阶段;同一命令可同时取得表锁与行锁。查看完整冲突矩阵
ROW SHARE· 表级锁MERGE(外键检查):外键检查的内部 SELECT … FOR KEY SHARE 在被引用表上取得 ROW SHARE;写入目标表上的模式仍是 ROW EXCLUSIVE。ROW EXCLUSIVE· 表级锁MERGE:目标表取得此模式;每个匹配分支是否加行锁以及锁强度由实际 INSERT / UPDATE / DELETE 动作决定。MERGE … WHEN MATCHED THEN UPDATE(非键):这个写入变体在目标表上取得 ROW EXCLUSIVE;对已有行的锁另按实际动作取得。MERGE … WHEN MATCHED THEN DELETE / UPDATE(键):这个写入变体在目标表上取得 ROW EXCLUSIVE;对已有行的锁另按实际动作取得。MERGE(外键检查):原始写入命令在写入目标表上取得 ROW EXCLUSIVE;外键检查的 ROW SHARE 与 FOR KEY SHARE 则作用于被引用的表和行,不能混为同一对象。FOR KEY SHARE· 行级锁MERGE(外键检查):实际检查非空外键引用时,在被引用表的匹配行上取得 FOR KEY SHARE;可能在语句内或延迟约束检查时执行。级联动作还会取得相应 UPDATE / DELETE 的锁。FOR NO KEY UPDATE· 行级锁MERGE … WHEN MATCHED THEN UPDATE(非键):实际执行 UPDATE 分支且不需 FOR UPDATE 时;不能给所有 MERGE 分支统一套用一种行锁。FOR UPDATE· 行级锁MERGE … WHEN MATCHED THEN DELETE / UPDATE(键):实际删除或更新键列的匹配行;纯插入分支不适用。
语法概要
相对 PostgreSQL 17 新增 1 行,移除 0 行;新增行以绿色标记。
[ WITHwith_query[, ...] ] MERGE INTO [ ONLY ]target_table_name[ * ] [ [ AS ]target_alias] USINGdata_sourceONjoin_conditionwhen_clause[...] [ RETURNING [ WITH ( { OLD | NEW } ASoutput_alias[, ...] ) ] { * |output_expression[ [ AS ]output_name] } [, ...] ] 其中data_source为: { [ ONLY ]source_table_name[ * ] | (source_query) } [ [ AS ]source_alias] 而when_clause为: { WHEN MATCHED [ ANDcondition] THEN {merge_update|merge_delete| DO NOTHING } | WHEN NOT MATCHED BY SOURCE [ ANDcondition] THEN {merge_update|merge_delete| DO NOTHING } | WHEN NOT MATCHED [ BY TARGET ] [ ANDcondition] THEN {merge_insert| DO NOTHING } } 而merge_insert为: INSERT [(column_name[, ...] )] [ OVERRIDING { SYSTEM | USER } VALUE ] { VALUES ( {expression| DEFAULT } [, ...] ) | DEFAULT VALUES } 而merge_update为: UPDATE SET {column_name= {expression| DEFAULT } | (column_name[, ...] ) = [ ROW ] ( {expression| DEFAULT } [, ...] ) | (column_name[, ...] ) = (sub-SELECT) } [, ...] 而merge_delete为: DELETE
描述
MERGE使用data_source 对由target_table_name标识的目标表中的行执行修改动作。MERGE提供了一条单独的SQL语句,可按条件对行执行INSERT、UPDATE或DELETE;否则,这项工作需要多条过程语言语句。
首先,MERGE命令会将data_source 与目标表执行连接,生成零个或多个候选变更行。对于每个候选变更行,系统只会确定一次其状态是MATCHED、NOT MATCHED BY SOURCE还是 NOT MATCHED [BY TARGET],然后按指定顺序对 WHEN子句求值。对于每个候选变更行,只执行第一个求值为真的子句。任何候选变更行都不会执行多个 WHEN子句。
MERGE中的各个动作,与常规的 UPDATE、INSERT或 DELETE同名命令具有相同效果。不过这些动作的语法有所不同,尤其是没有WHERE子句,也不指定表名。所有动作都针对目标表,不过也可以借助触发器修改其他表。
当指定DO NOTHING时,源行会被跳过。由于动作会按其指定顺序求值,DO NOTHING有助于在进行更细粒度处理之前跳过不需要关注的源行。
可选的RETURNING子句会让MERGE 针对每个被插入、更新或删除的行计算并返回值。可以计算任何使用源表或目标表列的表达式,也可以调用merge_action()函数。默认情况下,执行INSERT或UPDATE动作时,使用目标表各列的新值;执行DELETE动作时,使用目标表各列的旧值,但也可以显式请求旧值和新值。RETURNING列表的语法与 SELECT的输出列表完全相同。
没有单独的MERGE权限。如果指定更新动作,则必须对目标表中SET子句所引用的列具有UPDATE权限。如果指定插入动作,则必须对目标表具有INSERT权限。如果指定删除动作,则必须对目标表具有DELETE权限。如果指定DO NOTHING动作,则必须对目标表中至少一列具有SELECT权限。对于data_source以及目标表中在任何 condition(包括join_condition)或 expression中引用的列,你还必须具有SELECT权限。权限只会在语句开始时测试一次,而且无论特定的WHEN子句是否执行,都会进行检查。
如果目标表是物化视图、外部表,或者其上定义了任何规则,则不支持MERGE。
参数
with_queryWITH子句允许你指定一个或多个子查询,以便在MERGE查询中按名称引用它们。有关细节请参见第 7.8 节和SELECT。请注意,MERGE不支持WITH RECURSIVE。target_table_name要合并到其中的目标表或视图名称(可选模式限定)。如果在表名前指定
ONLY,则匹配行只会在该命名表中被更新或删除。如果未指定ONLY,则匹配行还会在继承自该命名表的任何表中被更新或删除。也可以在表名后指定*,以显式表明包含后代表。ONLY关键字和*选项不会影响插入动作,因为插入动作始终只会插入到该命名表中。如果
target_table_name是一个视图,它必须要么是没有INSTEAD OF触发器的自动可更新视图,要么为WHEN子句中指定的每一种动作类型(INSERT、UPDATE和DELETE)都定义了INSTEAD OF触发器。不支持带有规则的视图。target_alias目标表的替代名称。提供别名后,它会完全隐藏表的实际名称。例如,给定
MERGE INTO foo AS f,则MERGE语句的其余部分必须将该表称为f,而不是foo。source_table_name源表、视图或过渡表的名称(可选模式限定)。如果在表名前指定
ONLY,则只包含来自该命名表的匹配行。如果未指定ONLY,则还会包含来自继承自该命名表的任何表的匹配行。也可以在表名后指定*,以显式表明包含后代表。source_query一个查询(
SELECT语句或VALUES语句),用于提供要合并到目标表中的行。其语法说明请参见SELECT语句或VALUES语句。source_alias数据源的替代名称。提供别名后,它会完全隐藏表的实际名称,或者隐藏此处实际使用的是一个查询这一事实。
join_conditionjoin_condition是一个返回boolean类型值的表达式(类似于WHERE子句),用于指定data_source中的哪些行与目标表中的行匹配。警告
只有那些用于尝试将
data_source行与目标表行匹配的目标表列,才应出现在join_condition中。只引用目标表列的join_condition子表达式可能影响所采取的动作,而且常常会令人意外。如果同时指定了
WHEN NOT MATCHED BY SOURCE和WHEN NOT MATCHED [BY TARGET]子句,MERGE命令将对data_source与目标表执行FULL连接。要做到这一点,至少有一个join_condition子表达式必须使用可支持哈希连接的操作符,或者所有子表达式都必须使用可支持归并连接的操作符。when_clause至少需要一个
WHEN子句。WHEN子句可以指定WHEN MATCHED、WHEN NOT MATCHED BY SOURCE或WHEN NOT MATCHED [BY TARGET]。请注意,SQL标准只定义了WHEN MATCHED和WHEN NOT MATCHED(后者定义为没有匹配的目标行)。WHEN NOT MATCHED BY SOURCE是对SQL标准的扩展,而给WHEN NOT MATCHED追加BY TARGET选项也是扩展,用以使其含义更加明确。如果
WHEN子句指定了WHEN MATCHED,并且候选变更行表示data_source中的一行与目标表中的一行相匹配,那么如果condition未指定,或其求值结果为true,就会执行该WHEN子句。如果
WHEN子句指定了WHEN NOT MATCHED BY SOURCE,并且候选变更行表示目标表中的某一行与data_source中的任何行都不匹配,那么如果condition未指定,或其求值结果为true,就会执行该WHEN子句。如果
WHEN子句指定了WHEN NOT MATCHED [BY TARGET],并且候选变更行表示data_source中的某一行与目标表中的任何行都不匹配,那么如果condition未指定,或其求值结果为true,就会执行该WHEN子句。condition一个返回
boolean类型值的表达式。如果某个WHEN子句中的这个表达式返回true,则会对该行执行该子句对应的动作。WHEN MATCHED子句上的条件可以引用源关系和目标关系中的列。WHEN NOT MATCHED BY SOURCE子句上的条件只能引用目标关系中的列,因为按定义不存在匹配的源行。WHEN NOT MATCHED [BY TARGET]子句上的条件只能引用源关系中的列,因为按定义不存在匹配的目标行。只能访问目标表的系统属性。merge_insert用于向目标表插入一行的
INSERT动作规范。目标列名可以按任意顺序列出。如果根本没有给出列名列表,则默认为按声明顺序使用该表的所有列。任何未出现在显式或隐式列列表中的列都会填入默认值;如果没有声明默认值,则填入空值。
如果目标表是分区表,则每一行都会被路由到适当的分区并插入其中。如果目标表本身是一个分区,而任何输入行违反了分区约束,就会报错。
列名不得指定多次。
INSERT动作不能包含子查询。只能指定一个
VALUES子句。VALUES子句只能引用源关系中的列,因为按定义不存在匹配的目标行。merge_update用于更新目标表当前行的
UPDATE动作规范。列名不得指定多次。既不允许表名,也不允许
WHERE子句。merge_delete指定一个
DELETE动作,用于删除目标表的当前行。不要像通常编写DELETE命令那样包含表名或其他子句。column_name目标表中某一列的名称。如有需要,列名可以使用子字段名或数组下标进行限定。(只向复合列的部分字段插入值会使其余字段为
NULL。)在指定目标列时不要包含表名。OVERRIDING SYSTEM VALUE如果没有这个子句,那么为定义为
GENERATED ALWAYS的标识列指定显式值(除DEFAULT外)会报错。这个子句会覆盖该限制。OVERRIDING USER VALUE如果指定了此子句,则会忽略为定义为
GENERATED BY DEFAULT的标识列提供的任何值,并应用默认的序列生成值。DEFAULT VALUES所有列都会填入各自的默认值。(这种形式不允许出现
OVERRIDING子句。)expression赋给该列的表达式。如果用于
WHEN MATCHED子句,该表达式可以使用目标表中原始行的值以及data_source行的值。如果用于WHEN NOT MATCHED BY SOURCE子句,该表达式只能使用目标表中原始行的值。如果用于WHEN NOT MATCHED [BY TARGET]子句,该表达式只能使用data_source行的值。DEFAULT将该列设为其默认值(如果没有为其指定特定的默认表达式,则为
NULL)。sub-SELECT一个
SELECT子查询,其输出列数必须与它前面的括号列列表中列出的数量相同。执行时,该子查询最多只能产生一行。如果产生一行,其列值会赋给目标列;如果不产生任何行,则向目标列赋NULL值。如果用于WHEN MATCHED子句,子查询可以引用目标表中原始行的值,以及data_source行的值。如果用于WHEN NOT MATCHED BY SOURCE子句,子查询只能引用目标表中原始行的值。output_alias在
RETURNING列表中为OLD或NEW行指定的可选替代名称。默认情况下,可通过
OLD.或column_nameOLD.*返回目标表旧值,通过NEW.或column_nameNEW.*返回新值。提供别名后,这些名称会被隐藏,必须使用别名来引用旧行或新行。例如RETURNING WITH (OLD AS o, NEW AS n) o.*, n.*。output_expression由
MERGE命令在每一行发生变化后(无论是插入、更新还是删除)计算并返回的表达式。该表达式可以使用源表或目标表的任意列,也可以使用merge_action()函数返回关于已执行动作的附加信息。写成
*将返回源表的全部列,然后返回目标表的全部列。这往往会导致大量重复,因为源表和目标表通常有很多相同的列。可以通过使用源表或目标表的名称或别名限定*来避免这一点。列名或
*也可以用OLD或NEW,或者用OLD或NEW对应的output_alias来限定,以返回目标表中的旧值或新值。来自目标表的未限定列名,或者使用目标表名或别名限定的列名或*,会在INSERT和UPDATE动作中返回新值,在DELETE动作中返回旧值。output_name用于返回列的名称。
输出
成功完成时,MERGE命令返回如下形式的命令标签:
MERGE total_count
total_count是发生变化的总行数(无论是插入、更新还是删除)。如果total_count为 0,则没有任何行以任何方式发生变化。
如果MERGE命令包含RETURNING子句,则其结果将类似于一条SELECT语句,包含RETURNING列表中定义的列和值,这些值是针对该命令插入、更新或删除的行计算得到的。
注解
在执行MERGE期间会发生以下步骤:
对所有已指定的动作执行任何
BEFORE STATEMENT触发器,无论其WHEN子句是否匹配。执行从源到目标表的连接。生成的查询会按常规方式优化,并产生一组候选变更行。对于每个候选变更行,
判定该行是
MATCHED、NOT MATCHED BY SOURCE还是NOT MATCHED [BY TARGET]。按指定顺序测试每个
WHEN条件,直到有一个返回true。当某个条件返回
true时,执行以下动作:执行为该动作事件类型触发的任何
BEFORE ROW触发器。执行指定的动作,并对目标表应用任何检查约束。
执行为该动作事件类型触发的任何
AFTER ROW触发器。
如果目标关系是一个视图,并且为该动作的事件类型定义了
INSTEAD OF ROW触发器,则改由这些触发器执行该动作。
对已指定的动作执行任何
AFTER STATEMENT触发器,无论这些动作是否实际发生。这类似于一条未修改任何行的UPDATE语句的行为。
总之,只要我们指定了某种动作,对应事件类型(例如INSERT)的语句级触发器就会被触发。相反,行级触发器只会针对实际被执行的特定事件类型触发。因此,一条MERGE命令可能同时触发 UPDATE和INSERT的语句级触发器,即使实际上只触发了UPDATE的行级触发器。
你应确保该连接对每个目标行最多只生成一个候选变更行。换句话说,一个目标行不应连接到多于一个数据源行。如果发生这种情况,则只会使用其中一个候选变更行来修改目标行;随后再次尝试修改该行会导致错误。如果行级触发器对目标表进行了修改,而随后这些已被修改的行又被 MERGE再次修改,也可能发生这种情况。如果重复的动作是INSERT,则会导致唯一性违例错误;而重复的UPDATE或DELETE则会导致基数违规;后者是SQL标准要求的行为。这不同于PostgreSQL历史上在 UPDATE和DELETE语句中处理连接的行为:对同一行的第二次及后续修改尝试会被直接忽略。
如果某个WHEN子句省略了AND子句,它就会成为该类型(MATCHED、NOT MATCHED BY SOURCE或 NOT MATCHED [BY TARGET])最后一个可达的子句。如果之后又指定了同一类型的WHEN子句,则可证明它不可达,并会引发错误。如果这三类中都没有指定最后一个可达子句,则候选变更行可能不会执行任何动作。
默认情况下,从数据源生成行的顺序是不确定的。如果需要,可以使用source_query指定一致的排序,这可能有助于避免并发事务之间的死锁。
当MERGE与其他修改目标表的命令并发运行时,适用通常的事务隔离规则;请参见第 13.2 节,了解各个隔离级别下的行为说明。你也可以考虑使用INSERT ... ON CONFLICT作为替代语句,它提供了在发生并发INSERT时执行UPDATE的能力。这两种语句类型之间存在各种差异和限制,因此不能互换。
示例
根据新的recent_transactions对 customer_account执行维护。
MERGE INTO customer_account ca USING recent_transactions t ON t.customer_id = ca.customer_id WHEN MATCHED THEN UPDATE SET balance = balance + transaction_value WHEN NOT MATCHED THEN INSERT (customer_id, balance) VALUES (t.customer_id, t.transaction_value);
尝试插入一个新的库存项及其库存数量。如果该项已存在,则改为更新现有项的库存数量。不允许库存为零的条目。返回所有已发生更改的详细信息。
MERGE INTO wines w USING wine_stock_changes s ON s.winename = w.winename WHEN NOT MATCHED AND s.stock_delta > 0 THEN INSERT VALUES(s.winename, s.stock_delta) WHEN MATCHED AND w.stock + s.stock_delta > 0 THEN UPDATE SET stock = w.stock + s.stock_delta WHEN MATCHED THEN DELETE RETURNING merge_action(), w.winename, old.stock AS old_stock, new.stock AS new_stock;
例如,wine_stock_changes表可能是一个刚装载到数据库中的临时表。
根据替换后的酒单更新wines:为任何新库存插入行,更新已修改的库存条目,并删除新列表中不存在的任何葡萄酒。
MERGE INTO wines w USING new_wine_list s ON s.winename = w.winename WHEN NOT MATCHED BY TARGET THEN INSERT VALUES(s.winename, s.stock) WHEN MATCHED AND w.stock != s.stock THEN UPDATE SET stock = s.stock WHEN NOT MATCHED BY SOURCE THEN DELETE;
兼容性
这个命令符合SQL标准。
WITH子句、WHEN NOT MATCHED上的 BY SOURCE和BY TARGET限定词、DO NOTHING动作以及RETURNING子句,都是对SQL标准的扩展。
语法演化
相邻大版本之间的差异,新的在前。版本号链接到对应快照。
PostgreSQL 19← 18正文更新
正文更新PostgreSQL 18← 17语法变化
+ [ RETURNING [ WITH ( { OLD | NEW } AS output_alias [, ...] ) ]正文更新PostgreSQL 17← 16语法变化
+ when_clause [...]+ [ RETURNING { * | output_expression [ [ AS ] output_name ] } [, ...] ]+ WHEN NOT MATCHED BY SOURCE [ AND condition ] THEN { merge_update | merge_delete | DO NOTHING } |+ WHEN NOT MATCHED [ BY TARGET ] [ AND condition ] THEN { merge_insert | DO NOTHING } }正文更新PostgreSQL 15← 14新增此命令
同组命令
| 命令 | 动词 | 对象 | 版本变动 | 最近变更 |
|---|---|---|---|---|
COPY | COPY | — | 196 次 | |
| 在文件和表之间复制数据 | 现存 | |||
DELETE | DELETE | — | 182 次 | |
| 删除表中的行 | 现存 | |||
EXPLAIN | EXPLAIN | — | 195 次 | |
| 显示一个语句的执行计划 | 现存 | |||
INSERT | INSERT | — | 193 次 | |
| 在表中插入新行 | 现存 | |||
LOCK | LOCK | — | — | |
| 锁定表 | 现存 | |||
MERGE | MERGE | — | 182 次 | |
| 有条件地插入、更新或删除表中的行 | 现存 | |||
SELECT | SELECT | — | 176 次 | |
| 从表或视图中检索行 | 现存 | |||
UPDATE | UPDATE | — | 182 次 | |
| 更新表中的行 | 现存 | |||
VALUES | VALUES | — | — | |
| 计算一组行 | 现存 | |||
SELECT INTO | SELECT | INTO | 192 次 | |
| 根据查询结果定义一个新表 | 现存 | |||