SQL Server: est-il possible d'insérer dans deux tables en même temps?

143

Ma base de données contient trois tables appelées Object_Table, Data_Tableet Link_Table. La table de liaison contient juste deux colonnes, l'identité d'un enregistrement d'objet et l'identité d'un enregistrement de données.

Je souhaite copier les données d' DATA_TABLEoù elles sont liées à une identité d'objet donnée et insérer les enregistrements correspondants dans Data_Tableet Link_Tablepour une identité d'objet donnée différente.

Je peux le faire en sélectionnant dans une variable de table et en effectuant une boucle en faisant deux insertions pour chaque itération.

Est-ce la meilleure façon de le faire?

Edit : Je veux éviter une boucle pour deux raisons, la première est que je suis paresseux et une table de boucle / temporaire nécessite plus de code, plus de code signifie plus d'endroits pour faire une erreur et la deuxième raison est un problème de performances.

Je peux copier toutes les données dans une seule insertion, mais comment faire pour que la table de liens soit liée aux nouveaux enregistrements de données où chaque enregistrement a un nouvel identifiant?

puissance
la source
Je n'ai pas l'intérêt d'essayer de le faire avec UN insert, quand le faire avec 2 inserts fonctionne parfaitement bien. Voulez-vous dire que vous voulez vous assurer que les 2 inserts sont tous deux terminés? Ensuite, vous devrez vérifier cette instruction de commit / rollback.
Philippe Grondier
2
Je serais heureux avec deux insertions, c'est juste que les identités qui doivent être insérées dans la table de liens sont les identités générées dans le premier insert.
tpower

Réponses:

219

Dans une déclaration : Non.

En une seule transaction : Oui

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

La bonne nouvelle est que le code ci-dessus est également garanti atomique et peut être envoyé au serveur à partir d'une application cliente avec une chaîne SQL en un seul appel de fonction comme s'il s'agissait d'une seule instruction. Vous pouvez également appliquer un déclencheur à une table pour obtenir l'effet d'un seul insert. Cependant, il s'agit toujours de deux instructions et vous ne voulez probablement pas exécuter le déclencheur pour chaque insertion.

Joël Coehoorn
la source
2
C'est ce que je recherche depuis longtemps. Merci :)
nandu.com
33
@Joel, excellente question. Vraisemblablement, quelqu'un souhaitait une réalité alternative et vous étiez porteur de mauvaises nouvelles. ;)
Kirk Woll
2
cela a sauvé ma journée aujourd'hui :)
merci
12
Cela ne résout pas le problème. Il veut insérer des données lues à partir d'Object_Table. Ie une insert into ... select ...déclaration. Comment le code ci-dessus lit-il ou boucle-t-il les données Object_Table. Vous devez toujours utiliser une variable de table que le demandeur n'a pas voulu faire.
hofnarwillie
8
Bien sûr, cela résout le problème. Peut-être que je n'ai pas écrit tout le code pour cela, mais l'OP n'a pas non plus partagé toutes les colonnes qu'il voulait copier. Les fonctionnalités démontrées dans cette réponse permettront à l'OP de faire ce qu'il demande ... exécuter une requête pour créer un enregistrement, obtenir l'ID du nouvel enregistrement et utiliser cet ID pour un deuxième enregistrement de manière atomique. L'OP sait déjà comment faire une insertion / sélection. C'est la pièce qui lui manquait.
Joel Coehoorn
35

Vous avez toujours besoin de deux INSERTinstructions, mais il semble que vous souhaitiez obtenir le à IDENTITYpartir du premier insert et l'utiliser dans le second, auquel cas, vous voudrez peut-être consulter OUTPUTou OUTPUT INTO: http://msdn.microsoft.com/en- us / library / ms177564.aspx

Cade Roux
la source
1
Merci! Je ne connaissais pas le mot-clé OUTPUT, exactement ce que je cherchais. +1
Rex Morgan
est-il possible d'utiliser "OUTPUT INTO" deux fois dans un sql
V.Wu
@ V.Wu Je ne pense pas, je vais devoir mettre en place un test pour voir.
Cade Roux le
18

Ce qui suit met en place la situation que j'ai eue, en utilisant des variables de table.

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

Grâce à une autre réponse qui m'a pointé vers la clause OUTPUT, je peux démontrer une solution:

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

Il s'avère cependant que ce n'est pas si simple dans la vraie vie à cause de l'erreur suivante

la clause OUTPUT INTO ne peut pas être d'un côté ou de l'autre d'une relation (clé primaire, clé étrangère)

Je peux encore OUTPUT INTOune table temporaire et ensuite terminer avec un insert normal. Je peux donc éviter ma boucle mais je ne peux pas éviter la table temporaire.

puissance
la source
6

On dirait que la table Link capture la relation plusieurs: plusieurs entre la table Object et la table Data.

Ma suggestion est d'utiliser une procédure stockée pour gérer les transactions. Lorsque vous souhaitez insérer dans la table Object ou Data, effectuez vos insertions, récupérez les nouveaux ID et insérez-les dans la table Link.

Cela permet à toute votre logique de rester encapsulée dans un sproc facile à appeler.

Bob Probst
la source
Pourquoi personne d'autre ne vous a voté? La procédure stockée est la meilleure et la plus évidente. Combinez votre réponse avec la réponse de Joel Coehoorn et vous obtenez la meilleure réponse!
Rhyous
4

Si vous voulez que les actions soient plus ou moins atomiques, je veillerais à les envelopper dans une transaction. De cette façon, vous pouvez être sûr que les deux se sont produits ou que les deux ne se sont pas produits au besoin.

Craig
la source
2
Les actions sont atomiques si elles sont encapsulées dans une transaction, et non "plus ou moins" atomiques. Ce qui n'est pas nécessairement garanti, c'est le niveau d'isolement, sauf si vous le spécifiez.
Dave Markle
4

Vous pouvez créer une vue en sélectionnant les noms de colonne requis par votre instruction d'insertion, ajouter un déclencheur INSTEAD OF INSERT et l'insérer dans cette vue.

devio
la source
4

Je veux insister sur l'utilisation

SET XACT_ABORT ON;

pour la transaction MSSQL avec plusieurs instructions SQL.

Voir: https://msdn.microsoft.com/en-us/library/ms188792.aspx Ils fournissent un très bon exemple.

Ainsi, le code final devrait ressembler à ce qui suit:

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
Sergueï Zinovyev
la source
2

L'insert ne peut fonctionner que sur une seule table à la fois. Plusieurs insertions doivent avoir plusieurs instructions.

Je ne sais pas si vous devez faire le bouclage à travers une variable de table - ne pouvez-vous pas simplement utiliser une insertion de masse dans une table, puis l'insertion de masse dans l'autre?

Au fait - je suppose que vous voulez dire copier les données de Object_Table; sinon la question n'a pas de sens.

Carlton Jenke
la source
2

Avant de pouvoir effectuer une insertion multitable dans Oracle, vous pouvez utiliser une astuce impliquant une insertion dans une vue sur laquelle un déclencheur INSTEAD OF est défini pour effectuer les insertions. Cela peut-il être fait dans SQL Server?

David Aldridge
la source
-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
la source
Pouvez-vous ajouter quelques explications?
Kyll
-2

// si vous voulez insérer le même que le premier tableau

$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 si vous souhaitez insérer certaines parties du tableau un

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

// Je sais que cela semble trop beau pour avoir raison, mais cela fonctionne et vous pouvez continuer à ajouter des requêtes, il suffit de changer le

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

J'ai 17 tables dans lesquelles cela a fonctionné.

Brion
la source
si quelque chose ne va pas au milieu des inserts? Vos encarts seront incomplets. droite? Dans ce cas, avez-vous une fonction de restauration pour le traiter? Sinon, vous avez un problème avec l'intégrité de vos données.
deepcell
7
-1. Cette réponse semble utiliser les méthodes MySQL en PHP. La question est étiquetée sql et sql-server , sans aucune mention de MySQL ou PHP.
mskfisher