2024年9月26日:PostgreSQL 17 发布!
支持的版本:当前 (17) / 16 / 15
开发版本:devel

MERGE

MERGE — 有条件地插入、更新或删除表的行

概要

[ WITH with_query [, ...] ]
MERGE INTO [ ONLY ] target_table_name [ * ] [ [ AS ] target_alias ]
USING data_source ON join_condition
when_clause [...]
[ RETURNING { * | output_expression [ [ AS ] output_name ] } [, ...] ]

where data_source is:

{ [ ONLY ] source_table_name [ * ] | ( source_query ) } [ [ AS ] source_alias ]

and when_clause is:

{ WHEN MATCHED [ AND condition ] THEN { merge_update | merge_delete | DO NOTHING } |
  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 } }

and merge_insert is:

INSERT [( column_name [, ...] )]
[ OVERRIDING { SYSTEM | USER } VALUE ]
{ VALUES ( { expression | DEFAULT } [, ...] ) | DEFAULT VALUES }

and merge_update is:

UPDATE SET { column_name = { expression | DEFAULT } |
             ( column_name [, ...] ) = [ ROW ] ( { expression | DEFAULT } [, ...] ) |
             ( column_name [, ...] ) = ( sub-SELECT )
           } [, ...]

and merge_delete is:

DELETE

描述

MERGE 执行修改目标表中行(标识为target_table_name)的操作,使用data_sourceMERGE 提供了一个SQL语句,可以有条件地INSERTUPDATEDELETE 行,否则此任务需要多个过程语言语句。

首先,MERGE 命令从data_source 到目标表执行连接,生成零个或多个候选更改行。对于每个候选更改行,仅设置一次MATCHEDNOT MATCHED BY SOURCENOT MATCHED [BY TARGET] 的状态,之后按指定的顺序评估WHEN 子句。对于每个候选更改行,第一个评估为真的子句将被执行。对于任何候选更改行,最多执行一个WHEN 子句。

MERGE 操作与相同名称的常规UPDATEINSERTDELETE 命令具有相同的效果。这些命令的语法不同,特别之处在于没有WHERE 子句,也没有指定表名。所有操作都引用目标表,尽管可以使用触发器对其他表进行修改。

当指定DO NOTHING 时,源行将被跳过。由于操作按其指定的顺序进行评估,因此DO NOTHING 可以方便地在更细粒度的处理之前跳过不感兴趣的源行。

可选的RETURNING 子句导致MERGE 基于插入、更新或删除的每一行计算和返回值。可以使用源表或目标表的列,或merge_action() 函数的任何表达式进行计算。当执行INSERTUPDATE 操作时,将使用目标表列的新值。当执行DELETE 时,将使用目标表列的旧值。RETURNING 列表的语法与SELECT 的输出列表的语法相同。

没有单独的MERGE 权限。如果您指定更新操作,则必须对目标表中SET 子句中引用的列具有UPDATE 权限。如果您指定插入操作,则必须对目标表具有INSERT 权限。如果您指定删除操作,则必须对目标表具有DELETE 权限。如果您指定DO NOTHING 操作,则必须对目标表至少一列具有SELECT 权限。您还需要对data_source 和目标表中任何condition(包括join_condition)或expression 中引用的任何列具有SELECT 权限。权限在语句开始时测试一次,并检查是否执行特定的WHEN 子句。

如果目标表是物化视图、外部表,或者在其上定义了任何规则,则不支持MERGE

参数

with_query

使用WITH 子句,您可以指定一个或多个子查询,这些子查询可以在MERGE 查询中按名称引用。有关详细信息,请参见第 7.8 节SELECT。请注意,MERGE 不支持WITH RECURSIVE

target_table_name

要合并到的目标表或视图的名称(可选地带模式限定)。如果在表名前指定了ONLY,则仅在指定的表中更新或删除匹配的行。如果未指定ONLY,则还将在从指定表继承的任何表中更新或删除匹配的行。可选地,可以在表名后指定* 以明确指示包含后代表。ONLY 关键字和* 选项不影响插入操作,插入操作始终仅插入到指定的表中。

如果target_table_name 是一个视图,则它必须是自动可更新的,并且没有INSTEAD OF 触发器,或者它必须对WHEN 子句中指定的每种类型的操作(INSERTUPDATEDELETE)具有INSTEAD OF 触发器。不支持具有规则的视图。

target_alias

目标表的替代名称。当提供别名时,它会完全隐藏表的实际名称。例如,给定MERGE INTO foo AS fMERGE 语句的其余部分必须将此表称为f 而不是foo

source_table_name

源表、视图或转换表的名称(可选地带模式限定)。如果在表名前指定了ONLY,则仅从指定的表中包含匹配的行。如果未指定ONLY,则还将从从指定表继承的任何表中包含匹配的行。可选地,可以在表名后指定* 以明确指示包含后代表。

source_query

一个查询(SELECT 语句或VALUES 语句),用于提供要合并到目标表中的行。有关语法的说明,请参阅SELECT 语句或VALUES 语句。

source_alias

数据源的替代名称。当提供别名时,它会完全隐藏表的实际名称或已发出查询的事实。

join_condition

join_condition 是一个表达式,其结果为boolean 类型的值(类似于WHERE 子句),用于指定data_source 中哪些行与目标表中的行匹配。

警告

只有试图匹配data_source 行的目标表中的列才能出现在join_condition 中。join_condition 仅引用目标表列的子表达式会影响采取的操作,通常会以令人惊讶的方式影响。

如果同时指定了WHEN NOT MATCHED BY SOURCEWHEN NOT MATCHED [BY TARGET] 子句,则MERGE 命令将在data_source 和目标表之间执行FULL 连接。要使此操作正常工作,至少一个join_condition 子表达式必须使用可以支持哈希连接的操作符,或者所有子表达式都必须使用可以支持合并连接的操作符。

when_clause

至少需要一个WHEN 子句。

WHEN 子句可以指定WHEN MATCHEDWHEN NOT MATCHED BY SOURCEWHEN NOT MATCHED [BY TARGET]。请注意,SQL标准仅定义了WHEN MATCHEDWHEN 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

插入操作的规范,该操作将一行插入到目标表中。目标列名可以按任何顺序列出。如果根本没有给出列名列表,则默认为所有表的列,并按其声明的顺序排列。

显式或隐式列列表中不存在的每个列都将填充默认值,即其声明的默认值或(如果不存在)null。

如果目标表是分区表,则每行都会路由到相应的分区并插入其中。如果目标表是分区,则如果任何输入行违反分区约束,则会发生错误。

列名不能重复指定。 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_expression

在更改(插入、更新或删除)每行后,由 MERGE 命令计算并返回的表达式。该表达式可以使用源表或目标表的任何列,或者使用 merge_action() 函数返回有关执行的操作的其他信息。

写入 * 将返回源表中的所有列,然后是目标表中的所有列。通常,这会导致大量重复,因为源表和目标表通常具有许多相同的列。可以通过使用源表或目标表的名称或别名限定 * 来避免这种情况。

output_name

要用于返回列的名称。

输出

成功完成后,MERGE 命令将返回以下形式的命令标签

MERGE total_count

total_count 是更改的行总数(无论插入、更新还是删除)。如果 total_count 为 0,则没有任何行被更改。

如果 MERGE 命令包含 RETURNING 子句,则结果将类似于包含 RETURNING 列表中定义的列和值的 SELECT 语句,该语句在命令插入、更新或删除的行上计算。

注释

在执行 MERGE 期间,将执行以下步骤。

  1. 对所有指定的操作执行任何 BEFORE STATEMENT 触发器,无论其 WHEN 子句是否匹配。

  2. 从源表到目标表执行连接。生成的查询将正常优化,并将生成一组候选更改行。对于每个候选更改行,

    1. 评估每行是 MATCHEDNOT MATCHED BY SOURCE 还是 NOT MATCHED [BY TARGET]

    2. 按指定的顺序测试每个 WHEN 条件,直到一个返回 true。

    3. 当条件返回 true 时,执行以下操作

      1. 执行针对操作的事件类型触发的任何 BEFORE ROW 触发器。

      2. 执行指定的操作,调用目标表上的任何检查约束。

      3. 执行针对操作的事件类型触发的任何 AFTER ROW 触发器。

      如果目标关系是具有针对操作的事件类型的 INSTEAD OF ROW 触发器的视图,则它们将用于执行操作。

  3. 执行针对指定操作的任何 AFTER STATEMENT 触发器,无论它们是否实际发生。这类似于修改没有行的 UPDATE 语句的行为。

总而言之,事件类型(例如 INSERT)的语句触发器将在我们指定该类型的操作时触发。相反,行级触发器仅对正在执行的特定事件类型触发。因此,MERGE 命令可能会为 UPDATEINSERT 都触发语句触发器,即使仅触发了 UPDATE 行触发器。

您应该确保连接为每个目标行生成最多一个候选更改行。换句话说,目标行不应该连接到多个数据源行。如果确实如此,则只会使用其中一个候选更改行来修改目标行;以后尝试修改该行将导致错误。如果行触发器对目标表进行更改,并且随后 MERGE 也修改了这些已修改的行,则也会发生这种情况。如果重复的操作是 INSERT,这将导致唯一性冲突,而重复的 UPDATEDELETE 将导致基数冲突;后一种行为是SQL标准要求的。这与 PostgreSQLUPDATEDELETE 语句中连接的历史行为不同,在该行为中,第二次及后续尝试修改同一行将被简单地忽略。

如果 WHEN 子句省略了 AND 子句,则它将成为该类型(MATCHEDNOT MATCHED BY SOURCENOT MATCHED [BY TARGET])的最后一个可到达子句。如果指定了该类型的后续 WHEN 子句,则该子句将无法访问,并且会引发错误。如果未指定任何一种类型的最终可到达子句,则可能不会对候选更改行执行任何操作。

从数据源生成行的顺序默认为不确定的。如果需要,可以使用 source_query 指定一致的排序,这对于避免并发事务之间的死锁可能很有必要。

MERGE 与修改目标表的其他命令并发运行时,将应用通常的事务隔离规则;有关每个隔离级别下的行为说明,请参阅第 13.2 节。您可能还需要考虑使用 INSERT ... ON CONFLICT 作为替代语句,该语句提供在发生并发 INSERT 时运行 UPDATE 的功能。这两种语句类型之间存在多种差异和限制,它们不可互换。

示例

根据新的 recent_transactionscustomer_accounts 进行维护。

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);

请注意,这将与以下语句完全等效,因为 MATCHED 结果在执行期间不会更改。

MERGE INTO customer_account ca
USING (SELECT customer_id, transaction_value FROM recent_transactions) AS 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.*;

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 子句、BY SOURCEBY TARGET 限定符到 WHEN NOT MATCHEDDO NOTHING 操作以及 RETURNING 子句是SQL标准。

提交更正

如果您在文档中看到任何不正确的内容、与您对特定功能的体验不符的内容或需要进一步澄清的内容,请使用此表单报告文档问题。