在mssql中复制多级相关表

时间:2011-10-31 14:45:47

标签: sql-server sql-server-2008 tsql

我有4个相关的表,每个表与下一个表有一个1:N的关系,例如

One (OneID pk)
Two (TwoID pk, OneID fk)
Three (ThreeID pk, TwoID fk)
Four (FourID pk, ThreeID fk)

我需要实现当用户想要复制“一个”中的记录以及表二,三和四中的所有相关记录时的功能。

从前端完成此操作,以便用户可以在现有记录上建立新记录。做这个的最好方式是什么?我有新插入的'OneID'和原始的'OneID'。

我想到这样做的一种方法是为每个表都有一个'Copy'存储过程,每个表都有一个调用它的子表的游标为每一行复制SP一次。

我想到的唯一另一种方法就是拥有一个临时表,其中包含每个表的原始+新ID的记录,但这看起来很混乱,并且可能会失控。

有什么建议吗?

3 个答案:

答案 0 :(得分:5)

如果您的PK是IDENTITY列,则可以使用this question中描述的涉及MERGE的技术。

以下是整个过程的编写方式:

DECLARE @OldID int, @NewID int;
SET @OldID = some_value;

DECLARE @TwoMapping TABLE (OldID int, NewID int);
DECLARE @ThreeMapping TABLE (OldID int, NewID int);

INSERT INTO One
SELECT columns
FROM One
WHERE OneID = @OldID;
SET @NewID = SCOPE_IDENTITY();
/*
That one was simple: one row is copied, so just reading SCOPE_IDENTITY()
after the INSERT. The actual mapping technique starts at this point.
*/

MERGE Two tgt
USING (
  SELECT
    @NewID AS OneID,
    other columns
  FROM Two t
  WHERE OneID = @OldID
) src
ON 0 = 1
WHEN NOT MATCHED THEN
  INSERT (columns) VALUES (src.columns)
OUTPUT src.TwoID, INSERTED.TwoID INTO @TwoMapping (OldID, NewID);
/*
As you can see, MERGE allows us to reference the source table in the
OUTPUT clause, in addition to the pseudo-tables INSERTED and DELETED,
and that is a great advantage over INSERT and the core of the method.
*/

MERGE Three tgt
USING (
  SELECT
    map.NewID AS TwoID,
    t.other columns
  FROM Three t
    INNER JOIN @TwoMapping map ON t.TwoID = map.OldID
) src
ON 0 = 1
WHEN NOT MATCHED THEN
  INSERT (columns) VALUES (src.columns)
OUTPUT src.ThreeID, INSERTED.ThreeID INTO @ThreeMapping (OldID, NewID);
/*
Now that we've got a mapping table, we can easily substitute new FKs for the old
ones with a simple join. The same is repeated once again in the following MERGE.
*/

MERGE Four tgt
USING (
  SELECT
    map.NewID AS ThreeID,
    t.columns
  FROM Four t
    INNER JOIN @ThreeMapping map ON t.ThreeID = map.OldID
) src
ON 0 = 1
WHEN NOT MATCHED THEN
  INSERT (columns) VALUES (src.columns);
/*
The Four table is the last one in the chain of dependencies, so the last MERGE
has no OUTPUT clause. But if there were a Five table, we would go on like above.
*/

或者你可能不得不使用游标,这似乎是在SQL Server 2005和早期版本中执行此操作的唯一(理智)方式。

答案 1 :(得分:0)

我过去必须为大量数据做这件事。我找到了为每个表使用存储过程,临时表和GUID列的最佳方法。在我的例子中,我们有一个存储过程,它为所有涉及的表执行了所有复制,但如果您愿意,可以为每个表执行一次。我们创建了一组临时表,它们是我们要复制的所有表的精确副本,但是在不同的模式中并且没有键。复制时,我们将所有记录的副本插入临时表中。然后,我们从临时表插入到dbo表中。我们的想法是首先插入没有FK的记录(这些是顶级项目)。然后,在临时区域中,任何引用刚刚插入的顶级记录的记录都会在临时表中更新其FK字段,然后插入到dbo中。您会发现GUID列的原因是,为了更新foriegn键,这是将复制的记录绑定回原始记录的唯一方法。如果您有4条记录都通过foriegn密钥关系绑定在一起,那么您希望所有复制的记录以相同的方式绑定在一起。唯一的方法是以某种方式跟踪原件的ID和副本的ID,并相应地更新它们。如果您要进行批量插入(就像我们一样),那么GUID列是我们找到的唯一解决方案,但如果所有插入都是单个记录,那么表中可能不需要GUID列。以下是使用GUID列快速了解它的用法:

-- copy data from dbo to temp schema
INSERT temp.One (field1, field2, guid, etc)
SELECT field1, field2, guid, etc
  FROM dbo.One
 WHERE OneID = @OneID

INSERT temp.Two (field1, field2, guid, etc)
SELECT field1, field2, guid, etc
  FROM dbo.Two t
 INNER JOIN temp.One o ON o.OneID = t.OneID

...

-- update GUIDs in temp area
UPDATE temp.One
   SET guid = NEWID()

UPDATE temp.Two
   SET guid = NEWID()

...

-- insert from temp to dbo
INSERT dbo.One (field1, field2, guid, etc)
SELECT field1, field2, guid, etc
  FROM temp.One

-- need to update FK here before inserting to dbo, join from temp to dbo on GUID
UPDATE temp.Two
   SET OneID = c.OneID
  FROM temp.Two t
 INNER JOIN temp.One o ON t.OneID = o.OneID
 INNER JOIN dbo.One c ON c.GUID = o.GUID

INSERT dbo.Two (field1, field2, guid, etc)
SELECT field1, field2, guid, etc
  FROM temp.Two

...

答案 2 :(得分:0)

你基本上只需要一个表来映射你的旧/新值,一个物理表,如果你想保留副本的记录,一个临时表,如果你没有。

-- Create Tables
    CREATE TABLE #one (oneid UNIQUEIDENTIFIER)
    CREATE TABLE #two (twoid UNIQUEIDENTIFIER, oneid UNIQUEIDENTIFIER)
    CREATE TABLE #three (threeid UNIQUEIDENTIFIER, twoid UNIQUEIDENTIFIER)
    CREATE TABLE #four (fourid UNIQUEIDENTIFIER, threeid UNIQUEIDENTIFIER)

-- Insert test data
    DECLARE @guid UNIQUEIDENTIFIER
    SET @guid = newid()
    insert #one values (@guid)

    INSERT #two select NEWID(), oneid from #one
    INSERT #two select NEWID(), oneid from #one
    INSERT #two select NEWID(), oneid from #one

    INSERT #three SELECT NEWID(), twoid FROM #two WHERE oneid = @GUID
    INSERT #three SELECT NEWID(), twoid FROM #two WHERE oneid = @GUID
    INSERT #three SELECT NEWID(), twoid FROM #two WHERE oneid = @GUID

    INSERT #four SELECT NEWID(), threeid FROM #three WHERE twoid IN (SELECT twoid FROM #two WHERE oneid = @GUID)
    INSERT #four SELECT NEWID(), threeid FROM #three WHERE twoid IN (SELECT twoid FROM #two WHERE oneid = @GUID)
    INSERT #four SELECT NEWID(), threeid FROM #three WHERE twoid IN (SELECT twoid FROM #two WHERE oneid = @GUID)


-- Create temp tables
    CREATE TABLE #tempone (oneid UNIQUEIDENTIFIER, oldval UNIQUEIDENTIFIER)
    CREATE TABLE #temptwo (twoid UNIQUEIDENTIFIER, oneid UNIQUEIDENTIFIER, oldval UNIQUEIDENTIFIER)
    CREATE TABLE #tempthree (threeid UNIQUEIDENTIFIER, twoid UNIQUEIDENTIFIER, oldval UNIQUEIDENTIFIER)
    CREATE TABLE #tempfour (fourid UNIQUEIDENTIFIER, threeid UNIQUEIDENTIFIER, oldval UNIQUEIDENTIFIER)

    INSERT #tempone SELECT NEWID(), oneid FROM #one WHERE oneid = @guid
    INSERT #temptwo SELECT NEWID(), #tempone.oneid, #two.twoid FROM #two JOIN #tempone ON #two.oneid = #tempone.oldval
    INSERT #tempthree SELECT NEWID(), #temptwo.twoid, #three.threeid FROM #three JOIN #temptwo ON #three.twoid = #temptwo.oldval
    INSERT #tempfour SELECT NEWID(), #tempthree.threeid, #four.fourid FROM #four JOIN #tempthree ON #four.threeid = #tempthree.oldval

-- INSERT results
    INSERT #one SELECT t.oneid /*#one.column_list*/ FROM #tempone t JOIN #one oldT ON t.oldval = oldT.oneid
    INSERT #two SELECT t.twoid, t.oneid /*#two.column_list*/ FROM #temptwo t JOIN #two oldT ON t.oldval = oldT.twoid
    INSERT #three SELECT t.threeid, t.twoid /*#three.column_list*/ FROM #tempthree t JOIN #three oldT ON t.oldval = oldT.threeid    
    INSERT #four SELECT t.fourid, t.threeid /*#four.column_list*/ FROM #tempfour t JOIN #four oldT ON t.oldval = oldT.fourid    

-- View Results 
    SELECT one.oneid, two.twoid, three.threeid, four.fourid
    FROM #one one
    JOIN #two two ON one.oneid = two.oneid
    JOIN #three three on three.twoid = two.twoid
    JOIN #four four on four.threeid = three.threeid
    ORDER BY one.oneid, two.twoid, three.threeid, four.fourid