Ma base de données contient trois tables appelées Object_Table
, Data_Table
et Link_Table
. La table de liens ne contient que deux colonnes, l'identité d'un enregistrement d'objet et l'identité d'un enregistrement de données.
Je veux copier les données de DATA_TABLE
où elles sont liées à une identité d'objet donnée et insérer les enregistrements correspondants dans Data_Table
et Link_Table
pour une identité d'objet donnée différente.
Je peut le faire en sélectionnant une variable de table et la boucle en effectuant 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 qu'une table boucle/temp nécessite plus de code, plus de code signifie plus d'endroits pour faire une erreur et la seconde est une préoccupation à propos de la performance.
Je peux copier toutes les données dans un seul insert, mais comment faire en sorte que la table des liens soit liée aux nouveaux enregistrements de données où chaque enregistrement a un nouvel identifiant?
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 à un autre réponse qui m'a orienté 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 vie réelle à 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 toujours OUTPUT INTO
une table temporaire puis terminer par une insertion normale. Je peux donc éviter ma boucle mais je ne peux pas éviter la table temporaire.
En un déclaration: Non.
Dans un 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 dans 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’une seule insertion. Cependant, il reste finalement deux instructions et vous ne souhaiterez probablement pas exécuter le déclencheur de every insert.
Vous avez toujours besoin de deux instructions INSERT
, mais il semblerait que vous souhaitiez obtenir le IDENTITY
du premier insert et l’utiliser dans le second. Dans ce cas, vous voudrez peut-être examiner OUTPUT
ou OUTPUT INTO
: http://msdn.Microsoft.com/en-us/library/ms177564.aspx
Il semble que la table des liens capture les relations nombreuses: nombreuses 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, obtenez les nouveaux ID et insérez-les dans la table Link.
Cela permet à toute votre logique de rester encapsulée dans un seul appelant sproc.
Si vous voulez que les actions soient plus ou moins atomiques, je m'assurerais de les envelopper dans une transaction. De cette façon, vous pouvez être sûr que les deux sont arrivés ou que les deux ne sont pas arrivés au besoin.
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 à ceci:
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
Vous pouvez créer une vue en sélectionnant les noms de colonne requis par votre instruction insert, ajouter un déclencheur INSTEAD OF INSERT et l'insérer dans cette vue.
Insert ne peut fonctionner que sur une table à la fois. Plusieurs inserts doivent avoir plusieurs déclarations.
Je ne sais pas si vous devez faire la boucle dans une variable de table - ne pouvez-vous pas simplement insérer un insert de masse dans un tableau, puis l'insertion de masse dans l'autre?
En passant - je suppose que vous voulez dire copier les données de Object_Table; sinon la question n'a pas de sens.
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 a été défini pour effectuer les insertions. Cela peut-il être fait dans SQL Server?