T-SQL:复制层次结构数据的最佳方法?

时间:2010-10-04 11:01:17

标签: sql-server

我的数据库如下所示:

Questionnaire 
Id 
Description

Category
id
description
QuestionnaireId (FK)    

Question
id
CategoryId (FK)
field

当我复制问卷时,我想复制所有基础表格。所以这意味着表调查表得到一个新的Id。然后,还必须复制调查问卷的所有所有类别。因此,新插入的类别必须获得新的问卷ID。在类别之后,必须复制问题。但必须将categoryId更新为新插入的类别。

如何使用t-sql执行此操作?

3 个答案:

答案 0 :(得分:8)

这很容易实现,但你必须随时跟踪所有内容。我通常会为此创建一个SP,它将问卷作为输入进行复制。

  DECLARE @newQuestionnaireId INT
  INSERT INTO Questionnaire
  (Id,Description)
  SELECT Id, Description 
  FROM Questionnaire
  WHERE ID = @sourceQuestionnaireID
  SET @newquestionnaireId = SCOPE_IDENTITY()

此时您有一个新的标题记录,以及新生成的副本ID。下一步是将类别加载到临时表中,该临时表具有新Id的额外字段

DECLARE @tempCategories TABLE (id INT, description VARCHAR(50),newId INT)
INSERT INTO @tempCategories(id,description)
SELECT id, description FROM Category 
WHERE questionnaireId = @sourceQuestionnaireId

现在,您有一个临时表,其中包含要插入的所有类别,以及用于回填此类别的新ID的字段。使用光标覆盖插入新记录的列表,并使用类似的SCOPE_IDENTITY调用来回填新Id。

DECLARE cuCategory CURSOR FOR SELECT Id, Description FROM @tempCategories
DECLARE @catId INT, @catDescription, @newCatId INT
OPEN cuCategory
FETCH NEXT FROM cuCategory INTO @catId,@catDescription
WHILE @@FETCH_STATUS<>0
BEGIN
  INSERT INTO Category(description,questionnaireId)
  VALUES(@catDescription,@newQuestionnaireId)
  SET @newCatId = SCOPE_IDENTITY()

  UPDATE @tempCategories SET newCatId=@newCatId
  WHERE id=@catId
  FETCH NEXT FROM cuCategory INTO @catId,@catDescription
END
CLOSE cuCategory
DEALLOCATE cuCategory

此时您现在有一个临时表,它将catId从原始问卷映射到新问卷的catId。这可以用来填写决赛桌的方式大致相同 - 我将为你留下一个练习,但如果你有困难,可以随时回复。

最后,我建议整个操作在一次交易中进行,以便在出现问题时将您从半完成的副本中拯救出来。

一些免责声明:以上所有都是快速输入,不要指望它能够起作用。其次,我认为你所有的PK都是身份字段,它们应该是!如果他们不只是用适当的逻辑替换SCOPE_IDENTITY()调用来生成下一个ID。

修改:documentation for Cursor operations can be foundhere

答案 1 :(得分:2)

我有这样的问题,并开始实施@Jamiec建议的解决方案,但我很快意识到我需要一个更好的解决方案,因为我的模型比这里引用的例子大得多。我有一个包含三个中间表的主表,每个表都有一个或多个第三表。这三种中间体各有50列。这将意味着要输入所有内容的大量工作,特别是在带有临时memvars的获取部分中。我试图找到一种方法将FETCH直接导入临时表,但似乎你不能这样做。 我所做的是在名为OriginalId的中间表中添加一列。这是我的代码翻译成提问者使用的模型:

DECLARE @newQuestionnaireId INT
INSERT INTO Questionnaire (Id,Description)
SELECT Id, Description FROM Questionnaire
WHERE ID = @sourceQuestionnaireID
SET @newquestionnaireId = SCOPE_IDENTITY()

INSERT INTO Category(QuestionnaireId, description, originalId)
SELECT @newquestionnaireId, description, id FROM Category 
WHERE questionnaireId = @sourceQuestionnaireId

INSERT INTO Question SELECT Category.Id, Question.Field
FROM Question join Category on Question.CategoryId = Category.OriginalId
WHERE Category.QuestionnaireId = @newquestionnaireId

在我的模型中,id字段都是Identities,因此您不会在插入中提供它们。

我在放弃CURSOR方法之前发现的另一件事是this clever little trick,以避免必须使用带有BREAK的无限WHILE循环来输入两次FETCH语句:

答案 2 :(得分:1)

这是一种没有游标的方法,它依赖于记住事件的顺序,然后使用它来解决孩子。

Declare @Parrent TABLE( ID int PRIMARY KEY IDENTITY, Value nvarchar(50))
Declare @Child TABLE( ID int PRIMARY KEY IDENTITY, ParrentID int, Value nvarchar(50))

insert into @Parrent (Value) Values ('foo'),('bar'),('bob')
insert into @Child (ParrentID, Value) Values (1,'foo-1'),(1,'foo-2'),(2,'bar-1'),(2,'bar-2'),(3,'bob')

declare @parrentToCopy table (ID int) -- you can me this a collection
insert into @parrentToCopy values (2)

select * from @Parrent p inner join @Child c on p.ID = c.ParrentID order by p.ID asc, c.ID asc

DECLARE @Ids TABLE( nID INT);

INSERT INTO @Parrent (Value)
OUTPUT INSERTED.ID
INTO @Ids
SELECT
        Value
FROM @Parrent p
inner join @parrentToCopy pc on pc.ID=p.ID
ORDER BY p.ID ASC

INSERT INTO @Child (ParrentID, Value)
SELECT
       nID
       ,Value
FROM @Child c
inner join (select ID, ROW_NUMBER() OVER (ORDER BY ID ASC) AS 'RowNumber' from @parrentToCopy) o ON o.ID = c.ParrentID
inner join (select nID, ROW_NUMBER() OVER (ORDER BY nID ASC) AS 'RowNumber' from @Ids) n ON o.RowNumber = n.RowNumber

select * from @Parrent p inner join @Child c on p.ID = c.ParrentID order by p.ID asc, c.ID asc

完整帖子在这里http://bashamer.wordpress.com/2011/10/04/copying-hierarchical-data-in-sql-server/

相关问题