2010-10-04 11 views
5

Mi base de datos es el siguiente:T-SQL: ¿la mejor manera de copiar datos de jerarquía?

Questionnaire 
Id 
Description 

Category 
id 
description 
QuestionnaireId (FK)  

Question 
id 
CategoryId (FK) 
field 

Cuando copio un cuestionario, me gustaría copiar todas las tablas subyacentes. Entonces, esto significa que el Cuestionario de la tabla obtiene un Id nuevo. Luego, todas las categorías de pertenencia del cuestionario también deben copiarse. Entonces las categorías recién insertadas deben obtener el nuevo cuestionario Id. Después de las categorías, las preguntas deben ser copiadas. Pero la categoría Id debe actualizarse a la categoría recién insertada.

¿Cómo puedo hacer esto usando t-sql?

+3

Realmente no ver el problema aquí ... sólo tienes que copiar los datos e insertar el uso de 'SET_IDENTITY_INSERT [Tabla A] ON' mismo para B y C. Si – Jamiec

+0

inserte un registro en la tabla A, este registro obtenga una ID, digamos 104. Entonces, tengo que insertar una nueva fila en la tabla B, que tiene una referencia a la tabla A, por lo que table_A_id debe ser el mismo: 104 – Martijn

+1

No debería ser un problema, siempre que esté copiando a donde sea que esté copiando, ya que los nuevos datos tendrán las mismas PK usando IDENTITY_INSERT. ¿A dónde estás tratando de copiar? ¿Otra base de datos idéntica quizás? – Jamiec

Respuesta

5

Esto es bastante fácil de lograr, pero debe hacer un seguimiento de todo sobre la marcha. Por lo general, creo un solo SP para esto, que toma como entrada el cuestionario para copiar.

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

En este punto, tiene un nuevo registro de encabezado y el Id generado recientemente para la copia. El siguiente paso es cargar las categorías en una tabla temporal que tiene un campo adicional para el nuevo Id

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

Ahora, usted tiene una tabla temporal con todas las categorías de insertar, junto con un campo para rellenar el nuevo ID para esta categoría Use un cursor para recorrer la lista que inserta el nuevo registro, y use una llamada SCOPE_IDENTITY similar para rellenar el nuevo 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 [email protected] 
    WHERE [email protected] 
    FETCH NEXT FROM cuCategory INTO @catId,@catDescription 
END 
CLOSE cuCategory 
DEALLOCATE cuCategory 

En este punto se tienen ahora una tabla temporal que mapea la catId del cuestionario original a la catId para el nuevo cuestionario. Esto puede usarse para llenar la mesa final de la misma manera, que dejaré como ejercicio para ti, pero no dudes en publicar aquí si tienes dificultades.

Por último, sugiero que toda esta operación se lleve a cabo dentro de una transacción para salvarlo de la mitad de las copias completadas cuando algo sale mal.

Un par de descargos de responsabilidad: todo lo anterior se escribió rápidamente, no espere que funcione sin problemas. En segundo lugar, he supuesto que todos sus PK son campos de identidad, ¡lo que deberían ser! Si no son simplemente, reemplace las llamadas SCOPE_IDENTITY() con la lógica apropiada para generar el siguiente ID.

Editar: documentation for Cursor operations can be foundhere

+0

Gracias. Esto es de hecho lo que necesito. Veo muchas cosas nuevas con las que nunca he trabajado: CURSOR, FETCH NEXT FROM, FETCH_STATUS. ¿Puedes explicar estas cosas un poco? – Martijn

+3

Prácticamente he escrito la respuesta para usted y no puede escribir esas pocas frases en Google? Sheesh. – Jamiec

+0

Creo que lo entiendo :) ¡Thnx! – Martijn

2

tuve un problema como este y comenzó a poner en práctica la solución sugerida por @Jamiec pero rápidamente me di cuenta de que necesitaba una mejor solución, porque mi modelo es mucho mayor que en el ejemplo citado aquí . Tengo una tabla maestra con tres tablas intermedias, cada una de las cuales tiene una o más tablas terciarias. Y los tres intermedios tenían algo así como 50 columnas. Esto significaría mucho trabajo escribir todo eso, particularmente en la parte de búsqueda con los memvars temporales. Traté de encontrar una manera de FETCH directamente en la tabla temporal, pero parece que no puedes hacer eso. Lo que hice fue agregar una columna a las tablas intermedias llamada OriginalId. Aquí está mi código traducido en el modelo utilizado por el autor de la pregunta:

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 

En mi modelo de los campos id son todas las identidades por lo que no les hayan proporcionado en los insertos.

Otra cosa que descubrí antes de que me di por vencido en el enfoque cursor estaba this clever little trick para evitar tener que escribir la sentencia FETCH dos veces mediante el uso de un bucle WHILE infinita con una pausa:

1

aquí es una forma que no tiene cursores , se basa en recordar el orden de los eventos, y luego usar eso para resolver los niños.

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 

post completo está aquí http://bashamer.wordpress.com/2011/10/04/copying-hierarchical-data-in-sql-server/

Cuestiones relacionadas