2025年9月25日: PostgreSQL 18 发布!
支持的版本: 当前 (18) / 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 [ WITH ( { OLD | NEW } AS output_alias [, ...] ) ]
                { * | 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 中的哪些行与目标表中的行匹配。

警告

仅应在 join_condition 中出现尝试与 data_source 行匹配的目标表列。仅引用目标表列的 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

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

不在显式或隐式列列表中的每个列都将填充默认值,如果是其声明的默认值,或者如果没有任何默认值则为 null。

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

列名不能重复指定。INSERT 操作不能包含子查询。

只能指定一个 VALUES 子句。VALUES 子句只能引用源关系中的列,因为根据定义没有匹配的目标行。

merge_update

指定一个 UPDATE 操作,该操作更新目标表的当前行。列名不能重复指定。

不允许表名或 WHERE 子句。

merge_delete

指定一个 DELETE 操作,该操作删除目标表的当前行。不要包含表名或任何其他子句,就像您通常对 DELETE 命令所做的那样。

column_name

目标表中的列名。如果需要,列名可以由子字段名或数组下标限定。(仅将值插入复合列的某些字段会将其他字段留空。)请勿在目标列的规范中包含表的名称。

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 列表中的 OLDNEW 行的可选替代名称。

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

output_expression

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

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

也可以使用 OLDNEW,或者 OLDNEW 的相应 output_alias 来限定列名或 *,以便返回目标表的旧值或新值。来自目标表的未限定列名,或者使用目标表名或别名限定的列名或 *,将为 INSERTUPDATE 操作返回新值,为 DELETE 操作返回旧值。

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_transactions,对 customer_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);

尝试插入新库存商品以及库存数量。如果商品已存在,则更新现有商品的库存数量。不允许零库存的录入。返回所有更改的详细信息。

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;

兼容性

此命令符合SQLSQL 标准的一部分。

WITH 子句,WHEN NOT MATCHEDBY SOURCEBY TARGET 限定符,DO NOTHING 操作,以及 RETURNING 子句是对SQLSQL 标准的一部分。

提交更正

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