Warning: file_get_contents(/data/phpspider/zhask/data//catemap/7/sql-server/21.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Sql server 使用merge..output获取source.id和target.id之间的映射_Sql Server_Sql Server 2008_Merge - Fatal编程技术网

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列

有几种解决方案

  • 使用while循环或游标一次向目标插入一行(RBAR),并使用
    scope\u identity()
    填充
    TargetChild
    的FK
  • 将临时列添加到
    @Target
    并插入
    SourceID
    。然后,您可以加入该列以获取
    TargetChild
    中FK的
    TargetID
  • 设置标识\u为
    @Target
    插入OFF
    ,并自行处理分配新值的操作。您将获得一个范围,然后在
    TargetChild.TargetID
    中使用该范围
  • 我不太喜欢他们中的任何一个。到目前为止,我使用的是游标

    我真正想做的是使用insert语句的
    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