web-dev-qa-db-fra.com

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

J'écris une instruction d'insertion de traitement par lots et je voudrais utiliser une table temporaire pour suivre les 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.ID et d'autres astuces joignant la table à elle-même, mais rien ne semble fonctionner.

15
Louis Somers

En fait, vous pouvez réaliser la même chose en changeant votre INSERT en MERGE. Bien que l'instruction MERGE soit en fait une façon assez intéressante 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

Une des bonnes choses à propos de MERGE est qu'il vous permet d'accéder aux colonnes source ainsi qu'aux inserted et deleted tables dans la clause OUTPUT.

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 dans un peu plus de détails, y compris sur les performances des requêtes.

27
Daniel Hutmacher

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 de la table 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.

5
David Spillett