受支持版本: 当前版本 (18) / 17 / 16 / 15
开发版本: devel

MERGE

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

Synopsis

[ 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使用data_source 对由target_table_name标识的目标表中的行执行修改动作。 MERGE提供了一条单独的SQL语句, 可按条件对行执行INSERTUPDATEDELETE; 否则,这项工作通常需要多条过程语言语句。

首先,MERGE命令会将data_source 与目标表执行连接,生成零个或多个候选变更行。对于每个候选变更行, 系统只会确定一次其状态是MATCHEDNOT MATCHED BY SOURCE还是 NOT 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查询中按名称引用它们。 有关细节请参见Section 7.8SELECT。 请注意,MERGE不支持WITH RECURSIVE

target_table_name #

要合并到其中的目标表或视图名称(可选模式限定)。 如果在表名前指定ONLY,则匹配行只会在该命名表中被更新或删除。 如果未指定ONLY,则匹配行还会在继承自该命名表的任何表中被更新或删除。 也可以在表名后指定*,以显式表明包含后代表。 ONLY关键字和*选项不会影响插入动作, 因为插入动作始终只会插入到该命名表中。

如果target_table_name是一个视图, 它必须要么是没有INSTEAD OF触发器的自动可更新视图, 要么为WHEN子句中指定的每一种动作类型 (INSERTUPDATEDELETE) 都定义了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_condition #

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

Warning

只有那些用于尝试将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 #

用于向目标表插入一行的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_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, 或者用它们对应的output_alias来限定, 以返回目标表中的旧值或新值。来自目标表的未限定列名, 或者使用目标表名或别名限定的列名或 *,会在INSERTUPDATE动作中返回新值, 在DELETE动作中返回旧值。

output_name #

返回列所使用的名称。

输出

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

MERGE total_count

total_count是发生变化的总行数 (无论是插入、更新还是删除)。 如果total_count为 0, 则没有任何行以任何方式发生变化。

如果MERGE命令包含RETURNING子句, 则其结果将类似于一条SELECT语句, 包含RETURNING列表中定义的列和值, 这些值是针对该命令插入、更新或删除的行计算得到的。

注解

在执行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标准要求的行为。 这不同于PostgreSQL历史上在 UPDATEDELETE语句中处理连接的行为: 对同一行的第二次及后续修改尝试会被直接忽略。

如果某个WHEN子句省略了AND子句, 它就会成为该类型(MATCHEDNOT MATCHED BY SOURCENOT MATCHED [BY TARGET])最后一个可达的子句。 如果之后又指定了同一类型的WHEN子句, 则可证明它不可达,并会引发错误。 如果这三类中都没有指定最后一个可达子句,则候选变更行可能不会执行任何动作。

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

MERGE与其他修改目标表的命令并发运行时, 适用通常的事务隔离规则;请参见Section 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);

尝试插入一个新的库存项及其库存数量。如果该项已存在, 则改为更新现有项的库存数量。不允许库存为零的条目。 返回所有已发生更改的详细信息。

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 SOURCEBY TARGET限定词、 DO NOTHING动作以及RETURNING子句, 都是对SQL标准的扩展。

提交更正

如果您发现文档中有不正确的内容、与您使用特定功能的经验不符或需要进一步说明,请使用此表单来报告文档问题。