SQL Server: É possível inserir em duas tabelas ao mesmo tempo?

143

Meu banco de dados contém três tabelas chamadas Object_Table, Data_Tablee Link_Table. A tabela de links contém apenas duas colunas, a identidade de um registro de objeto e a identidade de um registro de dados.

Quero copiar os dados de DATA_TABLEonde eles estão vinculados a uma determinada identidade de objeto e inserir registros correspondentes em Data_Tablee Link_Tablepara uma identidade de objeto diferente.

Eu posso fazer isso selecionando em uma variável de tabela e fazendo o loop fazendo duas inserções para cada iteração.

Esse é o melhor jeito de fazer isso?

Edit : Eu quero evitar um loop por dois motivos, o primeiro é que eu sou preguiçoso e uma tabela loop / temp requer mais código, mais código significa mais lugares para cometer um erro e o segundo motivo é uma preocupação com o desempenho.

Posso copiar todos os dados em uma inserção, mas como fazer com que a tabela de links se vincule aos novos registros de dados em que cada registro possui um novo ID?

tpower
fonte
Eu não tenho o interesse de tentar fazê-lo com uma pastilha, quando fazê-la com 2 pastilhas funciona perfeitamente bem. Você quer ter certeza de que as duas inserções foram concluídas? Então você terá que verificar esta instrução de confirmação / reversão.
Philippe Grondier 6/10/08
2
Eu ficaria feliz com duas inserções, é apenas que as identidades que precisam ser inseridas na tabela de links são as identidades geradas na primeira inserção.
tpower 23/10/08

Respostas:

219

Em uma declaração : Não.

Em uma transação : Sim

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

A boa notícia é que o código acima também é garantido como atômico e pode ser enviado ao servidor a partir de um aplicativo cliente com uma string sql em uma única chamada de função, como se fosse uma declaração. Você também pode aplicar um gatilho a uma tabela para obter o efeito de uma única inserção. No entanto, em última análise, ainda existem duas instruções e você provavelmente não deseja executar o gatilho para cada inserção.

Joel Coehoorn
fonte
2
É isso que estou procurando há muito tempo. Obrigado :)
nandu.com
33
@ Joel, ótima pergunta. Presumivelmente, alguém desejava uma realidade alternativa e você era portador de más notícias. ;)
Kirk Woll 10/09
2
este salvou o meu dia hoje :) thanx
Shekhar_Pro
12
Isso não resolve o problema. Ele deseja inserir dados lidos de Object_Table. Ou seja, uma insert into ... select ...declaração. Como o código acima lê ou faz um loop nos dados de Object_Table. Você ainda precisará usar uma variável de tabela que o solicitante não quis fazer.
precisa saber é o seguinte
8
Claro que isso resolve o problema. Talvez eu não tenha escrito todo o código para isso, mas o OP também não compartilhou todas as colunas que ele queria copiar. Os recursos demonstrados nesta resposta permitirão que o OP faça o que ele está pedindo ... execute uma consulta para criar um registro, obtenha o ID do novo registro e use esse ID para um segundo registro de maneira atômica. O OP já sabe como inserir / selecionar. Esta é a peça que ele estava perdendo.
Joel Coehoorn
35

Você ainda precisa de duas INSERTinstruções, mas parece que deseja obter a IDENTITYprimeira inserção e usá-la na segunda; nesse caso, convém procurar OUTPUTou OUTPUT INTO: http://msdn.microsoft.com/en- pt-br / library / ms177564.aspx

Cade Roux
fonte
1
Obrigado! Eu não sabia sobre a palavra-chave OUTPUT, exatamente o que estava procurando. +1
Rex Morgan
é possível usar "OUTPUT INTO" duas vezes em um sql
V.Wu
@ V.Wu Acho que não, terei que fazer um teste para ver.
Cade Roux
18

O seguinte configura a situação que tive, usando variáveis ​​de tabela.

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

Graças a outra resposta que me indicou a cláusula OUTPUT, posso demonstrar uma solução:

-- 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

Acontece, no entanto, que não é tão simples na vida real devido ao seguinte erro

a cláusula OUTPUT INTO não pode estar nos dois lados de um relacionamento (chave primária, chave estrangeira)

Ainda posso usar OUTPUT INTOuma tabela temporária e terminar com a inserção normal. Portanto, posso evitar meu loop, mas não posso evitar a tabela temporária.

tpower
fonte
6

Parece que a tabela Link captura o relacionamento many: many entre a tabela Object e a tabela Data.

Minha sugestão é usar um procedimento armazenado para gerenciar as transações. Quando você deseja inserir na tabela Objeto ou Dados, faça suas inserções, obtenha os novos IDs e insira-os na tabela Link.

Isso permite que toda a sua lógica permaneça encapsulada em um sproc fácil de chamar.

Bob Probst
fonte
Por que ninguém mais te votou? O procedimento armazenado é a maneira mais óbvia e melhor. Combine sua resposta com a resposta de Joel Coehoorn e você obterá a melhor resposta!
Rhyous
4

Se você deseja que as ações sejam mais ou menos atômicas, certifique-se de envolvê-las em uma transação. Dessa forma, você pode ter certeza de que ambos aconteceram ou não, conforme necessário.

Craig
fonte
2
As ações são atômicas se estiverem envolvidas em uma transação, não "mais ou menos" atômicas. O que não é necessariamente garantido é o nível de isolamento, a menos que você o especifique.
Dave Markle
4

Você pode criar uma Visualização selecionando os nomes das colunas exigidas por sua instrução de inserção, adicionar um acionador INSTEAD OF INSERT e inserir nessa visualização.

devio
fonte
4

Eu quero enfatizar o uso

SET XACT_ABORT ON;

para a transação MSSQL com várias instruções sql.

Consulte: https://msdn.microsoft.com/en-us/library/ms188792.aspx Eles fornecem um exemplo muito bom.

Portanto, o código final deve se parecer com o seguinte:

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
fonte
2

O Insert só pode operar em uma mesa por vez. Múltiplas inserções precisam ter várias instruções.

Não sei se você precisa fazer o loop através de uma variável de tabela - você não pode simplesmente usar uma inserção em massa em uma tabela e depois a inserção em massa na outra?

A propósito - acho que você quer dizer copiar os dados de Object_Table; caso contrário, a questão não faz sentido.

Carlton Jenke
fonte
2

Antes de poder fazer uma inserção multititável no Oracle, você poderia usar um truque envolvendo uma inserção em uma exibição que tivesse um gatilho INSTEAD OF definido para executar as inserções. Isso pode ser feito no SQL Server?

David Aldridge
fonte
-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
fonte
Você poderia adicionar algumas explicações?
Kyll
-2

// se você deseja inserir o mesmo que a primeira tabela

$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);

// ou se você deseja inserir certas partes da tabela um

 $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);

// sei que parece bom demais para estar certo, mas funciona e você pode continuar adicionando consultas, basta alterar o

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

Eu tenho 17 tabelas em que isso funcionou.

Brion
fonte
se algo der errado no meio das inserções? Suas inserções estarão incompletas. certo? Se fizer isso .. você tem uma função de reversão para tratá-lo? Caso contrário, você tem um problema com a integridade dos dados.
deepcell
7
-1. Esta resposta parece estar usando métodos MySQL em PHP. A questão é identificada como sql e sql-server , sem menção ao MySQL ou PHP.
precisa saber é