我的理解是,主要障碍是OUTPUT
SQL Server 中子句的限制。它允许一个OUTPUT INTO table
和/或一个OUTPUT
将结果集返回给调用者。
您想以MERGE
两种不同的方式保存语句的结果:
MERGE
为收集统计信息而受影响的所有行
- 仅插入行
queue
简单变体
我会使用你的 S2 解决方案。至少开始。它易于理解和维护,并且应该非常高效,因为最耗费资源的操作(MERGE
对其Target
自身只会执行一次)。下面还有第二个变体,比较它们在真实数据上的表现会很有趣。
所以:
- 使用
OUTPUT INTO @TempTable
在MERGE
- 在插入之前从 into或聚合
INSERT
的所有行。如果您只需要聚合统计信息,那么聚合该批次的结果并将其合并到最终结果中而不是复制所有行是有意义的。@TempTable
Stats
Stats
INSERT
Queue
仅从@TempTable
. _
我将从@i-one 的答案中获取样本数据。
架构
-- I'll return to commented lines later
CREATE TABLE [dbo].[TestTarget](
-- [ID] [int] IDENTITY(1,1) NOT NULL,
[foo] [varchar](10) NULL,
[bar] [varchar](10) NULL
);
CREATE TABLE [dbo].[TestStaging](
[foo] [varchar](10) NULL,
[bar] [varchar](10) NULL,
[baz] [varchar](10) NULL
);
CREATE TABLE [dbo].[TestStats](
[MergeAction] [nvarchar](10) NOT NULL
);
CREATE TABLE [dbo].[TestQueue](
-- [TargetID] [int] NOT NULL,
[foo] [varchar](10) NULL,
[baz] [varchar](10) NULL
);
样本数据
TRUNCATE TABLE [dbo].[TestTarget];
TRUNCATE TABLE [dbo].[TestStaging];
TRUNCATE TABLE [dbo].[TestStats];
TRUNCATE TABLE [dbo].[TestQueue];
INSERT INTO [dbo].[TestStaging]
([foo]
,[bar]
,[baz])
VALUES
('A', 'AA', 'AAA'),
('B', 'BB', 'BBB'),
('C', 'CC', 'CCC');
INSERT INTO [dbo].[TestTarget]
([foo]
,[bar])
VALUES
('A', 'A_'),
('B', 'B?');
合并
DECLARE @TempTable TABLE (
MergeAction nvarchar(10) NOT NULL,
foo varchar(10) NULL,
baz varchar(10) NULL);
MERGE INTO TestTarget AS Dst
USING TestStaging AS Src
ON Dst.foo = Src.foo
WHEN MATCHED THEN
UPDATE SET
Dst.bar = Src.bar
WHEN NOT MATCHED BY TARGET THEN
INSERT (foo, bar)
VALUES (Src.foo, Src.bar)
OUTPUT $action AS MergeAction, inserted.foo, Src.baz
INTO @TempTable(MergeAction, foo, baz)
;
INSERT INTO [dbo].[TestStats] (MergeAction)
SELECT T.MergeAction
FROM @TempTable AS T;
INSERT INTO [dbo].[TestQueue]
([foo]
,[baz])
SELECT
T.foo
,T.baz
FROM @TempTable AS T
WHERE T.MergeAction = 'INSERT'
;
SELECT * FROM [dbo].[TestTarget];
SELECT * FROM [dbo].[TestStats];
SELECT * FROM [dbo].[TestQueue];
结果
TestTarget
+-----+-----+
| foo | bar |
+-----+-----+
| A | AA |
| B | BB |
| C | CC |
+-----+-----+
TestStats
+-------------+
| MergeAction |
+-------------+
| INSERT |
| UPDATE |
| UPDATE |
+-------------+
TestQueue
+-----+-----+
| foo | baz |
+-----+-----+
| C | CCC |
+-----+-----+
第二种变体
在 SQL Server 2014 Express 上测试。
OUTPUT
子句可以将其结果集发送到表和调用者。因此,OUTPUT INTO
可以直接进入Stats
,如果我们将语句包装MERGE
到存储过程中,那么我们可以使用INSERT ... EXEC
进入Queue
.
如果您检查执行计划,您会发现INSERT ... EXEC
无论如何都会在幕后创建一个临时表(另请参阅Adam Machanic的 INSERT EXEC 的隐藏成本),因此我希望在创建临时表时整体性能与第一个变体相似明确地。
还有一个要解决的问题:Queue
表应该只有“插入”的行,而不是所有受影响的行。为此,您可以在Queue
表上使用触发器来丢弃“插入”以外的行。另一种可能性是定义一个唯一索引IGNORE_DUP_KEY = ON
并以这样一种方式准备数据,即“未插入”的行将违反唯一索引并且不会插入到表中。
因此,我将ID IDENTITY
在Target
表格中添加一TargetID
列,然后在表格中添加一列Queue
。(在上面的脚本中取消注释它们)。另外,我将在Queue
表中添加一个索引:
CREATE UNIQUE NONCLUSTERED INDEX [IX_TargetID] ON [dbo].[TestQueue]
(
[TargetID] ASC
) WITH (
PAD_INDEX = OFF,
STATISTICS_NORECOMPUTE = OFF,
SORT_IN_TEMPDB = OFF,
IGNORE_DUP_KEY = ON,
DROP_EXISTING = OFF,
ONLINE = OFF,
ALLOW_ROW_LOCKS = ON,
ALLOW_PAGE_LOCKS = ON)
重要的部分是UNIQUE
和IGNORE_DUP_KEY = ON
。
这是 的存储过程MERGE
:
CREATE PROCEDURE [dbo].[TestMerge]
AS
BEGIN
SET NOCOUNT ON;
SET XACT_ABORT ON;
MERGE INTO dbo.TestTarget AS Dst
USING dbo.TestStaging AS Src
ON Dst.foo = Src.foo
WHEN MATCHED THEN
UPDATE SET
Dst.bar = Src.bar
WHEN NOT MATCHED BY TARGET THEN
INSERT (foo, bar)
VALUES (Src.foo, Src.bar)
OUTPUT $action INTO dbo.TestStats(MergeAction)
OUTPUT CASE WHEN $action = 'INSERT' THEN inserted.ID ELSE 0 END AS TargetID,
inserted.foo,
Src.baz
;
END
用法
TRUNCATE TABLE [dbo].[TestTarget];
TRUNCATE TABLE [dbo].[TestStaging];
TRUNCATE TABLE [dbo].[TestStats];
TRUNCATE TABLE [dbo].[TestQueue];
-- Make sure that `Queue` has one special row with TargetID=0 in advance.
INSERT INTO [dbo].[TestQueue]
([TargetID]
,[foo]
,[baz])
VALUES
(0
,NULL
,NULL);
INSERT INTO [dbo].[TestStaging]
([foo]
,[bar]
,[baz])
VALUES
('A', 'AA', 'AAA'),
('B', 'BB', 'BBB'),
('C', 'CC', 'CCC');
INSERT INTO [dbo].[TestTarget]
([foo]
,[bar])
VALUES
('A', 'A_'),
('B', 'B?');
INSERT INTO [dbo].[TestQueue]
EXEC [dbo].[TestMerge];
SELECT * FROM [dbo].[TestTarget];
SELECT * FROM [dbo].[TestStats];
SELECT * FROM [dbo].[TestQueue];
结果
TestTarget
+----+-----+-----+
| ID | foo | bar |
+----+-----+-----+
| 1 | A | AA |
| 2 | B | BB |
| 3 | C | CC |
+----+-----+-----+
TestStats
+-------------+
| MergeAction |
+-------------+
| INSERT |
| UPDATE |
| UPDATE |
+-------------+
TestQueue
+----------+------+------+
| TargetID | foo | baz |
+----------+------+------+
| 0 | NULL | NULL |
| 3 | C | CCC |
+----------+------+------+
期间会有额外的消息INSERT ... EXEC
:
Duplicate key was ignored.
如果MERGE
更新了一些行。INSERT
当唯一索引在到期期间丢弃某些行时发送此警告消息IGNORE_DUP_KEY = ON
。
将重复的键值插入唯一索引时会出现警告消息。只有违反唯一性约束的行才会失败。