Utilisation de colonnes source dans la clause OUTPUT INTO d'une instruction INSERT (SQL Server)


15

J'écris une instruction d'insertion de traitement par lots et je voudrais utiliser une table temporaire pour garder une trace des ID insérés au lieu de parcourir les éléments moi-même et d'appeler SCOPE_IDENTITY () pour chaque ligne insérée.

Les données qui doivent être insérées ont des identifiants (temporaires) les reliant à d'autres données qui devraient également être insérées dans une autre table, j'ai donc besoin d'une référence croisée de l'identifiant réel et de l'identifiant temporaire.

Voici un exemple de ce que j'ai jusqu'à présent:

-- The existing table 
DECLARE @MyTable TABLE (ID INT IDENTITY(1,1), [Name] NVARCHAR(MAX));

-- My data I want to insert
DECLARE @MyInsertData TABLE (ID INT, [Name] NVARCHAR(MAX));
INSERT INTO @MyInsertData ( ID,Name)
VALUES ( -1 , 'bla'),(-2,'test'),(-3,'last');

DECLARE @MyCrossRef TABLE ([NewId] INT, OldId INT);

INSERT INTO @MyTable ( [Name] )
   OUTPUT Inserted.ID, INS.ID INTO @MyCrossRef
   SELECT [NAME] FROM @MyInsertData INS

-- Check the result
SELECT * FROM @MyCrossRef

Le problème est que je ne peux pas obtenir la clause OUTPUT INTO pour accepter l'ID, j'ai essayé @MyInsertData.IDet d'autres astuces joignant la table à elle-même, mais rien ne semble fonctionner.

Réponses:


27

En fait, vous pouvez réaliser la même chose en changeant votre INSERTen a MERGE. Bien que l' MERGEinstruction soit en fait un moyen assez soigné de faire des "upserts" dans SQL Server, rien ne vous empêche de l'utiliser uniquement dans le but d'insérer:

-- The existing table 
DECLARE @MyTable TABLE (ID INT IDENTITY(1,1), [Name] NVARCHAR(MAX));

-- My data I want to insert
DECLARE @MyInsertData TABLE (ID INT, [Name] NVARCHAR(MAX));
INSERT INTO @MyInsertData ( ID,Name)
VALUES ( -1 , 'bla'),(-2,'test'),(-3,'last');

DECLARE @MyCrossRef TABLE ([NewId] INT, OldId INT);

MERGE INTO @MyTable AS dest
USING @MyInsertData AS ins ON 1=0   -- always false

WHEN NOT MATCHED BY TARGET          -- happens for every row, because 1 is never 0
    THEN INSERT ([Name])
         VALUES (ins.[NAME])

OUTPUT inserted.ID, ins.ID
INTO @MyCrossRef (NewId, OldId);

-- Check the result
SELECT * FROM @MyCrossRef

L'une des bonnes choses à propos de cela MERGEest qu'il vous permet d'accéder aux colonnes source ainsi qu'aux tables intégrées insertedet aux deletedtables de la OUTPUTclause.

Mon code peut contenir des erreurs, car je ne l'ai pas testé. Mon article de blog d'il y a quelques années va un peu plus en détail, y compris sur les performances des requêtes.


C'est génial! Cette fois, je devrai m'en tenir à une boucle car la prise en charge de SQL Server 2005 est requise. Cependant, je garderai cela à l'esprit pour les projets futurs. Merci!
Louis Somers

3
Brillant, cela devrait être la réponse acceptée.
Chris Peacock

3
J'aimerais que ce soit dans stackoverflow au lieu de dba stackexchange. Il a trop peu de visibilité. Réponse incroyable.
Lordbalmon

3
A fonctionné comme un charme dès le premier essai ... réponse géniale!
BeemerGuy

5

La clause de sortie ne peut accéder qu'aux données des lignes cibles et des constantes / variables, pas aux données provenant ailleurs dans la source SELECT, comme si vous fonctionniez dans un déclencheur.

https://docs.microsoft.com/en-us/sql/t-sql/queries/output-clause-transact-sql indique:

Toute référence aux colonnes du tableau en cours de modification doit être qualifiée avec le préfixe INSERTED ou DELETED.

Donc, pour obtenir l'ID d'origine, vous devez l'inclure dans la table de destination afin que la clause de sortie puisse le renvoyer, comme suit:

-- The existing table 
DECLARE @MyTable TABLE (ID INT IDENTITY(1,1), [Name] NVARCHAR(MAX), SourceID INT);

-- My data I want to insert
DECLARE @MyInsertData TABLE (ID INT, [Name] NVARCHAR(MAX));
INSERT INTO @MyInsertData ( ID,Name)
VALUES ( -1 , 'bla'),(-2,'test'),(-3,'last');

DECLARE @MyCrossRef TABLE ([NewId] INT, OldId INT);

INSERT INTO @MyTable ( [Name], SourceID )
   OUTPUT Inserted.ID, Inserted.SourceID INTO @MyCrossRef
   SELECT [NAME], ID FROM @MyInsertData INS

-- Check the result
SELECT * FROM @MyCrossRef

bien que la modification du schéma de l'objet cible puisse ne pas être pratique dans votre situation, cela peut ne pas être applicable.


3
C'est assez décevant. Cela rend la clause de sortie inutile pour mon scénario à moins qu'il n'y ait une deuxième colonne qui peut être utilisée comme clé :-( Oh, eh bien, au moins je peux arrêter d'essayer et de continuer la boucle. Merci.
Louis Somers
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.