Redrock Postgres 搜索 英文
版本: 15 / 16 / 17

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

说明

MERGEtarget_table_name 识别出的目标表中的行执行修改动作,使用 data_sourceMERGE 提供一个单一的SQL语句,可以有条件地 INSERTUPDATEDELETE 行,这项任务本来需要多条过程语言语句。

首先,MERGE 命令从 data_source 到目标表执行联接以产生零个或更多候选更改行。对于每个候选更改行,将 MATCHEDNOT MATCHED BY SOURCENOT MATCHED [BY TARGET] 的状态设定为只一次,然后以指定的顺序求值 WHEN 子句。对于每个候选更改行,求值为 true 的第一个子句会被执行。对于任何候选更改行不会执行多于一个 WHEN 子句。

MERGE 动作与同名常规 UPDATEINSERTDELETE 命令具有相同的影响。这些命令的语法有所不同,尤其是没有 WHERE 子句且未指定表名。所有动作都指目标表,不过可以使用触发器修改其他表。

指定 DO NOTHING 时,将跳过源行。由于动作是按照指定的顺序求值的,因此 DO NOTHING 在进行细粒度处理之前跳过无意义源行时非常方便。

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

没有单独的 MERGE 权限。如果您指定 update 操作,您必须对 SET 从句中引用的目标表的列具有 UPDATE 权限。如果您指定 insert 操作,您必须对目标表具有 INSERT 权限。如果您指定 delete 操作,您必须对目标表具有 DELETE 权限。如果您指定 DO NOTHING 操作,您必须至少对目标表的一列具有 SELECT 权限。您还需要在 SET 从句或任何 condition(包括 join_condition)或 expression 中引用的任何 data_source 和目标表列上具有 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 f,则MERGE语句的其余部分必须将此表引用为f,而不是foo

source_table_name

源表、视图或转换表的名称(可选的 schema 限定)。如果在表名前指定了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标准的扩展,将 BY TARGET 附加到 WHEN NOT MATCHED 以使其含义更明确,也是一种选项。

如果 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

目标表中列的名称。如果需要,可以对列名进行限定,从而使用子字段名或数组下标。(仅插入复合列的部分字段会使其他字段为空。)不要在对目标列的规范中包含表名。

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 子查询,其生成的输出列与前面括号中的列列表中列出的列一样多。执行时,该子查询不得产生多于 1 行。如果生成 1 行,则其列值分配给目标列;如果未生成行,则 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 触发器,无论它们是否实际发生。这类似于修改了 0 行的 UPDATE 语句的行为。

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

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

如果 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 子句、对 WHEN NOT MATCHEDBY SOURCEBY TARGET 限定符、DO NOTHING 动作和 RETURNING 子句是对SQL标准。