我有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的记录,但这看起来很混乱,并且可能会失控。
有什么建议吗?
答案 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