适用于:SQL Server
Azure SQL 数据库
Azure SQL 托管实例
Azure Synapse Analytics(仅限专用 SQL 池)
Microsoft Fabric 预览版
中的 SQL 数据库Microsoft Fabric 中的仓库
该 MERGE 语句从与源表的联接结果中对目标表运行插入、更新或删除作。 例如,根据与另一个表的区别,在一个表中插入、更新或删除行,从而同步两个表。
本文根据所选产品版本提供不同的语法、参数、备注、权限和示例。 从版本下拉列表中选择所需的产品版本。
Note
在 Fabric 数据仓库中, MERGE 预览版。
Syntax
SQL Server 和 Azure SQL 数据库的语法:
[ WITH <common_table_expression> [,...n] ]
MERGE
[ TOP ( expression ) [ PERCENT ] ]
[ INTO ] <target_table> [ WITH ( <merge_hint> ) ] [ [ AS ] table_alias ]
USING <table_source> [ [ AS ] table_alias ]
ON <merge_search_condition>
[ WHEN MATCHED [ AND <clause_search_condition> ]
THEN <merge_matched> ] [ ...n ]
[ WHEN NOT MATCHED [ BY TARGET ] [ AND <clause_search_condition> ]
THEN <merge_not_matched> ]
[ WHEN NOT MATCHED BY SOURCE [ AND <clause_search_condition> ]
THEN <merge_matched> ] [ ...n ]
[ <output_clause> ]
[ OPTION ( <query_hint> [ ,...n ] ) ]
;
<target_table> ::=
{
[ database_name . schema_name . | schema_name . ] [ [ AS ] target_table ]
| @variable [ [ AS ] target_table ]
| common_table_expression_name [ [ AS ] target_table ]
}
<merge_hint>::=
{
{ [ <table_hint_limited> [ ,...n ] ]
[ [ , ] { INDEX ( index_val [ ,...n ] ) | INDEX = index_val }]
}
}
<merge_search_condition> ::=
<search_condition>
<merge_matched>::=
{ UPDATE SET <set_clause> | DELETE }
<merge_not_matched>::=
{
INSERT [ ( column_list ) ]
{ VALUES ( values_list )
| DEFAULT VALUES }
}
<clause_search_condition> ::=
<search_condition>
Azure Synapse Analytics、Fabric 数据仓库的语法:
[ WITH <common_table_expression> [,...n] ]
MERGE
[ INTO ] <target_table> [ [ AS ] table_alias ]
USING <table_source> [ [ AS ] table_alias ]
ON <merge_search_condition>
[ WHEN MATCHED [ AND <clause_search_condition> ]
THEN <merge_matched> ] [ ...n ]
[ WHEN NOT MATCHED [ BY TARGET ] [ AND <clause_search_condition> ]
THEN <merge_not_matched> ]
[ WHEN NOT MATCHED BY SOURCE [ AND <clause_search_condition> ]
THEN <merge_matched> ] [ ...n ]
[ OPTION ( <query_hint> [ ,...n ] ) ]
; -- The semi-colon is required, or the query will return a syntax error.
<target_table> ::=
{
[ database_name . schema_name . | schema_name . ]
target_table
}
<merge_search_condition> ::=
<search_condition>
<merge_matched>::=
{ UPDATE SET <set_clause> | DELETE }
<merge_not_matched>::=
{
INSERT [ ( column_list ) ]
VALUES ( values_list )
}
<clause_search_condition> ::=
<search_condition>
Arguments
WITH common_table_expression<>
指定在语句范围内 MERGE 定义的临时命名结果集或视图(也称为公共表表达式)。 结果集派生自简单查询,并由 MERGE 语句引用。 有关详细信息,请参阅 WITH common_table_expression (Transact-SQL)。
TOP ( 表达式 ) [ PERCENT ]
指定受影响的行数或所占百分比。
expression 可以是行数或行百分比。 表达式中引用的 TOP 行不会按任何顺序排列。 有关详细信息,请参阅 TOP (Transact-SQL)。
子 TOP 句应用于整个源表和整个目标表联接以及不符合插入、更新或删除作条件的联接行之后。 该 TOP 子句进一步将联接的行数减少到指定的值。 这些操作(插入、更新或删除)以无序方式应用于其余联接行。 也就是说,行在子句中 WHEN 定义的作之间没有分布的顺序。 例如,指定 TOP (10) 会影响 10 行。 在这些行中,可能会更新 7 行并插入 3 行,也可能会删除 1 行、更新 5 行并插入 4 行等。
如果没有源表上的筛选器,该 MERGE 语句可能会对源表执行表扫描或聚集索引扫描,以及对目标表执行表扫描或聚集索引扫描。 因此,即使使用 TOP 子句通过创建多个批处理来修改大型表,I/O 性能也会受到影响。 在这种情况下,请务必要确保所有连续批处理都以新行为目标。
database_name
target_table 所在数据库的名称。
schema_name
target_table 所属架构的名称。
target_table
<table_source> 中的数据行根据 <clause_search_condition> 进行匹配的表或视图。
target_table是语句子MERGE句指定WHEN的任何插入、更新或删除作的目标。
如果 target_table 为视图,则针对它的任何操作都必须满足更新视图所需的条件。 有关详细信息,请参阅通过视图修改数据。
target_table 不得是远程表。 target_table 不能定义其中的任何规则。target_table 不能是内存优化表。
可以将提示指定为 <merge_hint>。
Azure Synapse Analytics 不支持 <merge_hint>。
[ AS ] table_alias
用于为 target_table 引用表的替代名称。
使用 <table_source>
指定根据 与 target_table<merge_search_condition> 中的数据行进行匹配的数据源。 此匹配的结果决定了语句子MERGE句要执行的作WHEN。
<table_source> 可以是一个远程表,或者是一个能够访问远程表的派生表。
<table_source> 可以是一个派生表,它使用 Transact-SQL 表值构造函数通过指定多行来构造表。
<table_source> 可以是一个派生表,它使用 SELECT ... UNION ALL 来通过指定多行构造表。
[ AS ] table_alias
用于为 table_source 引用表的替代名称。
有关此子句的语法和参数的详细信息,请参阅 FROM (Transact-SQL)。
ON <merge_search_condition>
指定联接 <table_source> 与 target_table 以确定匹配位置所要满足的条件。
Caution
请务必仅指定目标表中用于匹配目的的列。 也就是说,指定与源表中的对应列进行比较的目标表列。 请勿尝试通过筛选子句中目标表中 ON 的行来提高查询性能;例如,例如指定 AND NOT target_table.column_x = value。 这样做可能会返回意外和不正确的结果。
匹配时, <merge_matched>
指定根据子句更新或删除 <table_source> ON <merge_search_condition> *target_table的所有行(与返回<merge_matched>的行匹配并满足任何其他搜索条件)。
该 MERGE 语句最多可以包含两 WHEN MATCHED 个子句。 如果指定了两个子句,则第一个子句必须附带一个 AND<search_condition> 子句。 对于任何给定行,仅当第一 WHEN MATCHED 个子句未应用时,才应用第二个子句。 如果有两 WHEN MATCHED 个子句,一个必须指定一个 UPDATE 作,一个必须指定一个 DELETE 作。 在子句中<merge_matched>指定时UPDATE,SQL Server 将返回错误,并且多行<table_source>匹配target_table<merge_search_condition>中的行。 该 MERGE 语句不能多次更新同一行,也不能更新并删除同一行。
如果不匹配 [ BY TARGET ] ,则 <merge_not_matched>
指定将行插入 target_table ,该行返回 <table_source> ON <merge_search_condition> 的每一行与 target_table中的行不匹配,但满足其他搜索条件(如果存在)。 要插入的值是由 <merge_not_matched> 子句指定的。 该 MERGE 语句只能有一个 WHEN NOT MATCHED [ BY TARGET ] 子句。
当源不匹配 <时,merge_matched>
指定 *target_table的所有行(与返回 <table_source> ON <merge_search_condition>的行不匹配)以及满足任何其他搜索条件的所有行都会根据 <merge_matched> 子句进行更新或删除。
该 MERGE 语句最多可以有两 WHEN NOT MATCHED BY SOURCE 个子句。 如果指定了两个子句,则第一个子句必须附带一个 AND<clause_search_condition> 子句。 对于任何给定行,仅当第一 WHEN NOT MATCHED BY SOURCE 个子句未应用时,才应用第二个子句。 如果有两 WHEN NOT MATCHED BY SOURCE 个子句,则一个子句必须指定一个 UPDATE 作,一个子句必须指定一个 DELETE 作。 在 <clause_search_condition> 中只能引用目标表中的列。
如果 <table_source> 未返回任何行,无法访问源表中的列。 如果 <merge_matched> 子句中指定的更新或删除操作引用了源表中的列,则会返回错误 207(列名无效)。 例如,由于无法访问源表中的 WHEN NOT MATCHED BY SOURCE THEN UPDATE SET TargetTable.Col1 = SourceTable.Col1,因此 Col1 子句可能导致该语句失败。
AND <clause_search_condition>
指定任何有效的搜索条件。 有关详细信息,请参阅搜索条件(Transact-SQL)。
<table_hint_limited>
为语句执行 MERGE 的每个插入、更新或删除作指定对目标表应用的一个或多个表提示。 关键字 WITH 和括号是必需的。
NOLOCK 不允许使用 READUNCOMMITTED 。 有关表提示的详细信息,请参阅表提示(Transact-SQL)。
TABLOCK指定作为语句目标的INSERT表的提示与指定TABLOCKX提示的效果相同。 对表采用排他锁。 如果指定了 FORCESEEK,它会应用于与源表联接的目标表的隐式实例。
Caution
使用 READPAST 指定 WHEN NOT MATCHED [ BY TARGET ] THEN INSERT 可能会导致 INSERT 违反 UNIQUE 约束的作。
INDEX ( index_val [,...n ] )
指定目标表上一个或多个索引的名称或 ID,以执行与源表的隐式联接。 有关详细信息,请参阅表提示 (Transact-SQL)。
<output_clause>
针对 target_table 中不按照任何特定顺序更新、插入或删除的所有行返回一行。
$action 可在 output 子句中指定。
$action 是类型为 nvarchar(10) 的列,它返回每行(INSERT,UPDATE 或 DELETE)3 个值中的 1 个(具体视对相应行完成的操作而定)。 子 OUTPUT 句是查询或计算受 a MERGE. 有关此子句的参数和行为的详细信息,请参阅 OUTPUT 子句 (Transact-SQL)。
OPTION ( <query_hint> [ ,...n ] )
指定优化器提示用于自定义数据库引擎处理语句的方式。 有关详细信息,请参阅查询提示(Transact-SQL)。
<merge_matched>
指定应用于 与 返回 <table_source> ON <merge_search_condition>的行不匹配且满足任何其他搜索条件的所有target_table行的更新或删除作。
UPDATE SET <set_clause>
指定目标表中要更新的列或变量名称的列表,以及用来更新它们的值。
有关该子句的参数的详细信息,请参阅 UPDATE (Transact-SQL)。 不支持将变量设置为与列相同的值。
DELETE
指定删除与 target_table 中的行匹配的行。
<merge_not_matched>
指定要插入到目标表中的值。
( column_list )
要在其中插入数据的目标表中一个或多个列的列表。 列必须指定为单部分名称,否则 MERGE 语句将失败。 必须用括号将 column_list 括起来,并且用逗号进行分隔。
VALUES ( values_list)
返回要插入到目标表中的值的常量、变量或表达式的逗号分隔列表。 表达式不能包含语句 EXECUTE 。
<search_condition>
指定用于指定 <merge_search_condition> 或 <clause_search_condition> 的搜索条件。 有关此子句的参数的详细信息,请参阅搜索条件(Transact-SQL)。
<图形搜索模式>
指定图匹配模式。 有关此子句参数的详细信息,请参阅 MATCH (Transact-SQL)。
Remarks
当两个表具有复杂的匹配特征混合时,为 MERGE 语句描述的条件行为最有效。 例如,插入不存在的行,或更新匹配的行。 仅基于另一个表的行更新一个表时,使用INSERT和UPDATEDELETE语句提高性能和可伸缩性。 例如:
INSERT tbl_A (col, col2)
SELECT col, col2
FROM tbl_B
WHERE NOT EXISTS (SELECT col FROM tbl_A A2 WHERE A2.col = tbl_B.col);
必须至少指定三 MATCHED 个子句中的一个,但可以按任意顺序指定它们。 不能在同 MATCHED 一子句中多次更新变量。
语句在目标表 MERGE 上指定的任何插入、更新或删除作都受其定义的任何约束的限制,包括任何级联引用完整性约束。
ON如果IGNORE_DUP_KEY针对目标表上的任何唯一索引,MERGE则忽略此设置。
该 MERGE 语句需要分号 (;) 作为语句终止符。 在没有终止符的情况下运行语句时 MERGE ,将引发错误 10713。
使用后 MERGE, @@ROWCOUNT(Transact-SQL) 返回插入、更新和删除到客户端的行总数。
MERGE 如果数据库兼容性级别设置为 100 或更高,则为完全保留关键字。 语句MERGE在数据库90100兼容性级别下可用;但是,当数据库兼容性级别设置为90时,关键字不会完全保留。
Caution
使用排队更新复制时不要使用该MERGE语句。 排队 MERGE 更新触发器不兼容。 将 MERGE 语句替换为 and INSERTUPDATE 语句。
Azure Synapse Analytics 注意事项
在 Azure Synapse Analytics 中,与 SQL Server 和 Azure SQL 数据库相比,该 MERGE 命令具有以下差异。
- 在版本低于 10.0.17829.0 的内部版本中不支持更新
MERGE分发密钥列。 如果无法暂停或强制升级,请使用 ANSIUPDATE FROM ... JOIN语句作为解决方法,直到版本 10.0.17829.0。 - 更新
MERGE作为删除和插入对实现。 更新受影响的行计数MERGE包括已删除的行和插入的行。 具有 列的表不支持< - 不能在源表的
USING子句中使用表值构造函数。 使用SELECT ... UNION ALL创建包含多个行的派生源表。 - 此表描述了对具有不同分发类型的表的支持:
| Azure Synapse Analytics 中的 MERGE CLAUSE | 支持的 TARGET 分发表 |
支持的 SOURCE 分发表 | Comment |
|---|---|---|---|
WHEN MATCHED |
所有分发类型 | 所有分发类型 | |
NOT MATCHED BY TARGET |
HASH |
所有分发类型 | 使用 UPDATE/DELETE FROM...JOIN 同步两个表。 |
NOT MATCHED BY SOURCE |
所有分发类型 | 所有分发类型 |
Tip
如果使用分布哈希键作为 JOIN 列, MERGE 并且只执行相等比较,则可以从子句中的 WHEN MATCHED THEN UPDATE SET 列列表中省略分布键,因为这是冗余更新。
在 Azure Synapse Analytics 中, MERGE 基于版本低于 10.0.17829.0 的命令在某些情况下可能会使目标表处于不一致状态,并将行置于错误的分布中,导致以后的查询在某些情况下返回错误的结果。 下面两种案例可能会发生此类问题:
| Scenario | Comment |
|---|---|
|
案例 1 在 MERGE包含辅助索引或UNIQUE约束的 HASH 分布式TARGET表上使用。 |
- 已在 Synapse SQL 10.0.15563.0 及更高版本中修复。 - 如果 SELECT @@VERSION 返回的版本低于 10.0.15563.0,请手动暂停并恢复 Synapse SQL 池以选取此修补程序。- 在修复应用于 Synapse SQL 池之前,请避免对 MERGEHASH具有辅助索引或UNIQUE约束的分布式TARGET表使用命令。 |
|
案例 2 使用 MERGE 更新 HASH 分布式表的分布键列。 |
- 已在 Synapse SQL 10.0.17829.0 及更高版本中修复。 - 如果 SELECT @@VERSION 返回的版本低于 10.0.17829.0,请手动暂停并恢复 Synapse SQL 池以选取此修补程序。- 在修复应用于 Synapse SQL 池之前,请避免使用 MERGE 命令更新分发键列。 |
这两种方案中的更新都无法修复先前执行已受影响的 MERGE 表。 使用以下脚本手动标识和修复任何受影响的表。
若要检查数据库中哪些 HASH 分布式表可能令人担忧(如果在前面提到的事例中使用),请运行以下语句:
-- Case 1
SELECT a.name,
c.distribution_policy_desc,
b.type
FROM sys.tables a
INNER JOIN sys.indexes b
ON a.object_id = b.object_id
INNER JOIN sys.pdw_table_distribution_properties c
ON a.object_id = c.object_id
WHERE b.type = 2
AND c.distribution_policy_desc = 'HASH';
-- Subject to Case 2, if distribution key value is updated in MERGE statement
SELECT a.name,
c.distribution_policy_desc
FROM sys.tables a
INNER JOIN sys.pdw_table_distribution_properties c
ON a.object_id = c.object_id
WHERE c.distribution_policy_desc = 'HASH';
若要检查分布式表MERGE是否HASH受 Case 1 或 Case 2 的影响,请按照以下步骤检查表是否在错误的分布中存在行。 如果返回 no need for repair,则此表不受影响。
IF object_id('[check_table_1]', 'U') IS NOT NULL
DROP TABLE [check_table_1]
GO
IF object_id('[check_table_2]', 'U') IS NOT NULL
DROP TABLE [check_table_2]
GO
CREATE TABLE [check_table_1]
WITH (DISTRIBUTION = ROUND_ROBIN) AS
SELECT <DISTRIBUTION_COLUMN> AS x
FROM <MERGE_TABLE>
GROUP BY <DISTRIBUTION_COLUMN>;
GO
CREATE TABLE [check_table_2]
WITH (DISTRIBUTION = HASH (x)) AS
SELECT x
FROM [check_table_1];
GO
IF NOT EXISTS (
SELECT TOP 1 *
FROM (
SELECT <DISTRIBUTION_COLUMN> AS x
FROM <MERGE_TABLE>
EXCEPT
SELECT x
FROM [check_table_2]
) AS tmp
)
SELECT 'no need for repair' AS result
ELSE
SELECT 'needs repair' AS result
GO
IF object_id('[check_table_1]', 'U') IS NOT NULL
DROP TABLE [check_table_1]
GO
IF object_id('[check_table_2]', 'U') IS NOT NULL
DROP TABLE [check_table_2]
GO
若要修复受影响的表,请运行以下语句,将旧表中的所有行复制到新表中。
IF object_id('[repair_table_temp]', 'U') IS NOT NULL
DROP TABLE [repair_table_temp];
GO
IF object_id('[repair_table]', 'U') IS NOT NULL
DROP TABLE [repair_table];
GO
CREATE TABLE [repair_table_temp]
WITH (DISTRIBUTION = ROUND_ROBIN) AS
SELECT *
FROM <MERGE_TABLE>;
GO
-- [repair_table] will hold the repaired table generated from <MERGE_TABLE>
CREATE TABLE [repair_table]
WITH (DISTRIBUTION = HASH (<DISTRIBUTION_COLUMN>)) AS
SELECT *
FROM [repair_table_temp];
GO
IF object_id('[repair_table_temp]', 'U') IS NOT NULL
DROP TABLE [repair_table_temp];
GO
Troubleshooting
在某些情况下, MERGE 即使目标表或源表没有 1,024 列,语句也可能会导致错误 CREATE TABLE failed because column <> in table <> exceeds the maximum of 1024 columns.。 满足以下所有条件时,可能会出现这种情况:
- 在一个
DELETE或UPDATE SETINSERT作中MERGE指定了多个列(不特定于任何WHEN [NOT] MATCHED子句) - 条件
JOIN中的任何列都有非聚集索引 (NCI) - 目标表
HASH已分发
如果找到此类错误,建议的解决方法如下所示:
- 从
JOIN列中删除非聚集索引(NCI),或在没有 NCI 的列上联接。 如果以后更新基础表以在列上JOIN包括 NCI,则MERGE语句在运行时可能容易受到此错误的影响。 有关详细信息,请参阅 DROP INDEX。 - 使用 UPDATE、 DELETE 和 INSERT 语句,而不是
MERGE.
触发器实现
对于语句中指定的 MERGE 每个插入、更新或删除作,SQL Server 会触发目标表上定义的任何相应 AFTER 触发器,但不能保证触发触发器的第一个或最后一个作。 为相同操作定义的触发器会遵循您指定的顺序进行触发。 有关设置触发器激发顺序的详细信息,请参阅指定第一个和最后一个触发器。
如果目标表在其上为语句执行的MERGE插入、更新或删除作定义了已启用 INSTEAD OF 触发器,则必须为语句中指定的MERGE所有作启用 INSTEAD OF 触发器。
如果在target_table上定义了任何 INSTEAD OF UPDATE 或 INSTEAD OF DELETE 触发器,则不会运行更新或删除作。 而是会触发触发器,并相应地填充 inserted 和 deleted 表。
如果在target_table上定义了任何 INSTEAD OF INSERT 触发器,则不会执行插入作。 而是相应地填充表。
Note
与单独的 INSERT行 UPDATE和 DELETE 语句不同,触发器内反映 @@ROWCOUNT 的行数可能更高。 任何@@ROWCOUNTAFTER触发器(无论触发器捕获的数据修改语句如何)都将反映受MERGE该触发器影响的行总数。 例如,如果MERGE语句插入一行,更新一行,并删除一行,则为任何AFTER触发器的三个,@@ROWCOUNT即使触发器仅声明为INSERT语句。
Permissions
SELECT需要对源表具有权限,UPDATE或者INSERTDELETE需要对目标表具有权限。 有关详细信息,请参阅 SELECT(Transact-SQL)、 INSERT(Transact-SQL)、 UPDATE(Transact-SQL)和 DELETE(Transact-SQL) 文章中的“权限”部分。
有关索引的最佳做法
通过使用语句 MERGE ,可以将单个 DML 语句替换为单个语句。 由于操作是在单个语句中执行的,因此可以提高查询性能,从而最大限度地减少处理源表和目标表中数据的次数。 然而,性能的提升取决于是否进行了正确的索引和联接以及是否遵守了其他注意事项。
为了提高语句的性能 MERGE ,我们建议遵循以下索引准则:
- 创建索引以方便源和目标
MERGE之间的联接:- 在源表的联接列上创建索引,该索引具有涵盖目标表的联接逻辑的键。 如果可能,该索引应该是唯一的。
- 此外,在目标表中的联接列上创建索引。 如果可能,该索引应该是唯一的聚集索引。
- 这两个索引可确保对表中的数据进行排序,而唯一性有助于进行比较。 因为查询优化器不需要执行额外验证处理即可定位和更新重复的行,也不需要执行其他排序操作,所以查询性能得到了提高。
- 避免将任何形式的列存储索引作为语句的目标的
MERGE表。 与任何 UPDATEE 一样,通过更新分阶段行存储表、执行批处理DELETE和INSERT(而不是UPDATE或MERGE)来发现列存储索引的性能更好。
MERGE 的并发注意事项
在锁定方面,MERGE不同于离散、连续INSERTUPDATE和DELETE语句。
MERGE 但仍使用不同的锁定机制执行 INSERT、 UPDATE作和 DELETE 作。 为某些应用程序需求编写离散INSERTUPDATE语句和DELETE语句可能更有效。 大规模地, MERGE 可能会引入复杂的并发问题或需要高级故障排除。 因此,计划在部署到生产环境之前彻底测试任何 MERGE 语句。
MERGE语句是适合在以下方案中(但不限于)中的离散INSERTUPDATE作DELETE的替代方法:
- 涉及大量行计数的 ETL 操作在不需要其他并发操作的时间段内执行。 预期出现大量并发时,单独的
INSERTUPDATE并发和DELETE逻辑的性能可能比语句的阻塞MERGE更少。 - 涉及较小行计数的复杂操作和不太可能长时间执行的事务。
- 涉及用户表的复杂操作,其中索引可设计为确保最佳执行计划,避免表扫描和查找以支持索引扫描或索引查找(理想情况)。
并发的其他注意事项:
- 在某些情况下,唯一键应由 <
a0/> 插入和更新,指定 将防止唯一键冲突。 HOLDLOCK是事务隔离级别的同义词SERIALIZABLE,不允许其他并发事务修改此事务已读取的数据。SERIALIZABLE是最安全的隔离级别,但提供与其他事务的最小并发性,这些事务保留对数据范围的锁,以防止在读取正在进行时插入或更新幻影行。 有关详细信息HOLDLOCK,请参阅表提示和 SET TRANSACTION ISOLATION LEVEL(Transact-SQL)。
有关 JOIN 的最佳做法
为了提高语句的性能 MERGE 并确保获得正确的结果,建议遵循以下联接准则:
- 仅在子句中
ON <merge_search_condition>指定用于确定源表和目标表中匹配数据的条件的搜索条件。 也就是说,仅指定与源表中的对应列进行比较的目标表列。 - 不要包括与其他值(如常量)的比较。
若要从源表或目标表中筛选出行,请使用以下方法之一。
- 指定相应
WHEN子句中行筛选的搜索条件。 例如,WHEN NOT MATCHED AND S.EmployeeName LIKE 'S%' THEN INSERT.... - 对返回筛选行的源表或目标表定义视图,并且将该视图作为源表或目标表进行引用。 如果该视图是针对目标表定义的,则针对该视图的任何操作都必须满足更新视图所需的条件。 有关使用视图更新数据的详细信息,请参阅 “通过视图修改数据”。
- 使用
WITH <common table expression>子句筛选掉源表或目标表中的行。 此方法类似于在子句中ON指定其他搜索条件,并可能生成不正确的结果。 建议您避免使用此方法,或者在采用它前进行全面测试。
语句中的 MERGE 联接作以与语句中的 SELECT 联接相同的方式进行优化。 也就是说,当 SQL Server 处理联接时,查询优化器从多种可行方法中选择最高效的方法来处理联接。 如果源表和目标表的大小相似,且前面介绍的索引准则已应用于源表和目标表,那么合并联接运算符是最高效的查询计划。 这是由于对两个表都只扫描一次,并且无需对数据进行排序。 如果源表小于目标表,最好使用嵌套循环运算符。
可以通过在MERGE语句中指定子句来强制使用OPTION (<query_hint>)特定联接。 建议不要将哈希联接用作语句的 MERGE 查询提示,因为此联接类型不使用索引。
有关参数化的最佳做法
如果在不使用参数的情况下执行了 SELECT、 INSERT、 UPDATE或 DELETE 语句,SQL Server 查询优化器可以选择在内部参数化语句。 也就是说,使用参数替换查询中包含的任何文字值。 例如,语句 INSERT dbo.MyTable (Col1, Col2) VALUES (1, 10) 可在内部实现为 INSERT dbo.MyTable (Col1, Col2) VALUES (@p1, @p2)。 此过程称为“简单参数化”,它增强了关系引擎将新 SQL 语句与先前编译的现有执行计划进行匹配的能力。 由于减少了查询编译和重新编译的频率,因此可提高查询性能。 查询优化器不会将简单的参数化过程应用于 MERGE 语句。 因此,MERGE包含文本值的语句可能不会执行和单独INSERTUPDATE执行,或DELETE语句,因为每次MERGE执行语句时都会编译一个新计划。
若要提高查询性能,我们建议您遵循以下参数化准则:
- 参数化子句和
WHEN语句子MERGE句中的所有ON <merge_search_condition>文本值。 例如,可以将语句合并MERGE到存储过程中,将文本值替换为适当的输入参数。 - 如果无法参数化语句,请创建
TEMPLATE类型的计划指南,并在计划指南中指定PARAMETERIZATION FORCED查询提示。 有关详细信息,请参阅使用计划指南指定查询参数化行为。 - 如果
MERGE对数据库频繁执行语句,请考虑将PARAMETERIZATION数据库上的选项设置为FORCED。 设置此选项时请谨慎从事。PARAMETERIZATION选项是数据库级别设置,它影响如何对数据库处理所有查询。 有关详细信息,请参阅强制参数化。 - 计划指南的一种更新、更简单的替代方案是使用与查询存储提示类似的策略。 有关详细信息,请参阅查询存储提示。
有关 TOP 子句的最佳做法
MERGE在语句中,该TOP子句指定在源表和目标表联接后受影响的行数或百分比,在不符合插入、更新或删除作条件的行之后删除。 该 TOP 子句进一步将联接行数减少到指定值,并且插入、更新或删除作以无序方式应用于其余联接行。 也就是说,行在子句中 WHEN 定义的作之间没有分布的顺序。 例如,指定 TOP (10) 影响 10 行;在这些行中,7 行可能会更新,3 个插入,或者 1 个可能被删除、5 个更新和 4 个插入等等。
通常,使用 TOP 子句分批对大型表执行数据作语言 (DML)作。
TOP出于此目的使用MERGE语句中的子句时,请务必了解以下含义。
I/O 性能可能会受到影响。
该
MERGE语句对源表和目标表执行完整表扫描。 使操作分批执行可减少每批执行的写入操作的数量;但在每个批处理中都将对源表和目标表执行完全表扫描。 生成的读取活动可能会影响查询性能和表中的其他并发活动。可能产生不正确的结果。
请务必确保所有连续批处理都以新行为目标,否则可能会发生意外行为,如在目标表中错误地插入重复行。 如果源表包含的某行未包括在目标批处理中,但却包含在总目标表中,便会发生此情况。 确保获得正确的结果:
- 使用该
ON子句确定哪些源行会影响现有目标行以及哪些行是真正新的。 - 使用子句中的其他
WHEN MATCHED条件来确定目标行是否已由上一批更新。 - 使用子句和
SET逻辑中的其他WHEN MATCHED条件来验证无法更新同一行两次。
- 使用该
TOP由于子句仅在应用这些子句后应用,因此每次执行都插入一个真正不匹配的行或更新一个现有行。
有关大容量加载的最佳做法
该 MERGE 语句可用于通过将子句指定 OPENROWSET(BULK...) 为表源来有效地将数据从源数据文件大容量加载到目标表中。 通过这种方式,可以在单个批中处理整个文件。
若要改进大容量合并过程的性能,我们建议您遵循以下准则:
对目标表中的联接列创建聚集索引。
在大容量加载
MERGE期间禁用目标表上的其他非唯一非聚集索引,之后启用它们。 这对于夜间批量数据操作很常见且有用。ORDER使用子句中的OPENROWSET(BULK...)提示UNIQUE指定源数据文件的排序方式。默认情况下,大容量操作假定数据文件未排序。 因此,必须根据目标表上的聚集索引对源数据进行排序,并且
ORDER提示用于指示顺序,以便查询优化器可以生成更高效的查询计划。 在运行时将对提示进行验证;如果数据流不符合指定的提示,则会引发错误。
上述准则确保联接键唯一并且源文件中数据的排序顺序与目标表相符。 因为不需要执行其他排序操作和不必要的数据复制,所以提高了查询的性能。
测量和诊断 MERGE 性能
以下功能可用于帮助你测量和诊断语句的性能 MERGE 。
- 使用sys.dm_exec_query_optimizer_info动态管理视图中的合并 stmt 计数器返回语句
MERGE的查询优化数。 -
merge_action_type使用sys.dm_exec_plan_attributes动态管理视图中的属性返回用作语句结果的MERGE触发器执行计划的类型。 - 使用扩展事件会话以与其他数据作语言 (DML) 语句相同的方式收集语句的
MERGE故障排除数据。 有关扩展事件概述的详细信息,请参阅快速入门:扩展事件和使用 SSMS XEvent 探查器。
Examples
A. 使用 MERGE 在一个语句中对表执行 INSERT 和 UPDATE 操作
常见方案是,更新表中的一个或多个列(若有匹配行)。 或者,在没有匹配行的情况下,将数据作为新行插入。 通常,可以通过将参数传递给包含相应 UPDATE 语句的 INSERT 存储过程来执行任一方案。
MERGE使用语句,可以在单个语句中执行这两项任务。 下面的示例演示 AdventureWorks2022 数据库中包含 INSERT 语句和 UPDATE 语句的存储过程。 然后,使用单个 MERGE 语句修改该过程以运行等效的作。
CREATE PROCEDURE dbo.InsertUnitMeasure @UnitMeasureCode NCHAR(3), @Name NVARCHAR(25)
AS
BEGIN
SET NOCOUNT ON;
-- Update the row if it exists.
UPDATE Production.UnitMeasure
SET Name = @Name
WHERE UnitMeasureCode = @UnitMeasureCode
-- Insert the row if the UPDATE statement failed.
IF (@@ROWCOUNT = 0)
BEGIN
INSERT INTO Production.UnitMeasure (
UnitMeasureCode,
Name
)
VALUES (@UnitMeasureCode, @Name)
END
END;
GO
-- Test the procedure and return the results.
EXEC InsertUnitMeasure @UnitMeasureCode = 'ABC', @Name = 'Test Value';
SELECT UnitMeasureCode, Name
FROM Production.UnitMeasure
WHERE UnitMeasureCode = 'ABC';
GO
-- Rewrite the procedure to perform the same operations using the
-- MERGE statement.
-- Create a temporary table to hold the updated or inserted values
-- from the OUTPUT clause.
CREATE TABLE #MyTempTable (
ExistingCode NCHAR(3),
ExistingName NVARCHAR(50),
ExistingDate DATETIME,
ActionTaken NVARCHAR(10),
NewCode NCHAR(3),
NewName NVARCHAR(50),
NewDate DATETIME
);
GO
ALTER PROCEDURE dbo.InsertUnitMeasure @UnitMeasureCode NCHAR(3),
@Name NVARCHAR(25)
AS
BEGIN
SET NOCOUNT ON;
MERGE Production.UnitMeasure AS tgt
USING (SELECT @UnitMeasureCode, @Name) AS src(UnitMeasureCode, Name)
ON (tgt.UnitMeasureCode = src.UnitMeasureCode)
WHEN MATCHED
THEN
UPDATE
SET Name = src.Name
WHEN NOT MATCHED
THEN
INSERT (UnitMeasureCode, Name)
VALUES (src.UnitMeasureCode, src.Name)
OUTPUT deleted.*,
$action,
inserted.*
INTO #MyTempTable;
END;
GO
-- Test the procedure and return the results.
EXEC InsertUnitMeasure @UnitMeasureCode = 'ABC', @Name = 'New Test Value';
EXEC InsertUnitMeasure @UnitMeasureCode = 'XYZ', @Name = 'Test Value';
EXEC InsertUnitMeasure @UnitMeasureCode = 'ABC', @Name = 'Another Test Value';
SELECT * FROM #MyTempTable;
-- Cleanup
DELETE FROM Production.UnitMeasure
WHERE UnitMeasureCode IN ('ABC', 'XYZ');
DROP TABLE #MyTempTable;
GO
CREATE PROCEDURE dbo.InsertUnitMeasure @UnitMeasureCode NCHAR(3),
@Name NVARCHAR(25)
AS
BEGIN
SET NOCOUNT ON;
-- Update the row if it exists.
UPDATE Production.UnitMeasure
SET Name = @Name
WHERE UnitMeasureCode = @UnitMeasureCode
-- Insert the row if the UPDATE statement failed.
IF (@@ROWCOUNT = 0)
BEGIN
INSERT INTO Production.UnitMeasure (
UnitMeasureCode,
Name
)
VALUES (@UnitMeasureCode, @Name)
END
END;
GO
-- Test the procedure and return the results.
EXEC InsertUnitMeasure @UnitMeasureCode = 'ABC', @Name = 'Test Value';
SELECT UnitMeasureCode, Name
FROM Production.UnitMeasure
WHERE UnitMeasureCode = 'ABC';
GO
-- Rewrite the procedure to perform the same operations using the
-- MERGE statement.
ALTER PROCEDURE dbo.InsertUnitMeasure @UnitMeasureCode NCHAR(3),
@Name NVARCHAR(25)
AS
BEGIN
SET NOCOUNT ON;
MERGE Production.UnitMeasure AS tgt
USING (
SELECT @UnitMeasureCode,
@Name
) AS src(UnitMeasureCode, Name)
ON (tgt.UnitMeasureCode = src.UnitMeasureCode)
WHEN MATCHED
THEN
UPDATE SET Name = src.Name
WHEN NOT MATCHED
THEN
INSERT (UnitMeasureCode, Name)
VALUES (src.UnitMeasureCode, src.Name);
END;
GO
-- Test the procedure and return the results.
EXEC InsertUnitMeasure @UnitMeasureCode = 'ABC', @Name = 'New Test Value';
EXEC InsertUnitMeasure @UnitMeasureCode = 'XYZ', @Name = 'Test Value';
EXEC InsertUnitMeasure @UnitMeasureCode = 'ABC', @Name = 'Another Test Value';
-- Cleanup
DELETE FROM Production.UnitMeasure
WHERE UnitMeasureCode IN ('ABC', 'XYZ');
GO
B. 使用 MERGE 在一个语句中对表执行 UPDATE 和 DELETE 操作
以下示例用于 MERGE 根据表中处理的顺序,每天更新 ProductInventory AdventureWorks2022 示例数据库中的 SalesOrderDetail 表。 通过减去每天对 Quantity 表中的每种产品所下的订单数,更新 ProductInventory 表的 SalesOrderDetail 列。 如果某种产品的订单数导致该产品的库存量下降到 0 或更少,则从 ProductInventory 表中删除该产品对应的行。
CREATE PROCEDURE Production.usp_UpdateInventory @OrderDate DATETIME
AS
MERGE Production.ProductInventory AS tgt
USING (
SELECT ProductID,
SUM(OrderQty)
FROM Sales.SalesOrderDetail AS sod
INNER JOIN Sales.SalesOrderHeader AS soh
ON sod.SalesOrderID = soh.SalesOrderID
AND soh.OrderDate = @OrderDate
GROUP BY ProductID
) AS src(ProductID, OrderQty)
ON (tgt.ProductID = src.ProductID)
WHEN MATCHED
AND tgt.Quantity - src.OrderQty <= 0
THEN
DELETE
WHEN MATCHED
THEN
UPDATE
SET tgt.Quantity = tgt.Quantity - src.OrderQty,
tgt.ModifiedDate = GETDATE()
OUTPUT $action,
Inserted.ProductID,
Inserted.Quantity,
Inserted.ModifiedDate,
Deleted.ProductID,
Deleted.Quantity,
Deleted.ModifiedDate;
GO
EXECUTE Production.usp_UpdateInventory '20030501';
CREATE PROCEDURE Production.usp_UpdateInventory @OrderDate DATETIME
AS
MERGE Production.ProductInventory AS tgt
USING (
SELECT ProductID,
SUM(OrderQty)
FROM Sales.SalesOrderDetail AS sod
INNER JOIN Sales.SalesOrderHeader AS soh
ON sod.SalesOrderID = soh.SalesOrderID
AND soh.OrderDate = @OrderDate
GROUP BY ProductID
) AS src(ProductID, OrderQty)
ON (tgt.ProductID = src.ProductID)
WHEN MATCHED
AND tgt.Quantity - src.OrderQty <= 0
THEN
DELETE
WHEN MATCHED
THEN
UPDATE
SET tgt.Quantity = tgt.Quantity - src.OrderQty,
tgt.ModifiedDate = GETDATE();
GO
EXECUTE Production.usp_UpdateInventory '20030501';
C. 借助派生的源表,使用 MERGE 对目标表执行 UPDATE 和 INSERT 操作
以下示例通过 MERGE 更新或插入行来修改 SalesReason AdventureWorks2022 数据库中的表。
当源表中的 NewName 值与目标表 (Name) 的 SalesReason 列中的值匹配时,就会更新此目标表中的 ReasonType 列。 如果 NewName 的值不匹配,就会将源行插入目标表中。 此源表是一个派生表,它使用 Transact-SQL 表值构造函数指定源表的多个行。 有关在派生表中使用表值构造函数的详细信息,请参阅表值构造函数 (Transact-SQL)。
该OUTPUT子句可用于查询语句的结果MERGE,有关详细信息,请参阅 OUTPUT 子句(Transact-SQL)。 该示例还演示如何将子句的结果 OUTPUT 存储在表变量中。 然后,通过运行返回插入和更新行计数的简单选择作来汇总语句的结果 MERGE 。
-- Create a temporary table variable to hold the output actions.
DECLARE @SummaryOfChanges TABLE (Change VARCHAR(20));
MERGE INTO Sales.SalesReason AS tgt
USING (
VALUES ('Recommendation', 'Other'),
('Review', 'Marketing'),
('Internet', 'Promotion')
) AS src(NewName, NewReasonType)
ON tgt.Name = src.NewName
WHEN MATCHED
THEN
UPDATE
SET ReasonType = src.NewReasonType
WHEN NOT MATCHED BY TARGET
THEN
INSERT (Name, ReasonType)
VALUES (NewName, NewReasonType)
OUTPUT $action
INTO @SummaryOfChanges;
-- Query the results of the table variable.
SELECT Change,
COUNT(*) AS CountPerChange
FROM @SummaryOfChanges
GROUP BY Change;
当源表中的 NewName 值与目标表 (Name) 的 SalesReason 列中的值匹配时,就会更新此目标表中的 ReasonType 列。 如果 NewName 的值不匹配,就会将源行插入目标表中。 此源表是一个派生表,它使用 SELECT ... UNION ALL 来指定源表的多个行。
MERGE INTO Sales.SalesReason AS tgt
USING (
SELECT 'Recommendation', 'Other'
UNION ALL
SELECT 'Review', 'Marketing'
UNION ALL
SELECT 'Internet', 'Promotion'
) AS src(NewName, NewReasonType)
ON tgt.Name = src.NewName
WHEN MATCHED
THEN
UPDATE SET ReasonType = src.NewReasonType
WHEN NOT MATCHED BY TARGET
THEN
INSERT (Name, ReasonType)
VALUES (NewName, NewReasonType);
D. 将 MERGE 语句的执行结果插入到另一个表中
以下示例捕获从 OUTPUT 语句的 MERGE 子句返回的数据,并将该数据插入另一个表中。 该MERGE语句根据表中处理SalesOrderDetail的顺序更新 Quantity AdventureWorks2022 数据库中表的列ProductInventory。 下面的示例捕获已更新行,并将它们插入用于跟踪库存变化的另一个表中。
CREATE TABLE Production.UpdatedInventory (
ProductID INT NOT NULL,
LocationID INT,
NewQty INT,
PreviousQty INT,
CONSTRAINT PK_Inventory PRIMARY KEY CLUSTERED (
ProductID,
LocationID
)
);
GO
INSERT INTO Production.UpdatedInventory
SELECT ProductID, LocationID, NewQty, PreviousQty
FROM (
MERGE Production.ProductInventory AS pi
USING (
SELECT ProductID, SUM(OrderQty)
FROM Sales.SalesOrderDetail AS sod
INNER JOIN Sales.SalesOrderHeader AS soh
ON sod.SalesOrderID = soh.SalesOrderID
AND soh.OrderDate BETWEEN '20030701'
AND '20030731'
GROUP BY ProductID
) AS src(ProductID, OrderQty)
ON pi.ProductID = src.ProductID
WHEN MATCHED
AND pi.Quantity - src.OrderQty >= 0
THEN
UPDATE SET pi.Quantity = pi.Quantity - src.OrderQty
WHEN MATCHED
AND pi.Quantity - src.OrderQty <= 0
THEN
DELETE
OUTPUT $action,
Inserted.ProductID,
Inserted.LocationID,
Inserted.Quantity AS NewQty,
Deleted.Quantity AS PreviousQty
) AS Changes(Action, ProductID, LocationID, NewQty, PreviousQty)
WHERE Action = 'UPDATE';
GO
E. 使用 MERGE 对图形数据库中的目标边缘表执行 INSERT 或 UPDATE 操作
在此示例中,创建节点表 Person 和 City 以及边缘表 livesIn。 如果使用MERGE边缘上的livesIn语句,并在边缘之间PersonCity尚不存在该行,则插入一个新行。 如果已有边缘,只需更新 livesIn 边缘上的 StreetAddress 属性。
-- CREATE node and edge tables
CREATE TABLE Person
(
ID INTEGER PRIMARY KEY,
PersonName VARCHAR(100)
)
AS NODE
GO
CREATE TABLE City
(
ID INTEGER PRIMARY KEY,
CityName VARCHAR(100),
StateName VARCHAR(100)
)
AS NODE
GO
CREATE TABLE livesIn
(
StreetAddress VARCHAR(100)
)
AS EDGE
GO
-- INSERT some test data into node and edge tables
INSERT INTO Person VALUES (1, 'Ron'), (2, 'David'), (3, 'Nancy')
GO
INSERT INTO City VALUES (1, 'Redmond', 'Washington'), (2, 'Seattle', 'Washington')
GO
INSERT livesIn SELECT P.$node_id, C.$node_id, c
FROM Person P, City C, (values (1,1, '123 Avenue'), (2,2,'Main Street')) v(a,b,c)
WHERE P.id = a AND C.id = b
GO
-- Use MERGE to update/insert edge data
CREATE OR ALTER PROCEDURE mergeEdge
@PersonId integer,
@CityId integer,
@StreetAddress varchar(100)
AS
BEGIN
MERGE livesIn
USING ((SELECT @PersonId, @CityId, @StreetAddress) AS T (PersonId, CityId, StreetAddress)
JOIN Person ON T.PersonId = Person.ID
JOIN City ON T.CityId = City.ID)
ON MATCH (Person-(livesIn)->City)
WHEN MATCHED THEN
UPDATE SET StreetAddress = @StreetAddress
WHEN NOT MATCHED THEN
INSERT ($from_id, $to_id, StreetAddress)
VALUES (Person.$node_id, City.$node_id, @StreetAddress) ;
END
GO
-- Following will insert a new edge in the livesIn edge table
EXEC mergeEdge 3, 2, '4444th Avenue'
GO
-- Following will update the StreetAddress on the edge that connects Ron to Redmond
EXEC mergeEdge 1, 1, '321 Avenue'
GO
-- Verify that all the address were added/updated correctly
SELECT PersonName, CityName, StreetAddress
FROM Person , City , livesIn
WHERE MATCH(Person-(livesIn)->city)
GO