Sql server 使用merge..output获取source.id和target.id之间的映射
非常简单,我有两个表源和目标Sql server 使用merge..output获取source.id和target.id之间的映射,sql-server,sql-server-2008,merge,Sql Server,Sql Server 2008,Merge,非常简单,我有两个表源和目标 declare @Source table (SourceID int identity(1,2), SourceName varchar(50)) declare @Target table (TargetID int identity(2,2), TargetName varchar(50)) insert into @Source values ('Row 1'), ('Row 2') 我想将所有行从@Source移动到@Target,并了解每个Sourc
declare @Source table (SourceID int identity(1,2), SourceName varchar(50))
declare @Target table (TargetID int identity(2,2), TargetName varchar(50))
insert into @Source values ('Row 1'), ('Row 2')
我想将所有行从@Source
移动到@Target
,并了解每个SourceID
的TargetID
,因为还有需要复制的表SourceChild
和TargetChild
,我需要将新的TargetID
添加到TargetChild.TargetID
FK列
有几种解决方案
scope\u identity()
填充TargetChild
的FK李>
@Target
并插入SourceID
。然后,您可以加入该列以获取TargetChild
中FK的TargetID
设置标识\u为@Target
插入OFF
,并自行处理分配新值的操作。您将获得一个范围,然后在TargetChild.TargetID
中使用该范围output
子句
insert into @Target(TargetName)
output inserted.TargetID, S.SourceID
select SourceName
from @Source as S
但这是不可能的
The multi-part identifier "S.SourceID" could not be bound.
但合并是可能的
merge @Target as T
using @Source as S
on 0=1
when not matched then
insert (TargetName) values (SourceName)
output inserted.TargetID, S.SourceID;
结果
TargetID SourceID
----------- -----------
2 1
4 3
我想知道你是否用过这个?如果您对解决方案有任何想法或发现任何问题?它在简单的场景中工作得很好,但是当查询计划由于复杂的源查询而变得非常复杂时,可能会发生一些丑陋的事情。最糟糕的情况是TargetID/SourceID对实际上不匹配
MSDN对该子句的from_table_name
进行了说明
列前缀,用于指定DELETE、UPDATE或MERGE语句的FROM子句中包含的表,该语句用于指定要更新或删除的行
出于某种原因,他们不会说“要插入、更新或删除的行”,而只说“要更新或删除的行”
任何想法都是受欢迎的,对于原始问题的完全不同的解决方案都是非常受欢迎的 在我看来,这是合并和输出的一个很好的用途。我已经在几个场景中使用过,到目前为止还没有遇到任何奇怪的情况。 例如,下面是一个测试设置,它将文件夹和其中的所有文件(标识)克隆到新创建的文件夹(guid)中
我想在@Nathan的示例中添加另一个示例,因为我发现它有些混乱 我的大部分使用真实的表格,而不是临时表格 我也从这里得到了灵感:
这里有一个不使用MERGE的解决方案(如果可能的话,我多次尝试避免使用MERGE)。它依赖于两个具有匹配标识列的内存表(如果需要,可以使用临时表),重要的是,在执行插入时使用ORDER BY,以及两个插入之间匹配的条件。。。第一个保存源ID,第二个保存目标ID
-- Setup... We have a table that we need to know the old IDs and new IDs after copying.
-- We want to copy all of DocID=1
DECLARE @newDocID int = 99;
DECLARE @tbl table (RuleID int PRIMARY KEY NOT NULL IDENTITY(1, 1), DocID int, Val varchar(100));
INSERT INTO @tbl (DocID, Val) VALUES (1, 'RuleA-2'), (1, 'RuleA-1'), (2, 'RuleB-1'), (2, 'RuleB-2'), (3, 'RuleC-1'), (1, 'RuleA-3')
-- Create a break in IDENTITY values.. just to simulate more realistic data
INSERT INTO @tbl (Val) VALUES ('DeleteMe'), ('DeleteMe');
DELETE FROM @tbl WHERE Val = 'DeleteMe';
INSERT INTO @tbl (DocID, Val) VALUES (6, 'RuleE'), (7, 'RuleF');
SELECT * FROM @tbl t;
-- Declare TWO temp tables each with an IDENTITY - one will hold the RuleID of the items we are copying, other will hold the RuleID that we create
DECLARE @input table (RID int IDENTITY(1, 1), SourceRuleID int NOT NULL, Val varchar(100));
DECLARE @output table (RID int IDENTITY(1,1), TargetRuleID int NOT NULL, Val varchar(100));
-- Capture the IDs of the rows we will be copying by inserting them into the @input table
-- Important - we must specify the sort order - best thing is to use the IDENTITY of the source table (t.RuleID) that we are copying
INSERT INTO @input (SourceRuleID, Val) SELECT t.RuleID, t.Val FROM @tbl t WHERE t.DocID = 1 ORDER BY t.RuleID;
-- Copy the rows, and use the OUTPUT clause to capture the IDs of the inserted rows.
-- Important - we must use the same WHERE and ORDER BY clauses as above
INSERT INTO @tbl (DocID, Val)
OUTPUT Inserted.RuleID, Inserted.Val INTO @output(TargetRuleID, Val)
SELECT @newDocID, t.Val FROM @tbl t
WHERE t.DocID = 1
ORDER BY t.RuleID;
-- Now @input and @output should have the same # of rows, and the order of both inserts was the same, so the IDENTITY columns (RID) can be matched
-- Use this as the map from old-to-new when you are copying sub-table rows
-- Technically, @input and @output don't even need the 'Val' columns, just RID and RuleID - they were included here to prove that the rules matched
SELECT i.*, o.* FROM @output o
INNER JOIN @input i ON i.RID = o.RID
-- Confirm the matching worked
SELECT * FROM @tbl t
他们没有提到“insert”的原因是因为from_table_名称在insert into/output语句中无效,“deleted”前缀也是无效的(因为不能通过insert更改现有数据)。顺便说一句:Adam Machanic关于合并功能的博文太棒了!解决了我的问题。谢谢马丁·史密斯的发帖。希望我能给Adam Machanic文章提供更多有趣的+1替代链接,但要实现复制一行和一组子行的简单任务,这似乎异常复杂。。。光标不是更容易理解吗?
-- Copy the FormSectionInstance
DECLARE @FormSectionInstanceTable TABLE(OldFormSectionInstanceId INT, NewFormSectionInstanceId INT)
;MERGE INTO [dbo].[FormSectionInstance]
USING
(
SELECT
fsi.FormSectionInstanceId [OldFormSectionInstanceId]
, @NewFormHeaderId [NewFormHeaderId]
, fsi.FormSectionId
, fsi.IsClone
, @UserId [NewCreatedByUserId]
, GETDATE() NewCreatedDate
, @UserId [NewUpdatedByUserId]
, GETDATE() NewUpdatedDate
FROM [dbo].[FormSectionInstance] fsi
WHERE fsi.[FormHeaderId] = @FormHeaderId
) tblSource ON 1=0 -- use always false condition
WHEN NOT MATCHED
THEN INSERT
( [FormHeaderId], FormSectionId, IsClone, CreatedByUserId, CreatedDate, UpdatedByUserId, UpdatedDate)
VALUES( [NewFormHeaderId], FormSectionId, IsClone, NewCreatedByUserId, NewCreatedDate, NewUpdatedByUserId, NewUpdatedDate)
OUTPUT tblSource.[OldFormSectionInstanceId], INSERTED.FormSectionInstanceId
INTO @FormSectionInstanceTable(OldFormSectionInstanceId, NewFormSectionInstanceId);
-- Copy the FormDetail
INSERT INTO [dbo].[FormDetail]
(FormHeaderId, FormFieldId, FormSectionInstanceId, IsOther, Value, CreatedByUserId, CreatedDate, UpdatedByUserId, UpdatedDate)
SELECT
@NewFormHeaderId, FormFieldId, fsit.NewFormSectionInstanceId, IsOther, Value, @UserId, CreatedDate, @UserId, UpdatedDate
FROM [dbo].[FormDetail] fd
INNER JOIN @FormSectionInstanceTable fsit ON fsit.OldFormSectionInstanceId = fd.FormSectionInstanceId
WHERE [FormHeaderId] = @FormHeaderId
-- Setup... We have a table that we need to know the old IDs and new IDs after copying.
-- We want to copy all of DocID=1
DECLARE @newDocID int = 99;
DECLARE @tbl table (RuleID int PRIMARY KEY NOT NULL IDENTITY(1, 1), DocID int, Val varchar(100));
INSERT INTO @tbl (DocID, Val) VALUES (1, 'RuleA-2'), (1, 'RuleA-1'), (2, 'RuleB-1'), (2, 'RuleB-2'), (3, 'RuleC-1'), (1, 'RuleA-3')
-- Create a break in IDENTITY values.. just to simulate more realistic data
INSERT INTO @tbl (Val) VALUES ('DeleteMe'), ('DeleteMe');
DELETE FROM @tbl WHERE Val = 'DeleteMe';
INSERT INTO @tbl (DocID, Val) VALUES (6, 'RuleE'), (7, 'RuleF');
SELECT * FROM @tbl t;
-- Declare TWO temp tables each with an IDENTITY - one will hold the RuleID of the items we are copying, other will hold the RuleID that we create
DECLARE @input table (RID int IDENTITY(1, 1), SourceRuleID int NOT NULL, Val varchar(100));
DECLARE @output table (RID int IDENTITY(1,1), TargetRuleID int NOT NULL, Val varchar(100));
-- Capture the IDs of the rows we will be copying by inserting them into the @input table
-- Important - we must specify the sort order - best thing is to use the IDENTITY of the source table (t.RuleID) that we are copying
INSERT INTO @input (SourceRuleID, Val) SELECT t.RuleID, t.Val FROM @tbl t WHERE t.DocID = 1 ORDER BY t.RuleID;
-- Copy the rows, and use the OUTPUT clause to capture the IDs of the inserted rows.
-- Important - we must use the same WHERE and ORDER BY clauses as above
INSERT INTO @tbl (DocID, Val)
OUTPUT Inserted.RuleID, Inserted.Val INTO @output(TargetRuleID, Val)
SELECT @newDocID, t.Val FROM @tbl t
WHERE t.DocID = 1
ORDER BY t.RuleID;
-- Now @input and @output should have the same # of rows, and the order of both inserts was the same, so the IDENTITY columns (RID) can be matched
-- Use this as the map from old-to-new when you are copying sub-table rows
-- Technically, @input and @output don't even need the 'Val' columns, just RID and RuleID - they were included here to prove that the rules matched
SELECT i.*, o.* FROM @output o
INNER JOIN @input i ON i.RID = o.RID
-- Confirm the matching worked
SELECT * FROM @tbl t