SQL Server: ¿es posible insertar en dos tablas al mismo tiempo?

143

Mi base de datos contiene tres tablas llamadas Object_Table, Data_Tabley Link_Table. La tabla de enlaces solo contiene dos columnas, la identidad de un registro de objeto y la identidad de un registro de datos.

Quiero copiar los datos desde DATA_TABLEdonde están vinculados a una identidad de objeto dada e insertar registros correspondientes en Data_Tabley Link_Tablepara una identidad de objeto dada diferente.

Yo puedo hacer esto mediante la selección en una variable de tabla y el bucle a través de hacer dos inserciones para cada iteración.

¿Es esta la mejor manera de hacerlo?

Editar : Quiero evitar un bucle por dos razones, la primera es que soy vago y una tabla de bucle / temp requiere más código, más código significa más lugares para cometer un error y la segunda razón es una preocupación por el rendimiento.

Puedo copiar todos los datos en una inserción, pero ¿cómo hago para que la tabla de enlaces se vincule a los nuevos registros de datos donde cada registro tiene una nueva identificación?

tpower
fuente
No me interesa tratar de hacerlo con UN inserto, cuando hacerlo con 2 insertos funciona perfectamente bien. ¿Quiere decir que quiere asegurarse de que las 2 inserciones estén completadas? Luego tendrá que verificar esta instrucción de confirmación / reversión.
Philippe Grondier el
2
Estaría contento con dos inserciones, es solo que las identidades que deben insertarse en la tabla de enlaces son las identidades generadas en la primera inserción.
tpower

Respuestas:

219

En una declaración : No.

En una transacción : sí

BEGIN TRANSACTION
   DECLARE @DataID int;
   INSERT INTO DataTable (Column1 ...) VALUES (....);
   SELECT @DataID = scope_identity();
   INSERT INTO LinkTable VALUES (@ObjectID, @DataID);
COMMIT

La buena noticia es que también se garantiza que el código anterior es atómico y se puede enviar al servidor desde una aplicación cliente con una cadena sql en una sola llamada de función como si fuera una declaración. También puede aplicar un disparador a una tabla para obtener el efecto de una sola inserción. Sin embargo, en última instancia, todavía son dos declaraciones y probablemente no desee ejecutar el disparador para cada inserción.

Joel Coehoorn
fuente
2
Esto es lo que estoy buscando durante mucho tiempo. Gracias :)
nandu.com
33
@ Joel, gran pregunta. Presumiblemente, alguien deseaba una realidad alternativa y tú eras portador de malas noticias. ;)
Kirk Woll
2
esto me salvó el día hoy :) gracias
Shekhar_Pro
12
Esto no resuelve el problema. Quiere insertar datos leídos de Object_Table. Es decir, una insert into ... select ...declaración. ¿Cómo lee el código anterior o recorre los datos de Object_Table? Aún así, debe usar una variable de tabla que el autor de la pregunta no quería hacer.
hofnarwillie
8
Claro que esto resuelve el problema. Tal vez no escribí todo el código para esto, pero el OP tampoco compartió todas las columnas que quería copiar. Las características demostradas en esta respuesta permitirán que el OP haga lo que está pidiendo ... ejecutar una consulta para crear un registro, obtener la ID del nuevo registro y usar esa ID para un segundo registro de una manera atómica. El OP ya sabe cómo hacer una inserción / selección. Esta es la pieza que le faltaba.
Joel Coehoorn
35

Todavía necesita dos INSERTdeclaraciones, pero parece que desea obtener la IDENTITYprimera inserción y usarla en la segunda, en cuyo caso, es posible que desee examinar OUTPUTo OUTPUT INTO: http://msdn.microsoft.com/en- us / library / ms177564.aspx

Cade Roux
fuente
1
¡Gracias! No sabía acerca de la palabra clave OUTPUT, exactamente lo que estaba buscando. +1
Rex Morgan
¿es posible usar "OUTPUT INTO" dos veces en un sql
V.Wu
@ V.Wu No lo creo, tendré que configurar una prueba para ver.
Cade Roux
18

Lo siguiente establece la situación que tuve, usando variables de tabla.

DECLARE @Object_Table TABLE
(
    Id INT NOT NULL PRIMARY KEY
)

DECLARE @Link_Table TABLE
(
    ObjectId INT NOT NULL,
    DataId INT NOT NULL
)

DECLARE @Data_Table TABLE
(
    Id INT NOT NULL Identity(1,1),
    Data VARCHAR(50) NOT NULL
)

-- create two objects '1' and '2'
INSERT INTO @Object_Table (Id) VALUES (1)
INSERT INTO @Object_Table (Id) VALUES (2)

-- create some data
INSERT INTO @Data_Table (Data) VALUES ('Data One')
INSERT INTO @Data_Table (Data) VALUES ('Data Two')

-- link all data to first object
INSERT INTO @Link_Table (ObjectId, DataId)
SELECT Objects.Id, Data.Id
FROM @Object_Table AS Objects, @Data_Table AS Data
WHERE Objects.Id = 1

Gracias a otra respuesta que me indicó la cláusula OUTPUT, puedo demostrar una solución:

-- now I want to copy the data from from object 1 to object 2 without looping
INSERT INTO @Data_Table (Data)
OUTPUT 2, INSERTED.Id INTO @Link_Table (ObjectId, DataId)
SELECT Data.Data
FROM @Data_Table AS Data INNER JOIN @Link_Table AS Link ON Data.Id = Link.DataId
                INNER JOIN @Object_Table AS Objects ON Link.ObjectId = Objects.Id 
WHERE Objects.Id = 1

Sin embargo, resulta que no es tan simple en la vida real debido al siguiente error

la cláusula OUTPUT INTO no puede estar en ningún lado de una relación (clave primaria, clave externa)

Todavía puedo OUTPUT INTOuna tabla temporal y luego terminar con la inserción normal. Entonces puedo evitar mi ciclo pero no puedo evitar la tabla temporal.

tpower
fuente
6

Parece que la tabla Enlace captura la relación muchos: muchos entre la tabla Objeto y la tabla Datos.

Mi sugerencia es utilizar un procedimiento almacenado para administrar las transacciones. Cuando desee insertar en la tabla Objeto o Datos, realice sus inserciones, obtenga los nuevos ID e insértelos en la tabla Enlace.

Esto permite que toda su lógica permanezca encapsulada en una sproc fácil de llamar.

Bob Probst
fuente
¿Por qué nadie más te ha votado? El procedimiento almacenado es la mejor manera obvia. ¡Combina tu respuesta con la respuesta de Joel Coehoorn y obtendrás la mejor respuesta!
Rhyous
4

Si desea que las acciones sean más o menos atómicas, me aseguraré de incluirlas en una transacción. De esa manera, puede estar seguro de que ambos sucedieron o que ambos no sucedieron según sea necesario.

Craig
fuente
2
Las acciones son atómicas si están envueltas en una transacción, no "más o menos" atómicas. Lo que no está necesariamente garantizado es el nivel de aislamiento, a menos que usted lo especifique.
Dave Markle
4

Puede crear una Vista seleccionando los nombres de columna requeridos por su declaración de inserción, agregar un Disparador INSTEAD OF INSERT e insertar en esta vista.

devio
fuente
4

Quiero hacer hincapié en usar

SET XACT_ABORT ON;

para la transacción MSSQL con múltiples sentencias sql.

Ver: https://msdn.microsoft.com/en-us/library/ms188792.aspx Proporcionan un muy buen ejemplo.

Entonces, el código final debería verse así:

SET XACT_ABORT ON;

BEGIN TRANSACTION
   DECLARE @DataID int;
   INSERT INTO DataTable (Column1 ...) VALUES (....);
   SELECT @DataID = scope_identity();
   INSERT INTO LinkTable VALUES (@ObjectID, @DataID);
COMMIT
Sergei Zinovyev
fuente
2

El inserto solo puede funcionar en una mesa a la vez. Las inserciones múltiples deben tener varias declaraciones.

No sé si necesita hacer un bucle a través de una variable de tabla: ¿no puede simplemente usar un inserto en masa en una tabla, luego el inserto en masa en la otra?

Por cierto, supongo que te refieres a copiar los datos de Object_Table; de lo contrario la pregunta no tiene sentido.

Carlton Jenke
fuente
2

Antes de poder hacer una inserción multitarea en Oracle, puede usar un truco que implique una inserción en una vista que tenga un disparador INSTEAD OF definido para realizar las inserciones. ¿Se puede hacer esto en SQL Server?

David Aldridge
fuente
-1
-- ================================================
-- Template generated from Template Explorer using:
-- Create Procedure (New Menu).SQL
--
-- Use the Specify Values for Template Parameters 
-- command (Ctrl-Shift-M) to fill in the parameter 
-- values below.
--
-- This block of comments will not be included in
-- the definition of the procedure.
-- ================================================
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO

ALTER PROCEDURE InsetIntoTwoTable

(
@name nvarchar(50),
@Email nvarchar(50)
)

AS
BEGIN

    SET NOCOUNT ON;


    insert into dbo.info(name) values (@name)
    insert into dbo.login(Email) values (@Email)
END
GO
FakirPori
fuente
¿Podría agregar algunas explicaciones?
Kyll
-2

// si quieres insertar lo mismo que la primera tabla

$qry = "INSERT INTO table (one, two, three) VALUES('$one','$two','$three')";

$result = @mysql_query($qry);

$qry2 = "INSERT INTO table2 (one,two, three) VVALUES('$one','$two','$three')";

$result = @mysql_query($qry2);

// o si quieres insertar ciertas partes de la tabla uno

 $qry = "INSERT INTO table (one, two, three) VALUES('$one','$two','$three')";


  $result = @mysql_query($qry);

 $qry2 = "INSERT INTO table2 (two) VALUES('$two')";

 $result = @mysql_query($qry2);

// Sé que parece demasiado bueno para estar en lo cierto, pero funciona y puedes seguir agregando consultas solo cambia el

    "$qry"-number and number in @mysql_query($qry"")

Tengo 17 tablas en las que esto ha funcionado.

Brion
fuente
si algo sale mal en medio de las inserciones? Sus insertos estarán incompletos. ¿Derecha? Si lo hace, ¿tiene una función de reversión para tratarlo? Si no ... tiene un problema con la integridad de sus datos.
deepcell
77
-1. Esta respuesta parece estar usando métodos MySQL en PHP. La pregunta está etiquetada como sql y sql-server , sin mencionar MySQL o PHP.
mskfisher