SQL Server: è possibile inserire due tabelle contemporaneamente?


143

Il mio database contiene tre tabelle denominate Object_Table, Data_Tablee Link_Table. La tabella dei collegamenti contiene solo due colonne, l'identità di un record di oggetto e un'identità di un record di dati.

Voglio copiare i dati da DATA_TABLEdove sono collegati a una determinata identità dell'oggetto e inserire i record corrispondenti in Data_Tablee Link_Tableper una diversa identità dell'oggetto.

Io posso farlo selezionando in una variabile di tabella e il loop attraverso facendo due inserti per ogni iterazione.

È questo il modo migliore per farlo?

Modifica : voglio evitare un loop per due motivi, il primo è che sono pigro e una tabella loop / temp richiede più codice, più codice significa più luoghi in cui fare un errore e il secondo motivo è una preoccupazione per le prestazioni.

Posso copiare tutti i dati in un solo inserto, ma come ottenere la tabella dei collegamenti per collegarsi ai nuovi record di dati in cui ogni record ha un nuovo ID?


Non mi interessa provare a farlo con UN inserto, quando farlo con 2 inserti funziona perfettamente. Vuoi dire che vuoi assicurarti che i 2 inserti siano entrambi completati? Quindi dovrai controllare queste istruzioni di commit / rollback.
Philippe Grondier,

2
Sarei felice con due inserti, è solo che le identità che devono essere inserite nella tabella dei collegamenti sono le identità generate nel primo inserto.
potenza

Risposte:


219

In una frase : No.

In una transazione : Sì

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

La buona notizia è che anche il codice sopra è garantito per essere atomico e può essere inviato al server da un'applicazione client con una stringa sql in una singola chiamata di funzione come se fosse un'istruzione. È inoltre possibile applicare un trigger a una tabella per ottenere l'effetto di un singolo inserimento. Tuttavia, in definitiva sono ancora due affermazioni e probabilmente non si desidera eseguire il trigger per ogni inserimento.


2
Questo è quello che sto cercando da molto tempo. Grazie :)
nandu.com

33
@Joel, ottima domanda. Presumibilmente qualcuno desiderava una realtà alternativa e tu eri il portatore di cattive notizie. ;)
Kirk Woll il

2
questo mi ha salvato la giornata oggi :) grazie
Shekhar_Pro

12
Questo non risolve il problema. Vuole inserire i dati letti da Object_Table. Cioè una insert into ... select ...dichiarazione. In che modo il codice sopra riportato legge o scorre i dati Object_Table. È quindi necessario utilizzare una variabile di tabella che il richiedente non voleva fare.
hofnarwillie,

8
Sicuro questo risolve il problema. Forse non ho scritto tutto il codice per questo, ma poi l'OP non ha condiviso tutte le colonne che voleva copiare. Le funzionalità dimostrate in questa risposta consentiranno all'OP di fare ciò che sta chiedendo ... eseguire una query per creare un record, ottenere l'ID del nuovo record e utilizzare tale ID per un secondo record in modo atomico. L'OP sa già come eseguire un inserimento / selezione. Questo è il pezzo che gli mancava.
Joel Coehoorn,

35

Hai ancora bisogno di due INSERTistruzioni, ma sembra che tu voglia ottenere il IDENTITYprimo inserto e usarlo nel secondo, nel qual caso, potresti voler esaminare OUTPUTo OUTPUT INTO: http://msdn.microsoft.com/en- us / library / ms177564.aspx


1
Grazie! Non sapevo della parola chiave OUTPUT, esattamente quello che stavo cercando. +1
Rex Morgan,

è possibile usare "OUTPUT INTO" due volte in un sql
V.Wu

@ V.Wu non credo, dovrò impostare un test per vedere.
Cade Roux,

18

Quanto segue imposta la situazione che ho avuto, usando le variabili di tabella.

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

Grazie a un'altra risposta che mi ha indicato la clausola OUTPUT, posso dimostrare una soluzione:

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

Si scopre tuttavia che non è così semplice nella vita reale a causa del seguente errore

la clausola OUTPUT INTO non può trovarsi su entrambi i lati di una relazione (chiave primaria, chiave esterna)

Posso ancora OUTPUT INTOuna tabella temporanea e poi finire con un normale inserto. Quindi posso evitare il mio ciclo ma non posso evitare la tabella temporanea.



6

Sembra che la tabella Link catturi i molti: molte relazioni tra la tabella Object e la tabella Data.

Il mio suggerimento è di utilizzare una procedura memorizzata per gestire le transazioni. Quando si desidera inserire nella tabella Oggetto o Dati eseguire gli inserimenti, ottenere i nuovi ID e inserirli nella tabella Collegamento.

Ciò consente a tutta la tua logica di rimanere incapsulata in uno sproc facile da chiamare.


Perché nessun altro ti ha votato? La procedura memorizzata è il modo ovvio e migliore. Combina la tua risposta con la risposta di Joel Coehoorn e otterrai la risposta migliore!
Rhyous,

4

Se vuoi che le azioni siano più o meno atomiche, mi assicurerei di includerle in una transazione. In questo modo puoi essere sicuro che entrambi siano accaduti o che entrambi non siano avvenuti secondo necessità.


2
Le azioni sono atomiche se sono racchiuse in una transazione, non "più o meno" atomiche. Ciò che non è necessariamente garantito è il livello di isolamento, a meno che non venga specificato.
Dave Markle,

4

È possibile creare una vista selezionando i nomi di colonna richiesti dall'istruzione insert, aggiungere un trigger INSTEAD OF INSERT e inserirla in questa vista.


4

Voglio sottolineare l'utilizzo

SET XACT_ABORT ON;

per la transazione MSSQL con più istruzioni sql.

Vedi: https://msdn.microsoft.com/en-us/library/ms188792.aspx Forniscono un ottimo esempio.

Quindi, il codice finale dovrebbe essere simile al seguente:

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

2

L'inserimento può funzionare solo su una tabella alla volta. Gli inserti multipli devono avere più istruzioni.

Non so che devi eseguire il ciclo attraverso una variabile di tabella: non puoi semplicemente usare un inserto di massa in una tabella, quindi l'inserto di massa nell'altra?

A proposito: immagino che intendi copiare i dati da Object_Table; altrimenti la domanda non ha senso.


2

Prima di essere in grado di eseguire un inserimento a più livelli in Oracle, è possibile utilizzare un trucco che coinvolge un inserimento in una vista in cui è stato definito un trigger INSTEAD OF per eseguire gli inserimenti. Questo può essere fatto in SQL Server?


-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

Potresti aggiungere qualche spiegazione?
Kyll

-2

// se si desidera inserire lo stesso della prima tabella

$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 se si desidera inserire alcune parti della tabella 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);

// so che sembra troppo bello per essere giusto, ma funziona e puoi continuare ad aggiungere query basta cambiare il

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

Ho 17 tavoli in cui ha funzionato.


se qualcosa va storto nel mezzo di inserti? I tuoi inserti saranno incompleti. giusto? Se lo fai .. hai una funzione di rollback per trattarla? Altrimenti .. hai un problema con l'integrità dei tuoi dati.
deepcell,

7
-1. Questa risposta sembra utilizzare i metodi MySQL in PHP. La domanda è taggata sql e sql-server , senza menzione di MySQL o PHP.
mskfisher,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.